/*
* Copyright 2010 Manuel Carrasco Moñino. (manolo at apache/org)
* http://code.google.com/p/gwtupload
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package gwtupload.server;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUploadBase;
import org.apache.commons.fileupload.FileUploadBase.SizeLimitExceededException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.IOUtils;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.ResourceBundle;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import static gwtupload.shared.UConsts.MULTI_SUFFIX;
import static gwtupload.shared.UConsts.PARAM_DELAY;
import static gwtupload.shared.UConsts.PARAM_MAX_FILE_SIZE;
import static gwtupload.shared.UConsts.TAG_BLOBSTORE;
import static gwtupload.shared.UConsts.TAG_BLOBSTORE_PATH;
import static gwtupload.shared.UConsts.TAG_CANCELED;
import static gwtupload.shared.UConsts.TAG_CTYPE;
import static gwtupload.shared.UConsts.TAG_CURRENT_BYTES;
import static gwtupload.shared.UConsts.TAG_DELETED;
import static gwtupload.shared.UConsts.TAG_ERROR;
import static gwtupload.shared.UConsts.TAG_FIELD;
import static gwtupload.shared.UConsts.TAG_FILE;
import static gwtupload.shared.UConsts.TAG_FILES;
import static gwtupload.shared.UConsts.TAG_FINISHED;
import static gwtupload.shared.UConsts.TAG_KEY;
import static gwtupload.shared.UConsts.TAG_MSG_END;
import static gwtupload.shared.UConsts.TAG_MSG_GT;
import static gwtupload.shared.UConsts.TAG_MSG_LT;
import static gwtupload.shared.UConsts.TAG_MSG_START;
import static gwtupload.shared.UConsts.TAG_NAME;
import static gwtupload.shared.UConsts.TAG_PARAM;
import static gwtupload.shared.UConsts.TAG_PARAMS;
import static gwtupload.shared.UConsts.TAG_PERCENT;
import static gwtupload.shared.UConsts.TAG_SESSION_ID;
import static gwtupload.shared.UConsts.TAG_SIZE;
import static gwtupload.shared.UConsts.TAG_TOTAL_BYTES;
import static gwtupload.shared.UConsts.TAG_VALUE;
import gwtupload.server.exceptions.UploadActionException;
import gwtupload.server.exceptions.UploadCanceledException;
import gwtupload.server.exceptions.UploadException;
import gwtupload.server.exceptions.UploadSizeLimitException;
import gwtupload.server.exceptions.UploadTimeoutException;
import gwtupload.shared.UConsts;
/**
* <p>
* Upload servlet for the GwtUpload library.
* </p>
*
* <ul>
* <li>For customizable application actions, it's better to extend the UloadAction
* class instead of this.</li>
*
* <li>
* This servlet supports to be deployed in google application engine. It is able to
* detect this environment and in this case it does:
* <ul>
* <li>Set the request size to 512 KB which is the maximal size allowed</li>
* <li>Store received data in memory and cache instead of file system</li>
* <li>Uses memcache for session tracking instead of normal session objects,
* because objects stored in session seem not to be available until the thread finishes</li>
* </ul>
* </li>
* </ul>
*
*
* <p>
* <b>Example of web.xml</b>
* </p>
*
* <pre>
* <context-param>
* <!-- max size of the upload request -->
* <param-name>maxSize</param-name>
* <param-value>3145728</param-value>
* </context-param>
*
* <context-param>
* <!-- useful in development mode to see the upload progress bar in fast networks. (sleep time in milliseconds) -->
* <param-name>slowUploads</param-name>
* <param-value>200</param-value>
* </context-param>
*
* <context-param>
* <!-- max file size of the upload request -->
* <param-name>maxFileSize</param-name>
* <param-value>3145728</param-value>
* </context-param>
*
* <servlet>
* <servlet-name>uploadServlet</servlet-name>
* <servlet-class>gwtupload.server.UploadServlet</servlet-class>
* </servlet>
*
* <servlet-mapping>
* <servlet-name>uploadServlet</servlet-name>
* <url-pattern>*.gupld</url-pattern>
* </servlet-mapping>
*
*
* </pre>
*
* @author Manolo Carrasco Moñino
*
*/
public class UploadServlet extends HttpServlet implements Servlet {
private static final String SESSION_FILES = "FILES";
private static final String SESSION_LAST_FILES = "LAST_FILES";
protected static final int DEFAULT_REQUEST_LIMIT_KB = 5 * 1024 * 1024;
protected static final int DEFAULT_SLOW_DELAY_MILLIS = 300;
protected static final String XML_CANCELED_TRUE = "<" + TAG_CANCELED + ">true</" + TAG_CANCELED + ">";
protected static final String XML_DELETED_TRUE = "<" + TAG_DELETED + ">true</" + TAG_DELETED + ">";
protected static final String XML_ERROR_ITEM_NOT_FOUND = "<" + TAG_ERROR + ">item not found</" + TAG_ERROR + ">";
protected static final String XML_ERROR_TIMEOUT = "<" + TAG_ERROR + ">timeout receiving file</" + TAG_ERROR + ">";
protected static final String XML_FINISHED_OK = "<" + TAG_FINISHED + ">OK</" + TAG_FINISHED + ">";
protected static UploadLogger logger = UploadLogger.getLogger(UploadServlet.class);
protected static final ThreadLocal<HttpServletRequest> perThreadRequest = new ThreadLocal<HttpServletRequest>();
private static boolean appEngine = false;
private static final long serialVersionUID = 2740693677625051632L;
private static String XML_TPL = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<response>%%MESSAGE%%</response>\n";
private String corsDomainsRegex = "^$";
/**
* Copy the content of an input stream to an output one.
*
* @param in
* @param out
* @throws IOException
*/
public static void copyFromInputStreamToOutputStream(InputStream in, OutputStream out) throws IOException {
IOUtils.copy(in, out);
}
/**
* Utility method to get a fileItem of type file from a vector using either
* the file name or the attribute name.
*
* @param sessionFiles
* @param parameter
* @return fileItem of the file found or null
*/
public static FileItem findFileItem(List<FileItem> sessionFiles, String parameter) {
if (sessionFiles == null || parameter == null) {
return null;
}
FileItem item = findItemByFieldName(sessionFiles, parameter);
if (item == null) {
item = findItemByFileName(sessionFiles, parameter);
}
if (item != null && !item.isFormField()) {
return item;
}
return null;
}
/**
* Utility method to get a fileItem from a vector using the attribute name.
*
* @param sessionFiles
* @param attrName
* @return fileItem found or null
*/
public static FileItem findItemByFieldName(List<FileItem> sessionFiles, String attrName) {
if (sessionFiles != null) {
for (FileItem fileItem : sessionFiles) {
if (fileItem.getFieldName().equalsIgnoreCase(attrName)) {
return fileItem;
}
}
}
return null;
}
/**
* Utility method to get a fileItem from a vector using the file name It
* only returns items of type file.
*
* @param sessionFiles
* @param fileName
* @return fileItem of the file found or null
*/
public static FileItem findItemByFileName(List<FileItem> sessionFiles, String fileName) {
if (sessionFiles != null) {
for (FileItem fileItem : sessionFiles) {
if (fileItem.isFormField() == false && fileItem.getName().equalsIgnoreCase(fileName)) {
return fileItem;
}
}
}
return null;
}
/**
* Return the list of FileItems stored in session under the provided session key.
*/
@SuppressWarnings("unchecked")
public static List<FileItem> getSessionFileItems(HttpServletRequest request, String sessionFilesKey) {
return (List<FileItem>) request.getSession().getAttribute(sessionFilesKey);
}
/**
* Return the list of FileItems stored in session under the default name.
*/
public static List<FileItem> getSessionFileItems(HttpServletRequest request) {
return getSessionFileItems(request, SESSION_FILES);
}
/**
* Return the list of FileItems stored in session under the session key.
*/
// FIXME(manolo): Not sure about the convenience of this and sessionFilesKey.
public List<FileItem> getMySessionFileItems(HttpServletRequest request) {
return getSessionFileItems(request, getSessionFilesKey(request));
}
/**
* Return the most recent list of FileItems received
*/
@SuppressWarnings("unchecked")
public static List<FileItem> getLastReceivedFileItems(HttpServletRequest request, String sessionLastFilesKey) {
return (List<FileItem>) request.getSession().getAttribute(sessionLastFilesKey);
}
/**
* Return the most recent list of FileItems received under the default key
*/
public static List<FileItem> getLastReceivedFileItems(HttpServletRequest request) {
return getLastReceivedFileItems(request, SESSION_LAST_FILES);
}
/**
* Return the most recent list of FileItems received under the session key
*/
public List<FileItem> getMyLastReceivedFileItems(HttpServletRequest request) {
return getLastReceivedFileItems(request, getSessionLastFilesKey(request));
}
/**
* @deprecated use getSessionFileItems
*/
public static List<FileItem> getSessionItems(HttpServletRequest request) {
return getSessionFileItems(request);
}
/**
* Returns the localized text of a key.
*/
public static String getMessage(String key, Object... pars) {
Locale loc =
getThreadLocalRequest() == null || getThreadLocalRequest().getLocale() == null
? new Locale("en")
: getThreadLocalRequest().getLocale();
ResourceBundle res =
ResourceBundle.getBundle(UploadServlet.class.getName(), loc);
String msg = res.getString(key);
return new MessageFormat(msg, loc).format(pars);
}
public static final HttpServletRequest getThreadLocalRequest() {
return perThreadRequest.get();
}
/**
* Just a method to detect whether the web container is running with appengine
* restrictions.
*
* @return true if the case of the application is running in appengine
*/
public boolean isAppEngine() {
return appEngine;
}
/**
* Removes all FileItems stored in session under the session key and the temporary data.
*
* @param request
*/
public static void removeSessionFileItems(HttpServletRequest request, String sessionFilesKey) {
removeSessionFileItems(request, sessionFilesKey, true);
}
/**
* Removes all FileItems stored in session under the default key and the temporary data.
*/
public static void removeSessionFileItems(HttpServletRequest request) {
removeSessionFileItems(request, SESSION_FILES, true);
removeSessionFileItems(request, SESSION_LAST_FILES, true);
}
/**
* Removes all FileItems stored in session under the session key, but in this case
* the user can specify whether the temporary data is removed from disk.
*
* @param request
* @param removeData
* true: the file data is deleted.
* false: use it when you are referencing file items
* instead of copying them.
*/
public static void removeSessionFileItems(HttpServletRequest request, String sessionFilesKey, boolean removeData) {
logger.debug("UPLOAD-SERVLET (" + request.getSession().getId() + ") removeSessionFileItems: removeData=" + removeData);
List<FileItem> sessionFiles = getSessionFileItems(request, sessionFilesKey);
if (removeData && sessionFiles != null) {
for (FileItem fileItem : sessionFiles) {
if (fileItem != null && !fileItem.isFormField()) {
fileItem.delete();
}
}
}
request.getSession().removeAttribute(sessionFilesKey);
}
/**
* Removes all FileItems stored in session under the default key, but in this case
* the user can specify whether the temporary data is removed from disk.
*/
public static void removeSessionFileItems(HttpServletRequest request, boolean removeData) {
removeSessionFileItems(request, SESSION_FILES, removeData);
removeSessionFileItems(request, SESSION_LAST_FILES, removeData);
}
/**
* Delete an uploaded file.
*
* @param request
* @param response
* @return FileItem
* @throws IOException
*/
protected static FileItem removeUploadedFile(HttpServletRequest request, HttpServletResponse response) throws IOException {
String parameter = request.getParameter(UConsts.PARAM_REMOVE);
FileItem item = findFileItem(getSessionFileItems(request), parameter);
if (item != null) {
getSessionFileItems(request).remove(item);
logger.debug("UPLOAD-SERVLET (" + request.getSession().getId() + ") removeUploadedFile: " + parameter + " " + item.getName() + " " + item.getSize());
} else {
logger.info("UPLOAD-SERVLET (" + request.getSession().getId() + ") removeUploadedFile: " + parameter + " not in session.");
}
renderXmlResponse(request, response, XML_DELETED_TRUE);
return item;
}
/**
* Writes a response to the client.
*/
protected static void renderMessage(HttpServletResponse response, String message, String contentType) throws IOException {
response.addHeader("Cache-Control", "no-cache");
response.setContentType(contentType + "; charset=UTF-8");
response.setCharacterEncoding("UTF-8");
PrintWriter out = response.getWriter();
out.print(message);
out.flush();
out.close();
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
if (checkCORS(request, response) && request.getMethod().equals("OPTIONS")) {
String method = request.getHeader("Access-Control-Request-Method");
if (method != null) {
response.addHeader("Access-Control-Allow-Methods", method);
response.setHeader("Allow", "GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS");
}
String headers = request.getHeader("Access-Control-Request-Headers");
if (headers != null) {
response.addHeader("Access-Control-Allow-Headers", headers);
}
response.setContentType("text/plain");
}
super.service(request, response);
}
private boolean checkCORS(HttpServletRequest request, HttpServletResponse response) {
String origin = request.getHeader("Origin");
if (origin != null && origin.matches(corsDomainsRegex)) {
// Maybe the user has used this domain before and has a session-cookie, we delete it
// Cookie c = new Cookie("JSESSIONID", "");
// c.setMaxAge(0);
// response.addCookie(c);
// All doXX methods should set these header
response.addHeader("Access-Control-Allow-Origin", origin);
response.addHeader("Access-Control-Allow-Credentials", "true");
return true;
} else if (origin != null) {
logger.error("checkCORS error Origin: " + origin + " does not match:" + corsDomainsRegex);
}
return false;
}
/**
* Writes an XML response to the client.
*/
protected void renderHtmlMessage(HttpServletResponse response, String message) throws IOException {
renderMessage(response, message, "text/html");
}
/**
* Writes a XML response to the client.
* The message must be a text which will be wrapped in an XML structure.
*
* Note: if the request is a POST, the response should set the content type
* to text/html or text/plain in order to be able in the client side to
* read the iframe body (submitCompletEvent.getResults()), otherwise the
* method returns null
*
* @param request
* @param response
* @param message
* @param post
* specify whether the request is post or not.
* @throws IOException
*/
protected static void renderXmlResponse(HttpServletRequest request, HttpServletResponse response, String message, boolean post) throws IOException {
String contentType = post ? "text/plain" : "text/html";
String xml = XML_TPL.replace("%%MESSAGE%%", message != null ? message : "");
if (post) {
xml = TAG_MSG_START + xml.replaceAll("<", TAG_MSG_LT).replaceAll(">", TAG_MSG_GT) + TAG_MSG_END;
}
renderMessage(response, xml, contentType);
}
protected static void renderXmlResponse(HttpServletRequest request, HttpServletResponse response, String message) throws IOException {
renderXmlResponse(request, response, message, false);
}
protected static void setThreadLocalRequest(HttpServletRequest request) {
perThreadRequest.set(request);
}
/**
* Simple method to get a string from the exception stack.
*
* @param e
* @return string
*/
protected static String stackTraceToString(Throwable e) {
StringWriter writer = new StringWriter();
e.printStackTrace(new PrintWriter(writer));
return writer.getBuffer().toString();
}
protected long maxSize = DEFAULT_REQUEST_LIMIT_KB;
protected long maxFileSize = DEFAULT_REQUEST_LIMIT_KB;
protected int uploadDelay = 0;
protected boolean useBlobstore = false;
/**
* Mark the current upload process to be canceled.
*
* @param request
*/
public void cancelUpload(HttpServletRequest request) {
logger.debug("UPLOAD-SERVLET (" + request.getSession().getId() + ") cancelling Upload");
AbstractUploadListener listener = getCurrentListener(request);
if (listener != null && !listener.isCanceled()) {
listener.setException(new UploadCanceledException());
}
}
/**
* Override this method if you want to check the request before it is passed
* to commons-fileupload parser.
*
* @param request
* @throws RuntimeException
*/
public void checkRequest(HttpServletRequest request) {
logger.debug("UPLOAD-SERVLET (" + request.getSession().getId() + ") procesing a request with size: " + getContentLength(request) + " bytes.");
if (getContentLength(request) > maxSize) {
throw new UploadSizeLimitException(maxSize, getContentLength(request));
}
}
/**
* Get an uploaded file item.
*
* @param request
* @param response
* @throws IOException
*/
public void getUploadedFile(HttpServletRequest request, HttpServletResponse response) throws IOException {
String parameter = request.getParameter(UConsts.PARAM_SHOW);
FileItem item = findFileItem(getMySessionFileItems(request), parameter);
if (item != null) {
logger.error("UPLOAD-SERVLET (" + request.getSession().getId() + ") getUploadedFile: " + parameter + " returning: " + item.getContentType() + ", " + item.getName() + ", " + item.getSize()
+ " bytes");
response.setContentType(item.getContentType());
copyFromInputStreamToOutputStream(item.getInputStream(), response.getOutputStream());
} else {
logger.error("UPLOAD-SERVLET (" + request.getSession().getId() + ") getUploadedFile: " + parameter + " file isn't in session.");
renderXmlResponse(request, response, XML_ERROR_ITEM_NOT_FOUND);
}
}
@Override
public String getInitParameter(String name) {
String value = getServletContext().getInitParameter(name);
if (value == null) {
value = super.getInitParameter(name);
}
return value;
}
/**
* Read configurable parameters during the servlet initialization.
*/
public void init(ServletConfig config) throws ServletException {
super.init(config);
String size = getInitParameter("maxSize");
if (size != null) {
try {
maxSize = Long.parseLong(size);
} catch (NumberFormatException e) {
}
}
String fileSize = getInitParameter("maxFileSize");
if (null != fileSize){
try {
maxFileSize = Long.parseLong(fileSize);
} catch (NumberFormatException e){
}
}
String slow = getInitParameter("slowUploads");
if (slow != null) {
if ("true".equalsIgnoreCase(slow)) {
uploadDelay = DEFAULT_SLOW_DELAY_MILLIS;
} else {
try {
uploadDelay = Integer.valueOf(slow);
} catch (NumberFormatException e) {
}
}
}
String timeout = getInitParameter("noDataTimeout");
if (timeout != null){
try {
UploadListener.setNoDataTimeout(Integer.parseInt(timeout));
} catch (NumberFormatException e) {
}
}
String appe = getInitParameter("appEngine");
if (appe != null) {
appEngine = "true".equalsIgnoreCase(appe);
} else {
appEngine = isAppEngine();
}
String cors = getInitParameter("corsDomainsRegex");
if (cors != null) {
corsDomainsRegex = cors;
}
logger.info("UPLOAD-SERVLET init: maxSize=" + maxSize + ", slowUploads=" + slow + ", isAppEngine=" + isAppEngine() + ", corsRegex=" + corsDomainsRegex);
}
/**
* Create a new listener for this session.
*
* @param request
* @return the appropriate listener
*/
protected AbstractUploadListener createNewListener(HttpServletRequest request) {
int delay = request.getParameter("nodelay") != null ? 0 : uploadDelay;
if (isAppEngine()) {
return new MemoryUploadListener(delay, getContentLength(request));
} else {
return new UploadListener(delay, getContentLength(request));
}
}
private long getContentLength(HttpServletRequest request) {
long size = -1;
try {
size = Long.parseLong(request.getHeader(FileUploadBase.CONTENT_LENGTH));
} catch (NumberFormatException e) {
}
return size;
}
/**
* The get method is used to monitor the uploading process or to get the
* content of the uploaded files.
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
perThreadRequest.set(request);
try {
AbstractUploadListener listener = getCurrentListener(request);
if (request.getParameter(UConsts.PARAM_SESSION) != null) {
logger.debug("UPLOAD-SERVLET (" + request.getSession().getId() + ") new session, blobstore=" + (isAppEngine() && useBlobstore));
String sessionId = request.getSession().getId();
renderXmlResponse(request, response,
"<" + TAG_BLOBSTORE + ">" + (isAppEngine() && useBlobstore) + "</" + TAG_BLOBSTORE + ">" +
"<" + TAG_SESSION_ID + ">" + sessionId + "</" + TAG_SESSION_ID + ">");
} else if (isAppEngine() && (request.getParameter(UConsts.PARAM_BLOBSTORE) != null || request.getParameterMap().size() == 0)) {
String blobStorePath = getBlobstorePath(request);
logger.debug("UPLOAD-SERVLET (" + request.getSession().getId() + ") getBlobstorePath=" + blobStorePath);
renderXmlResponse(request, response, "<" + TAG_BLOBSTORE_PATH + ">" + blobStorePath + "</" + TAG_BLOBSTORE_PATH + ">");
} else if (request.getParameter(UConsts.PARAM_SHOW) != null) {
getUploadedFile(request, response);
} else if (request.getParameter(UConsts.PARAM_CANCEL) != null) {
cancelUpload(request);
renderXmlResponse(request, response, XML_CANCELED_TRUE);
} else if (request.getParameter(UConsts.PARAM_REMOVE) != null) {
removeUploadedFile(request, response);
} else if (request.getParameter(UConsts.PARAM_CLEAN) != null) {
logger.debug("UPLOAD-SERVLET (" + request.getSession().getId() + ") cleanListener");
if (listener != null) {
listener.remove();
}
renderXmlResponse(request, response, XML_FINISHED_OK);
} else if (listener != null && listener.isFinished()) {
removeCurrentListener(request);
renderXmlResponse(request, response, listener.getPostResponse());
} else {
String message = statusToString(getUploadStatus(request, request.getParameter(UConsts.PARAM_FILENAME), null));
renderXmlResponse(request, response, message);
}
} finally {
perThreadRequest.set(null);
}
}
protected String statusToString(Map<String, String> stat) {
String message = "";
for (Entry<String, String> e : stat.entrySet()) {
if (e.getValue() != null) {
String k = e.getKey();
String v = e.getValue().replaceAll("</*pre>", "").replaceAll("<", "<").replaceAll(">", ">");
message += "<" + k + ">" + v + "</" + k + ">\n";
}
}
return message;
}
/**
* The post method is used to receive the file and save it in the user
* session. It returns a very XML page that the client receives in an
* iframe.
*
* The content of this xml document has a tag error in the case of error in
* the upload process or the string OK in the case of success.
*
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
perThreadRequest.set(request);
String error;
try {
error = parsePostRequest(request, response);
Map<String, String> stat = new HashMap<String, String>();
if (error != null && error.length() > 0 ) {
stat.put(TAG_ERROR, error);
} else {
getFileItemsSummary(request, stat);
}
String postResponse = statusToString(stat);
finish(request, postResponse);
renderXmlResponse(request, response, postResponse, true);
} catch (UploadCanceledException e) {
renderXmlResponse(request, response, XML_CANCELED_TRUE, true);
} catch (UploadTimeoutException e) {
renderXmlResponse(request, response, XML_ERROR_TIMEOUT, true);
} catch (UploadSizeLimitException e) {
renderXmlResponse(request, response, "<" + TAG_ERROR + ">" + e.getMessage() + "</" + TAG_ERROR + ">", true);
} catch (Exception e) {
logger.error("UPLOAD-SERVLET (" + request.getSession().getId() + ") Exception -> " + e.getMessage() + "\n" + stackTraceToString(e));
error = e.getMessage();
renderXmlResponse(request, response, "<" + TAG_ERROR + ">" + error + "</" + TAG_ERROR + ">", true);
} finally {
perThreadRequest.set(null);
}
}
protected Map<String, String> getFileItemsSummary(HttpServletRequest request, Map<String, String> stat) {
if (stat == null) {
stat = new HashMap<String, String>();
}
List<FileItem> s = getMyLastReceivedFileItems(request);
if (s != null) {
String files = "";
String params = "";
for (FileItem i : s) {
if (i.isFormField()) {
params += formFieldToXml(i);
} else {
files += fileFieldToXml(i);
}
}
stat.put(TAG_FILES, files);
stat.put(TAG_PARAMS, params);
stat.put(TAG_FINISHED, "ok");
}
return stat;
}
private String formFieldToXml(FileItem i) {
Map<String, String> item = new HashMap<String, String>();
item.put(TAG_VALUE, "" + i.getString());
item.put(TAG_FIELD, "" + i.getFieldName());
Map<String, String> param = new HashMap<String, String>();
param.put(TAG_PARAM, statusToString(item));
return statusToString(param);
}
private String fileFieldToXml(FileItem i) {
Map<String, String> item = new HashMap<String, String>();
item.put(TAG_CTYPE, i.getContentType() !=null ? i.getContentType() : "unknown");
item.put(TAG_SIZE, "" + i.getSize());
item.put(TAG_NAME, "" + i.getName());
item.put(TAG_FIELD, "" + i.getFieldName());
if (i instanceof HasKey) {
String k = ((HasKey)i).getKeyString();
item.put(TAG_KEY, k);
}
Map<String, String> file = new HashMap<String, String>();
file.put(TAG_FILE, statusToString(item));
return statusToString(file);
}
/**
* Notify to the listener that the upload has finished.
*
* @param request
* @param postResponse
*/
protected void finish(HttpServletRequest request, String postResponse) {
AbstractUploadListener listener = getCurrentListener(request);
if (listener != null) {
listener.setFinished(postResponse);
}
}
protected String getBlobstorePath(HttpServletRequest request) {
return null;
}
/**
* Get the listener active in this session.
*
* @param request
* @return the listener active
*/
protected AbstractUploadListener getCurrentListener(HttpServletRequest request) {
if (isAppEngine()) {
return MemoryUploadListener.current(request.getSession().getId());
} else {
return UploadListener.current(request);
}
}
/**
* Override this method if you want to implement a different ItemFactory.
*
* @return FileItemFactory
*/
protected FileItemFactory getFileItemFactory(long requestSize) {
return new DefaultFileItemFactory();
}
/**
* Method executed each time the client asks the server for the progress status.
* It uses the listener to generate the adequate response
*
* @param request
* @param fieldname
* @return a map of tag/values to be rendered
*/
protected Map<String, String> getUploadStatus(HttpServletRequest request, String fieldname, Map<String, String> ret) {
perThreadRequest.set(request);
HttpSession session = request.getSession();
if (ret == null) {
ret = new HashMap<String, String>();
}
long currentBytes = 0;
long totalBytes = 0;
long percent = 0;
AbstractUploadListener listener = getCurrentListener(request);
if (listener != null) {
if (listener.isFinished()) {
} else if (listener.getException() != null) {
if (listener.getException() instanceof UploadCanceledException) {
ret.put(TAG_CANCELED, "true");
ret.put(TAG_FINISHED, TAG_CANCELED);
logger.error("UPLOAD-SERVLET (" + session.getId() + ") getUploadStatus: " + fieldname + " canceled by the user after " + listener.getBytesRead() + " Bytes");
} else {
String errorMsg = getMessage("server_error", listener.getException().getMessage());
ret.put(TAG_ERROR, errorMsg);
ret.put(TAG_FINISHED, TAG_ERROR);
logger.error("UPLOAD-SERVLET (" + session.getId() + ") getUploadStatus: " + fieldname + " finished with error: " + listener.getException().getMessage());
}
} else {
currentBytes = listener.getBytesRead();
totalBytes = listener.getContentLength();
percent = totalBytes != 0 ? currentBytes * 100 / totalBytes : 0;
// logger.debug("UPLOAD-SERVLET (" + session.getId() + ") getUploadStatus: " + fieldname + " " + currentBytes + "/" + totalBytes + " " + percent + "%");
ret.put(TAG_PERCENT, "" + percent);
ret.put(TAG_CURRENT_BYTES, "" + currentBytes);
ret.put(TAG_TOTAL_BYTES, "" + totalBytes);
}
} else if (getMySessionFileItems(request) != null) {
if (fieldname == null) {
ret.put(TAG_FINISHED, "ok");
logger.debug("UPLOAD-SERVLET (" + session.getId() + ") getUploadStatus: " + request.getQueryString() +
" finished with files: " + session.getAttribute(getSessionFilesKey(request)));
} else {
List<FileItem> sessionFiles = getMySessionFileItems(request);
for (FileItem file : sessionFiles) {
if (file.isFormField() == false && file.getFieldName().equals(fieldname)) {
ret.put(TAG_FINISHED, "ok");
ret.put(UConsts.PARAM_FILENAME, fieldname);
logger.debug("UPLOAD-SERVLET (" + session.getId() + ") getUploadStatus: " + fieldname +
" finished with files: " + session.getAttribute(getSessionFilesKey(request)));
}
}
}
} else {
logger.debug("UPLOAD-SERVLET (" + session.getId() + ") getUploadStatus: no listener in session");
ret.put("wait", "listener is null");
}
if (ret.containsKey(TAG_FINISHED)) {
removeCurrentListener(request);
}
perThreadRequest.set(null);
return ret;
}
/**
* This method parses the submit action, puts in session a listener where the
* progress status is updated, and eventually stores the received data in
* the user session.
*
* returns null in the case of success or a string with the error
*
*/
protected String parsePostRequest(HttpServletRequest request, HttpServletResponse response) {
try {
String delay = request.getParameter(PARAM_DELAY);
String maxFilesize = request.getParameter(PARAM_MAX_FILE_SIZE);
maxSize = maxFilesize != null && maxFilesize.matches("[0-9]*") ? Long.parseLong(maxFilesize) : maxSize;
uploadDelay = Integer.parseInt(delay);
} catch (Exception e) { }
HttpSession session = request.getSession();
logger.debug("UPLOAD-SERVLET (" + session.getId() + ") new upload request received.");
AbstractUploadListener listener = getCurrentListener(request);
if (listener != null) {
if (listener.isFrozen() || listener.isCanceled() || listener.getPercent() >= 100) {
removeCurrentListener(request);
} else {
String error = getMessage("busy");
logger.error("UPLOAD-SERVLET (" + session.getId() + ") " + error);
return error;
}
}
// Create a file upload progress listener, and put it in the user session,
// so the browser can use ajax to query status of the upload process
listener = createNewListener(request);
List<FileItem> uploadedItems;
try {
// Call to a method which the user can override
checkRequest(request);
// Create the factory used for uploading files,
FileItemFactory factory = getFileItemFactory(getContentLength(request));
ServletFileUpload uploader = new ServletFileUpload(factory);
uploader.setSizeMax(maxSize);
uploader.setFileSizeMax(maxFileSize);
uploader.setProgressListener(listener);
// Receive the files
logger.error("UPLOAD-SERVLET (" + session.getId() + ") parsing HTTP POST request ");
uploadedItems = uploader.parseRequest(request);
session.removeAttribute(getSessionLastFilesKey(request));
logger.error("UPLOAD-SERVLET (" + session.getId() + ") parsed request, " + uploadedItems.size() + " items received.");
// Received files are put in session
List<FileItem> sessionFiles = getMySessionFileItems(request);
if (sessionFiles == null) {
sessionFiles = new ArrayList<FileItem>();
}
String error = "";
if (uploadedItems.size() > 0) {
sessionFiles.addAll(uploadedItems);
String msg = "";
for (FileItem i : sessionFiles) {
msg += i.getFieldName() + " => " + i.getName() + "(" + i.getSize() + " bytes),";
}
logger.debug("UPLOAD-SERVLET (" + session.getId() + ") puting items in session: " + msg);
session.setAttribute(getSessionFilesKey(request), sessionFiles);
session.setAttribute(getSessionLastFilesKey(request), uploadedItems);
} else if (!isAppEngine()){
logger.error("UPLOAD-SERVLET (" + session.getId() + ") error NO DATA received ");
error += getMessage("no_data");
}
return error.length() > 0 ? error : null;
// So much silly questions in the list about this issue.
} catch(LinkageError e) {
logger.error("UPLOAD-SERVLET (" + request.getSession().getId() + ") Exception: " + e.getMessage() + "\n" + stackTraceToString(e));
RuntimeException ex = new UploadActionException(getMessage("restricted", e.getMessage()), e);
listener.setException(ex);
throw ex;
} catch (SizeLimitExceededException e) {
RuntimeException ex = new UploadSizeLimitException(e.getPermittedSize(), e.getActualSize());
listener.setException(ex);
throw ex;
} catch (UploadSizeLimitException e) {
listener.setException(e);
throw e;
} catch (UploadCanceledException e) {
listener.setException(e);
throw e;
} catch (UploadTimeoutException e) {
listener.setException(e);
throw e;
} catch (Throwable e) {
logger.error("UPLOAD-SERVLET (" + request.getSession().getId() + ") Unexpected Exception -> " + e.getMessage() + "\n" + stackTraceToString(e));
e.printStackTrace();
RuntimeException ex = new UploadException(e);
listener.setException(ex);
throw ex;
}
}
/**
* Remove the listener active in this session.
*
* @param request
*/
protected void removeCurrentListener(HttpServletRequest request) {
AbstractUploadListener listener = getCurrentListener(request);
if (listener != null) {
listener.remove();
}
}
/**
* Override this to provide a session key which allow to differentiate between
* multiple instances of uploaders in an application with the same session but
* who do not wish to share the uploaded files.
* Example:
* protected String getSessionFilesKey(HttpServletRequest request) {
* return getSessionFilesKey(request.getParameter("randomNumber"));
* }
*
* public static String getSessionFilesKey(String parameter) {
* return "SESSION_FILES_" + parameter;
* }
*
*/
protected String getSessionFilesKey(HttpServletRequest request) {
return SESSION_FILES;
}
/**
* Override this to provide a session key which allow to differentiate between
* multiple instances of uploaders in an application with the same session but
* who do not wish to share the uploaded files.
* See getSessionFilesKey() for an example.
*/
protected String getSessionLastFilesKey(HttpServletRequest request) {
return SESSION_LAST_FILES;
}
/**
* DiskFileItemFactory for Multiple file selection.
*/
public static class DefaultFileItemFactory extends DiskFileItemFactory {
private HashMap<String, Integer> map = new HashMap<String, Integer>();
@Override
public FileItem createItem(String fieldName, String contentType, boolean isFormField, String fileName) {
Integer cont = map.get(fieldName) != null ? (map.get(fieldName) + 1): 0;
map.put(fieldName, cont);
fieldName = fieldName.replace(MULTI_SUFFIX, "") + "-" + cont;
return super.createItem(fieldName, contentType, isFormField, fileName);
}
}
}