Using Google App Engine Files API with GWTUpload

415 views
Skip to first unread message

Vyacheslav Sokolov

unread,
Apr 9, 2011, 12:42:58 PM4/9/11
to gwtupload
Did anyone of GWTUpload developers notice this new API introduced by
Google about a week ago? It allows handling a Blobstore blob much like
an ordinary file: now a servlet can create a blob itself, open a
java.io.OutputStream for the blob and write to it.
It solves all problems related to using GWTUpload with Google App
Engine. I already wrote FileItem, FileItemFactory and UploadAction
descendants for GWTUpload that use Files API. They are debugged, and
they work.
I'd like to share these sources with GWTUpload developers. Is anyone
interested?

Manuel Carrasco Moñino

unread,
Apr 10, 2011, 10:26:00 AM4/10/11
to gwtu...@googlegroups.com, Vyacheslav Sokolov
Please attach the files in a ticket, and I'll be pleased to include in
the distribution.

- Manolo

> --
> --
> http://groups.google.com/group/gwtupload
>

Kayode Odeyemi

unread,
Apr 10, 2011, 10:25:58 AM4/10/11
to gwtu...@googlegroups.com
Absolutely. Please share. I've been working on this for about 4 days now. I also posted my errors with Google App Engine on this group. I will be so interested.

Regards




--
Odeyemi 'Kayode O.
http://www.sinati.com

Vyacheslav Sokolov

unread,
Apr 10, 2011, 11:35:07 AM4/10/11
to gwtupload
> Absolutely. Please share. I've been working on this for about 4 days now. I
> also posted my errors with Google App Engine on this group. I will be so
> interested.

Here it is. The solution consists of 4 classes:
=1=================================================================
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.nio.channels.Channels;
import org.apache.commons.fileupload.FileItem;
import com.google.appengine.api.blobstore.BlobInfo;
import com.google.appengine.api.blobstore.BlobInfoFactory;
import com.google.appengine.api.blobstore.BlobKey;
import com.google.appengine.api.blobstore.BlobstoreService;
import com.google.appengine.api.blobstore.BlobstoreServiceFactory;
import com.google.appengine.api.files.AppEngineFile;
import com.google.appengine.api.files.FileReadChannel;
import com.google.appengine.api.files.FileService;
import com.google.appengine.api.files.FileServiceFactory;

