diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/filestorage/uploadrequest/FileUploadServletRequest.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/filestorage/uploadrequest/FileUploadServletRequest.java
new file mode 100644
index 000000000..8dff4522b
--- /dev/null
+++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/filestorage/uploadrequest/FileUploadServletRequest.java
@@ -0,0 +1,332 @@
+/* $This file is distributed under the terms of the license in /doc/license.txt$ */
+
+package edu.cornell.mannlib.vitro.webapp.filestorage.uploadrequest;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.security.Principal;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletInputStream;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import org.apache.commons.fileupload.FileItem;
+import org.apache.commons.fileupload.servlet.ServletFileUpload;
+
+/**
+ *
+ * Wraps an HTTP request and parses it for file uploads, without losing the
+ * request parameters.
+ *
+ *
+ * Most methods are declared here, and simply delegate to the wrapped request.
+ * Methods that have to do with parameters or files are handled differently for
+ * simple requests and multipart request, and are implemented in the
+ * sub-classes.
+ *
+ */
+public abstract class FileUploadServletRequest implements HttpServletRequest {
+ // ----------------------------------------------------------------------
+ // The factory method
+ // ----------------------------------------------------------------------
+
+ /**
+ * Wrap this {@link HttpServletRequest} in an appropriate wrapper class.
+ */
+ public static FileUploadServletRequest parseRequest(
+ HttpServletRequest request) throws IOException {
+ boolean isMultipart = ServletFileUpload.isMultipartContent(request);
+ if (isMultipart) {
+ return new MultipartHttpServletRequest(request);
+ } else {
+ return new SimpleHttpServletRequestWrapper(request);
+ }
+ }
+
+ // ----------------------------------------------------------------------
+ // The constructor and the delegate.
+ // ----------------------------------------------------------------------
+
+ private final HttpServletRequest delegate;
+
+ public FileUploadServletRequest(HttpServletRequest delegate) {
+ this.delegate = delegate;
+ }
+
+ protected HttpServletRequest getDelegate() {
+ return this.delegate;
+ }
+
+ // ----------------------------------------------------------------------
+ // New functionality to be implemented by the subclasses.
+ // ----------------------------------------------------------------------
+
+ public abstract boolean isMultipart();
+
+ public abstract Map> getFiles();
+
+ // ----------------------------------------------------------------------
+ // Delegated methods.
+ // ----------------------------------------------------------------------
+
+ @Override
+ public String getAuthType() {
+ return delegate.getAuthType();
+ }
+
+ @Override
+ public String getContextPath() {
+ return delegate.getContextPath();
+ }
+
+ @Override
+ public Cookie[] getCookies() {
+ return delegate.getCookies();
+ }
+
+ @Override
+ public long getDateHeader(String name) {
+ return delegate.getDateHeader(name);
+ }
+
+ @Override
+ public String getHeader(String name) {
+ return delegate.getHeader(name);
+ }
+
+ @Override
+ public Enumeration> getHeaderNames() {
+ return delegate.getHeaderNames();
+ }
+
+ @Override
+ public Enumeration> getHeaders(String name) {
+ return delegate.getHeaders(name);
+ }
+
+ @Override
+ public int getIntHeader(String name) {
+ return delegate.getIntHeader(name);
+ }
+
+ @Override
+ public String getMethod() {
+ return delegate.getMethod();
+ }
+
+ @Override
+ public String getPathInfo() {
+ return delegate.getPathInfo();
+ }
+
+ @Override
+ public String getPathTranslated() {
+ return delegate.getPathTranslated();
+ }
+
+ @Override
+ public String getQueryString() {
+ return delegate.getQueryString();
+ }
+
+ @Override
+ public String getRemoteUser() {
+ return delegate.getRemoteUser();
+ }
+
+ @Override
+ public String getRequestURI() {
+ return delegate.getRequestURI();
+ }
+
+ @Override
+ public StringBuffer getRequestURL() {
+ return delegate.getRequestURL();
+ }
+
+ @Override
+ public String getRequestedSessionId() {
+ return delegate.getRequestedSessionId();
+ }
+
+ @Override
+ public String getServletPath() {
+ return delegate.getServletPath();
+ }
+
+ @Override
+ public HttpSession getSession() {
+ return delegate.getSession();
+ }
+
+ @Override
+ public HttpSession getSession(boolean create) {
+ return delegate.getSession(create);
+ }
+
+ @Override
+ public Principal getUserPrincipal() {
+ return delegate.getUserPrincipal();
+ }
+
+ @Override
+ public boolean isRequestedSessionIdFromCookie() {
+ return delegate.isRequestedSessionIdFromCookie();
+ }
+
+ @Override
+ public boolean isRequestedSessionIdFromURL() {
+ return delegate.isRequestedSessionIdFromURL();
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public boolean isRequestedSessionIdFromUrl() {
+ return delegate.isRequestedSessionIdFromUrl();
+ }
+
+ @Override
+ public boolean isRequestedSessionIdValid() {
+ return delegate.isRequestedSessionIdValid();
+ }
+
+ @Override
+ public boolean isUserInRole(String role) {
+ return delegate.isUserInRole(role);
+ }
+
+ @Override
+ public Object getAttribute(String name) {
+ return delegate.getAttribute(name);
+ }
+
+ @Override
+ public Enumeration> getAttributeNames() {
+ return delegate.getAttributeNames();
+ }
+
+ @Override
+ public String getCharacterEncoding() {
+ return delegate.getCharacterEncoding();
+ }
+
+ @Override
+ public int getContentLength() {
+ return delegate.getContentLength();
+ }
+
+ @Override
+ public String getContentType() {
+ return delegate.getContentType();
+ }
+
+ @Override
+ public ServletInputStream getInputStream() throws IOException {
+ return delegate.getInputStream();
+ }
+
+ @Override
+ public String getLocalAddr() {
+ return delegate.getLocalAddr();
+ }
+
+ @Override
+ public String getLocalName() {
+ return delegate.getLocalName();
+ }
+
+ @Override
+ public int getLocalPort() {
+ return delegate.getLocalPort();
+ }
+
+ @Override
+ public Locale getLocale() {
+ return delegate.getLocale();
+ }
+
+ @Override
+ public Enumeration> getLocales() {
+ return delegate.getLocales();
+ }
+
+ @Override
+ public String getProtocol() {
+ return delegate.getProtocol();
+ }
+
+ @Override
+ public BufferedReader getReader() throws IOException {
+ return delegate.getReader();
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public String getRealPath(String path) {
+ return delegate.getRealPath(path);
+ }
+
+ @Override
+ public String getRemoteAddr() {
+ return delegate.getRemoteAddr();
+ }
+
+ @Override
+ public String getRemoteHost() {
+ return delegate.getRemoteHost();
+ }
+
+ @Override
+ public int getRemotePort() {
+ return delegate.getRemotePort();
+ }
+
+ @Override
+ public RequestDispatcher getRequestDispatcher(String path) {
+ return delegate.getRequestDispatcher(path);
+ }
+
+ @Override
+ public String getScheme() {
+ return delegate.getScheme();
+ }
+
+ @Override
+ public String getServerName() {
+ return delegate.getServerName();
+ }
+
+ @Override
+ public int getServerPort() {
+ return delegate.getServerPort();
+ }
+
+ @Override
+ public boolean isSecure() {
+ return delegate.isSecure();
+ }
+
+ @Override
+ public void removeAttribute(String name) {
+ delegate.removeAttribute(name);
+ }
+
+ @Override
+ public void setAttribute(String name, Object o) {
+ delegate.setAttribute(name, o);
+ }
+
+ @Override
+ public void setCharacterEncoding(String env)
+ throws UnsupportedEncodingException {
+ delegate.setCharacterEncoding(env);
+ }
+
+}
diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/filestorage/uploadrequest/MultipartHttpServletRequest.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/filestorage/uploadrequest/MultipartHttpServletRequest.java
new file mode 100644
index 000000000..4f174fc1f
--- /dev/null
+++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/filestorage/uploadrequest/MultipartHttpServletRequest.java
@@ -0,0 +1,157 @@
+/* $This file is distributed under the terms of the license in /doc/license.txt$ */
+
+package edu.cornell.mannlib.vitro.webapp.filestorage.uploadrequest;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.commons.fileupload.FileItem;
+import org.apache.commons.fileupload.FileItemFactory;
+import org.apache.commons.fileupload.FileUploadException;
+import org.apache.commons.fileupload.disk.DiskFileItemFactory;
+import org.apache.commons.fileupload.servlet.ServletFileUpload;
+import org.apache.log4j.Logger;
+
+/**
+ * A wrapper for a servlet request that holds multipart content. Parsing the
+ * request will consume the parameters, so we need to hold them here to answer
+ * any parameter-related requests. File-related information will also be held
+ * here, to answer file-related requests.
+ */
+class MultipartHttpServletRequest extends FileUploadServletRequest {
+ private static final Logger LOG = Logger
+ .getLogger(MultipartHttpServletRequest.class);
+
+ private static final String[] EMPTY_ARRAY = new String[0];
+
+ private final Map> parameters;
+ private final Map> files;
+
+ /**
+ * Parse the multipart request. Store the info about the request parameters
+ * and the uploaded files.
+ */
+ public MultipartHttpServletRequest(HttpServletRequest request)
+ throws IOException {
+ super(request);
+
+ Map> parameters = new HashMap>();
+ Map> files = new HashMap>();
+
+ try {
+ FileItemFactory factory = new DiskFileItemFactory();
+ ServletFileUpload upload = new ServletFileUpload(factory);
+ List items = parseRequestIntoFileItems(request, upload);
+ for (FileItem item : items) {
+ // Process a regular form field
+ if (item.isFormField()) {
+ addToParameters(parameters, item.getFieldName(), item
+ .getString());
+ LOG.debug("Form field (parameter) " + item.getFieldName()
+ + "=" + item.getString());
+ } else {
+ addToFileItems(files, item);
+ LOG.debug("File " + item.getFieldName() + ": "
+ + item.getName());
+ }
+ }
+ } catch (FileUploadException e) {
+ String message = "Failed to parse a multipart-content request.";
+ LOG.error(message, e);
+ throw new IOException(message, e);
+ }
+
+ this.parameters = Collections.unmodifiableMap(parameters);
+ LOG.debug("Parameters are: " + this.parameters);
+ this.files = Collections.unmodifiableMap(files);
+ LOG.debug("Files are: " + this.files);
+ }
+
+ /** Either create a new List for the value, or add to an existing List. */
+ private void addToParameters(Map> map, String name,
+ String value) {
+ if (!map.containsKey(name)) {
+ map.put(name, new ArrayList());
+ }
+ map.get(name).add(value);
+ }
+
+ /** Either create a new List for the file, or add to an existing List. */
+ private void addToFileItems(Map> map,
+ FileItem file) {
+ String name = file.getFieldName();
+ if (!map.containsKey(name)) {
+ map.put(name, new ArrayList());
+ }
+ map.get(name).add(file);
+ }
+
+ /** Minimize the code that uses the unchecked cast. */
+ @SuppressWarnings("unchecked")
+ private List parseRequestIntoFileItems(HttpServletRequest req,
+ ServletFileUpload upload) throws FileUploadException {
+ return upload.parseRequest(req);
+ }
+
+ // ----------------------------------------------------------------------
+ // This is a multipart request, so make the file info available.
+ // ----------------------------------------------------------------------
+
+ @Override
+ public boolean isMultipart() {
+ return true;
+ }
+
+ @Override
+ public Map> getFiles() {
+ return files;
+ }
+
+ // ----------------------------------------------------------------------
+ // Parameter-related methods won't find anything on the delegate request,
+ // since parsing consumed the parameters. So we need to look to the parsed
+ // info for the answers.
+ // ----------------------------------------------------------------------
+
+ @Override
+ public String getParameter(String name) {
+ if (parameters.containsKey(name)) {
+ return parameters.get(name).get(0);
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public Enumeration> getParameterNames() {
+ return Collections.enumeration(parameters.keySet());
+ }
+
+ @Override
+ public String[] getParameterValues(String name) {
+ if (parameters.containsKey(name)) {
+ return parameters.get(name).toArray(EMPTY_ARRAY);
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public Map, ?> getParameterMap() {
+ Map result = new HashMap();
+ for (Entry> entry : parameters.entrySet()) {
+ result.put(entry.getKey(), entry.getValue().toArray(EMPTY_ARRAY));
+ }
+ LOG.debug("resulting parameter map: " + result);
+ return result;
+ }
+
+}
diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/filestorage/uploadrequest/SimpleHttpServletRequestWrapper.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/filestorage/uploadrequest/SimpleHttpServletRequestWrapper.java
new file mode 100644
index 000000000..91e98f16b
--- /dev/null
+++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/filestorage/uploadrequest/SimpleHttpServletRequestWrapper.java
@@ -0,0 +1,64 @@
+/* $This file is distributed under the terms of the license in /doc/license.txt$ */
+
+package edu.cornell.mannlib.vitro.webapp.filestorage.uploadrequest;
+
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.commons.fileupload.FileItem;
+
+/**
+ * A wrapper for a servlet request that does not hold multipart content. Pass
+ * all parameter-related requests to the delegate, and give simple answers to
+ * all file-related requests.
+ */
+class SimpleHttpServletRequestWrapper extends FileUploadServletRequest {
+
+ SimpleHttpServletRequestWrapper(HttpServletRequest request) {
+ super(request);
+ }
+
+ // ----------------------------------------------------------------------
+ // Not a multipart request, so there are no files.
+ // ----------------------------------------------------------------------
+
+ @Override
+ public boolean isMultipart() {
+ return false;
+ }
+
+ @Override
+ public Map> getFiles() {
+ return Collections.emptyMap();
+ }
+
+ // ----------------------------------------------------------------------
+ // Since this is not a multipart request, the parameter methods can be
+ // delegated.
+ // ----------------------------------------------------------------------
+
+ @Override
+ public String getParameter(String name) {
+ return getDelegate().getParameter(name);
+ }
+
+ @Override
+ public Map, ?> getParameterMap() {
+ return getDelegate().getParameterMap();
+ }
+
+ @Override
+ public Enumeration> getParameterNames() {
+ return getDelegate().getParameterNames();
+ }
+
+ @Override
+ public String[] getParameterValues(String name) {
+ return getDelegate().getParameterValues(name);
+ }
+
+}