Primefaces 3.0 fileUpload component on GAE
I was recently working on a project where I used JSF 2.0 and Primefaces 3.0 for an application deployed to Google App Engine (GAE). The particular problem was dealing with the fileUpload component. In the documentation for Primefaces, it describes the need to configure org.primefaces.webapp.filter.FileUploadFilter
so that the multipart request will be handled correctly. Normally, JSF ignores them. The problem with FileUploadFilter
is that it tries to write a temporary file which is not allowed on GAE. I got around this limit by creating my own filter based on FileUploadFilter
.
My initial solution was to save the temporary files as blobs,
but keeping the files in memory was much simpler. I used FileUploadFilter
as a reference and built an in-memory only org.apache.commons.fileupload.FileItem
implementation.
Warning: the whole file will be in memory, so this could cause resource issues. For my particular use, I only was dealing with small files (<1MB) being uploaded infrequently.
####ii.green.phillip.primefaces.GaeFileUploadFilter
package ii.green.phillip.primefaces;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.primefaces.webapp.MultipartRequest;
import org.primefaces.webapp.filter.FileUploadFilter;
/**
* Works like FileUploadFilter, but store files not as files because of restrictions from Google App Engine.
*
* @see FileUploadFilter
* @author pdgreen
*/
public class GaeFileUploadFilter implements Filter {
private final static Logger logger = Logger.getLogger(GaeFileUploadFilter.class.getName());
public void init(FilterConfig filterConfig) throws ServletException {
if (logger.isLoggable(Level.FINE)) {
logger.fine("FileUploadFilter initiated successfully");
}
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
boolean isMultipart = ServletFileUpload.isMultipartContent(httpServletRequest);
if (isMultipart) {
if (logger.isLoggable(Level.FINE)) {
logger.fine("Parsing file upload request");
}
final FileItemFactory fileItemFactory = new InMemoryFileItemFactory();
ServletFileUpload servletFileUpload = new ServletFileUpload(fileItemFactory);
MultipartRequest multipartRequest = new MultipartRequest(httpServletRequest, servletFileUpload);
if (logger.isLoggable(Level.FINE)) {
logger.fine("File upload request parsed succesfully, continuing with filter chain with a wrapped multipart request");
}
try {
filterChain.doFilter(multipartRequest, response);
} catch (ServletException ex) {
logger.log(Level.SEVERE, "servlet exception occured", ex);
throw ex;
} catch (IOException ex) {
logger.log(Level.SEVERE, "io exception occured", ex);
throw ex;
}
} else {
filterChain.doFilter(request, response);
}
}
public void destroy() {
if (logger.isLoggable(Level.FINE)) {
logger.fine("Destroying FileUploadFilter");
}
}
}
####ii.green.phillip.primefaces.InMemoryFileItemFactory
package ii.green.phillip.primefaces;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
/**
* Creates {@link InMemoryFileItem}s.
* @author pdgreen
*/
public class InMemoryFileItemFactory implements FileItemFactory {
@Override
public FileItem createItem(String fieldName, String contentType, boolean isFormField, String fileName) {
return new InMemoryFileItem(fieldName, contentType, isFormField, fileName);
}
}
####ii.green.phillip.primefaces.InMemoryFileItem
package ii.green.phillip.primefaces;
import java.io.*;
import org.apache.commons.fileupload.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Only stores the file in memory.
*
* @author pdgreen
*/
public class InMemoryFileItem implements FileItem {
private final static Logger LOGGER = LoggerFactory.getLogger(InMemoryFileItem.class);
public static final String DEFAULT_CHARSET = "ISO-8859-1";
private String fieldName;
private String contentType;
private boolean isFormField;
private String fileName;
private byte[] content = new byte[0];
/**
* Constructs a new
* <code>GaeFileItem</code> instance.
*
* @param fieldName The name of the form field.
* @param contentType The content type passed by the browser or
* <code>null</code> if not specified.
* @param isFormField Whether or not this item is a plain form field, as opposed to a file upload.
* @param fileName The original filename in the user's filesystem, or
* <code>null</code> if not specified.
*/
public InMemoryFileItem(String fieldName,
String contentType, boolean isFormField, String fileName) {
this.fieldName = fieldName;
this.contentType = contentType;
this.isFormField = isFormField;
this.fileName = fileName;
}
@Override
public InputStream getInputStream()
throws IOException {
return new ByteArrayInputStream(content);
}
@Override
public String getContentType() {
return contentType;
}
@Override
public String getName() {
return fileName;
}
@Override
public boolean isInMemory() {
return true;
}
@Override
public long getSize() {
return content.length;
}
@Override
public byte[] get() {
return content;
}
@Override
public String getString(final String charset)
throws UnsupportedEncodingException {
return new String(get(), charset);
}
@Override
public String getString() {
try {
return new String(content, DEFAULT_CHARSET);
} catch (UnsupportedEncodingException e) {
return new String(content);
}
}
@Override
public void write(File file) throws Exception {
FileOutputStream fout = null;
try {
fout = new FileOutputStream(file);
fout.write(get());
} finally {
if (fout != null) {
fout.close();
}
}
}
@Override
public void delete() {
content = new byte[0];
}
@Override
public String getFieldName() {
return fieldName;
}
@Override
public void setFieldName(String fieldName) {
this.fieldName = fieldName;
}
@Override
public boolean isFormField() {
return isFormField;
}
@Override
public void setFormField(boolean state) {
isFormField = state;
}
@Override
public OutputStream getOutputStream()
throws IOException {
return new ByteArrayOutputStream() {
@Override
public synchronized void reset() {
super.reset();
content = toByteArray();
}
@Override
public void flush() throws IOException {
super.flush();
content = toByteArray();
}
@Override
public void close() throws IOException {
super.close();
content = toByteArray();
}
};
}
}
Conclusion
The above code worked great for my requirements.
If my requirements were to expand, I would try storing the temporary files as blobs.