public class FilesAPIFileItem implements FileItem {
private static final long serialVersionUID = 1;

private String field;
private String type;
private boolean formField;
private String name;

static private FileService fileService =
FileServiceFactory.getFileService();
static private BlobstoreService blobstoreService =
BlobstoreServiceFactory.getBlobstoreService();
private AppEngineFile file = null;

public FilesAPIFileItem(
String fieldName,
String contentType,
boolean isFormField,
String fileName ) throws IOException {
field = fieldName;
type = contentType;
formField = isFormField;
name = fileName;

file = fileService.createNewBlobFile( contentType );
}

@Override
public void delete() {
BlobKey key = getKey();
if( key != null ) {
blobstoreService.delete( key );
file = null;
}
}

@Override
public byte[] get() {
BlobKey key = getKey();
if( key == null )
return null;
return blobstoreService.fetchData( key, 0, getSize() - 1 );
}

@Override
public String getContentType() {
return type;
}

@Override
public String getFieldName() {
return field;
}

@Override
public InputStream getInputStream() throws IOException {
if( file == null )
return null;
FileReadChannel channel = fileService.openReadChannel( file,
false );
return Channels.newInputStream( channel );
}

@Override
public String getName() {
return name;
}

@Override
public OutputStream getOutputStream() throws IOException {
if( file == null )
return null;
return new FilesAPIOutputStream( fileService, file );
}

@Override
public long getSize() {
BlobKey key = getKey();
if( key == null )
return 0;
BlobInfo info = new BlobInfoFactory().loadBlobInfo( key );
if( info == null )
return 0;
return info.getSize();
}

@Override
public String getString() {
return get().toString();
}

@Override
public String getString( String encoding ) throws
UnsupportedEncodingException {
return new String( get(), encoding );
}

@Override
public boolean isFormField() {
return formField;
}

@Override
public boolean isInMemory() {
return false;
}

@Override
public void setFieldName( String arg0 ) {
field = arg0;
}

@Override
public void setFormField( boolean arg0 ) {
formField = arg0;

}

@Override
public void write(File arg0) throws Exception {
throw new UnsupportedOperationException( "Writing to file is
not allowed" );
}

public BlobKey getKey() {
if( file == null )
return null;
return fileService.getBlobKey( file );
}

}
=2===============================================================
import gwtupload.server.MemoryFileItemFactory;
import java.io.IOException;
import org.apache.commons.fileupload.FileItem;

public class FilesAPIFileItemFactory extends MemoryFileItemFactory {
private static final long serialVersionUID = 1;

public FilesAPIFileItemFactory() {
// Consider that there are no fields in the form being posted whose
value can exceed 1024 bytes
super( 1024 );
}

@Override
public FileItem createItem( String fieldName, String contentType,
boolean isFormField, String fileName ) {
if( isFormField )
return super.createItem( fieldName, contentType, isFormField,
fileName );
try {
return new FilesAPIFileItem( fieldName, contentType, isFormField,
fileName );
} catch( IOException x ) {
return null;
}
}
}
=3========================================================================
import java.io.IOException;
import java.io.OutputStream;
import java.nio.channels.Channels;
import com.google.appengine.api.files.AppEngineFile;
import com.google.appengine.api.files.FileService;
import com.google.appengine.api.files.FileWriteChannel;

public class FilesAPIOutputStream extends OutputStream {

private FileWriteChannel channel;
private OutputStream stream;

public FilesAPIOutputStream( FileService service, AppEngineFile
file ) throws IOException {
channel = service.openWriteChannel( file, true );
stream = Channels.newOutputStream( channel );
}


@Override
public void close() throws IOException {
stream.close();
channel.closeFinally();
}

@Override
public void flush() throws IOException {
stream.flush();
}

@Override
public void write(byte[] b, int off, int len) throws IOException {
stream.write( b, off, len );
}

@Override
public void write(byte[] b) throws IOException {
stream.write( b );
}

@Override
public void write(int b) throws IOException {
stream.write( b );
}

}
=4================================================================
import org.apache.commons.fileupload.FileItemFactory;
import gwtupload.server.UploadAction;

public class FilesAPIUploadAction extends UploadAction {
private static final long serialVersionUID = 1;

@Override
protected FileItemFactory getFileItemFactory( int requestSize ) {
return new FilesAPIFileItemFactory();
}

}
==================================================================
Special servlets handling individual uploads should be derived from
FilesAPIUploadAction.
Note that all this is compatible with GAE version 1.4.3 and probably
with future versions (1.4.3 is the current one).

Kayode Odeyemi

unread,
Apr 10, 2011, 12:16:51 PM4/10/11
to gwtu...@googlegroups.com
Great! Testing. Manual, I'll be looking forward to this in the next release.

Thanks Vyacheslav

Manuel Carrasco Moñino

unread,
Apr 11, 2011, 12:16:35 PM4/11/11
to gwtu...@googlegroups.com, Kayode Odeyemi
The code has been committed:
http://code.google.com/p/gwtupload/source/detail?r=884#

Can you download last snapshot and test it.

Thank you for contributing.
- Manolo

> --
> --
> http://groups.google.com/group/gwtupload
>

Vyacheslav Sokolov

unread,
Apr 12, 2011, 12:05:44 PM4/12/11
to gwtupload
On 11 апр, 23:16, Manuel Carrasco Moñino <man...@apache.org> wrote:
> The code has been committed:http://code.google.com/p/gwtupload/source/detail?r=884#
I doubt in the following fragment:
======================================================================
public FileItem createItem(String fieldName, String contentType,
boolean isFormField, String fileName) {
try {
/*----->*/ return new FilesAPIFileItem(fieldName, contentType,
isFormField, fileName);
} catch (IOException x) {
x.printStackTrace();
return null;
}
======================================================================
This way we create a blobstore file for every field in the submitted
form, even if it's a small text field or even something like a
checkbox, not only an uploaded file. It's theoretically possible to
add such fields to GWTUpload client-side widgets, i remember i saw
such examples in the documentation. Maybe it would be better to create
a MemoryFileItem which stores data in a byte array instead of
FilesAPIFileItem for such fields (isFormField == true), wouldn't it?

Manuel Carrasco Moñino

unread,
Apr 12, 2011, 12:52:40 PM4/12/11
to gwtu...@googlegroups.com, Vyacheslav Sokolov
Not really, only it will created if getOutputStream() is called which
only occurs with file fields

private AppEngineFile file = null;

public OutputStream getOutputStream() throws IOException {


if (file == null)
return null;
return new FilesAPIOutputStream(fileService, file);
}

- Manolo

> --
> --
> http://groups.google.com/group/gwtupload
>

Reply all
Reply to author
Forward
0 new messages