Source Code : Wrap file upload requests
Wrap file upload requests
Support for file uploads was added relatively late to the Servlet Specification, starting with version 3.0. Earlier versions of the spec have poor support for file uploads. The following applies to such earlier versions.
If a form contains one or morefile uploadcontrols, the format of the underlying HTTP request changes dramatically. Unless special steps are taken, it's likely thatrequest.getParameter(String)will returnnullforallparameters (both regular parameters and file upload parameters).
Reminder - if a form includes a file upload control, then it must have:
- method='POST'
- enctype='multipart/form-data'
One technique for handling file upload requests uses awrapperfor the underlying request, such thatrequest.getParameter(String)and related methods may be used in the usual way.
Example
The following wrapper uses the Apache CommonsFileUploadtool to parse the request into both regular parameters and file upload parameters. An action class may use this class as it would any request, with one exception: to access the methods related specifically to files, a cast is necessary.
import java.util.*;
import java.io.*;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.FileItem;
/**
* Wrapper for a file upload request (before Servlet 3.0).
*
* <P>This class uses the Apache Commons
* <a href='http://commons.apache.org/fileupload/'>File Upload tool</a>.
* The generous Apache License will very likely allow you to use it in your
* applications as well.
*/
public class FileUploadWrapper extends HttpServletRequestWrapper {
/** Constructor. */
public FileUploadWrapper(HttpServletRequest aRequest) throws IOException {
super(aRequest);
ServletFileUpload upload = new ServletFileUpload( new DiskFileItemFactory());
try {
List<FileItem> fileItems = upload.parseRequest(aRequest);
convertToMaps(fileItems);
}
catch(FileUploadException ex){
throw new IOException("Cannot parse underlying request: " + ex.toString());
}
}
/**
* Return all request parameter names, for both regular controls and file upload
* controls.
*/
@Override public Enumeration<String> getParameterNames() {
Set<String> allNames = new LinkedHashSet<>();
allNames.addAll(fRegularParams.keySet());
allNames.addAll(fFileParams.keySet());
return Collections.enumeration(allNames);
}
/**
* Return the parameter value. Applies only to regular parameters, not to
* file upload parameters.
*
* <P>If the parameter is not present in the underlying request,
* then <tt>null</tt> is returned.
* <P>If the parameter is present, but has no associated value,
* then an empty string is returned.
* <P>If the parameter is multivalued, return the first value that
* appears in the request.
*/
@Override public String getParameter(String aName) {
String result = null;
List<String> values = fRegularParams.get(aName);
if(values == null){
//you might try the wrappee, to see if it has a value
}
else if (values.isEmpty()) {
//param name known, but no values present
result = "";
}
else {
//return first value in list
result = values.get(FIRST_VALUE);
}
return result;
}
/**
* Return the parameter values. Applies only to regular parameters,
* not to file upload parameters.
*/
@Override public String[] getParameterValues(String aName) {
String[] result = null;
List<String> values = fRegularParams.get(aName);
if(values != null) {
result = values.toArray(new String[values.size()]);
}
return result;
}
/**
* Return a {@code Map<String, List<String>>} for all regular parameters.
* Does not return any file upload parameters at all.
*/
@Override public Map<String, List<String>> getParameterMap() {
return Collections.unmodifiableMap(fRegularParams);
}
/**
* Return a {@code List<FileItem>}, in the same order as they appear
* in the underlying request.
*/
public List<FileItem> getFileItems(){
return new ArrayList<FileItem>(fFileParams.values());
}
/**
* Return the {@link FileItem} of the given name.
* <P>If the name is unknown, then return <tt>null</tt>.
*/
public FileItem getFileItem(String aFieldName){
return fFileParams.get(aFieldName);
}
// PRIVATE
/** Store regular params only. May be multivalued (hence the List). */
private final Map<String, List<String>> fRegularParams = new LinkedHashMap<>();
/** Store file params only. */
private final Map<String, FileItem> fFileParams = new LinkedHashMap<>();
private static final int FIRST_VALUE = 0;
private void convertToMaps(List<FileItem> aFileItems){
for(FileItem item: aFileItems) {
if ( isFileUploadField(item) ) {
fFileParams.put(item.getFieldName(), item);
}
else {
if( alreadyHasValue(item) ){
addMultivaluedItem(item);
}
else {
addSingleValueItem(item);
}
}
}
}
private boolean isFileUploadField(FileItem aFileItem){
return ! aFileItem.isFormField();
}
private boolean alreadyHasValue(FileItem aItem){
return fRegularParams.get(aItem.getFieldName()) != null;
}
private void addSingleValueItem(FileItem aItem){
List<String> list = new ArrayList<>();
list.add(aItem.getString());
fRegularParams.put(aItem.getFieldName(), list);
}
private void addMultivaluedItem(FileItem aItem){
List<String> values = fRegularParams.get(aItem.getFieldName());
values.add(aItem.getString());
}
}
The wrapper is in turn used by aFilter:
import java.io.*;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
/**
* Filter that wraps an underlying file upload request (before Servlet 3.0).
*
* <P>This filter should be configured only for those operations that use a
* file upload request.
*/
public final class FileUploadFilter implements Filter {
public void init(FilterConfig aConfig) throws ServletException {
//do nothing
}
public void destroy() {
//do nothing
}
public void doFilter(
ServletRequest aRequest, ServletResponse aResponse, FilterChain aChain
) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) aRequest;
if ( isFileUploadRequest(request) ) {
FileUploadWrapper wrapper = new FileUploadWrapper(request);
aChain.doFilter(wrapper, aResponse);
}
else {
aChain.doFilter(aRequest, aResponse);
}
}
private boolean isFileUploadRequest(HttpServletRequest aRequest){
return
aRequest.getMethod().equalsIgnoreCase("POST") &&
aRequest.getContentType().startsWith("multipart/form-data")
;
}
}
TheFilteris configured inweb.xmlto handle specific URLs :
<web-app>
<filter>
<filter-name>FileUpload</filter-name>
<display-name>File Upload</display-name>
<description>
Applied to file upload requests.
Wraps the request, allowing it to be used as a regular request,
'as if' it were parsed by the Servlet API.
</description>
<filter-class>com.blah.FileUploadFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>FileUpload</filter-name>
<url-pattern>/someurl/*</url-pattern>
</filter-mapping>
<!-- Required by the Apache FileUpload tool. -->
<listener>
<listener-class>
org.apache.commons.fileupload.servlet.FileCleanerCleanup
</listener-class>
</listener>
</web-app>
There's a variation regarding therequest.getDispatcher(String)method which can be important here. This method takes a path argument, and that path can contain query parametersnot present in the original request. Therefore, the above wrapper will not 'see' such query parameters unless special measures are taken. For example,getParameter(String)might be modified along these lines:
@Override public String getParameter(String aName) {
String result = null;
List values = fRegularParams.get(aName);
if(values == null){
//see if the 'wrappee' has a value
String superValue = super.getParameter(aName);
if(Util.textHasContent(superValue)) {
result = superValue;
}
}
...elided...
}