views:

116

answers:

2

I'm trying to add a very simple file input to my webapp which I'm doing using JSF2.0 and RichFaces 3.3.3, the thing is I really dislike the richfaces fileInput component and I'm looking for something simpler (in terms of use and looks), so far I've found:

  • tomahawk's fileInput - but tomahawk only supports JSF1.2
  • trinidad fileInput - but trinidad for JSF2.0 is in alpha stage
  • primefaces - but of course they won't work with RichFaces 3.3.3 (only 4.0 which are in beta)

Are there any other options? I need something really simple like a normal html file input component.

A: 

Dear either you have to use rich:uploadFile or make custom component in JSF by following http://balusc.blogspot.com/2009/12/uploading-files-with-jsf-20-and-servlet.html

taher
The problem here is that I'm using Tomcat 6, which doesn't support Servlet 3.0 (and it isn't my call to change to tomcat 7).
Zenzen
+1  A: 

You basically need to do two things:

  1. Create a Filter which puts the multipart/form-data items in a custom map and replace the original request parameter map with it so that the normal request.getParameter() process keeps working.

  2. Create a JSF 2.0 custom component which renders a input type="file" and which is aware of this custom map and can obtain the uploaded files from it.

@taher has already given a link where you could find insights and code snippets. The JSF 2.0 snippets should be reuseable. You yet have to modify the MultipartMap to use the good 'ol Apache Commons FileUpload API instead of the Servlet 3.0 API.

If I have time, I will by end of day rewrite it and post it here.


Update: I almost forgot you, I did a quick update to replace Servlet 3.0 API by Commons FileUpload API, it should work:

package net.balusc.http.multipart;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
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.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;

public class MultipartMap extends HashMap<String, Object> {

    // Constants ----------------------------------------------------------------------------------

    private static final String ATTRIBUTE_NAME = "parts";
    private static final String DEFAULT_ENCODING = "UTF-8";
    private static final int DEFAULT_BUFFER_SIZE = 10240; // 10KB.

    // Vars ---------------------------------------------------------------------------------------

    private String encoding;
    private String location;

    // Constructors -------------------------------------------------------------------------------

    /**
     * Construct multipart map based on the given multipart request and file upload location. When
     * the encoding is not specified in the given request, then it will default to <tt>UTF-8</tt>.
     * @param multipartRequest The multipart request to construct the multipart map for.
     * @param location The location to save uploaded files in.
     * @throws ServletException If something fails at Servlet level.
     * @throws IOException If something fails at I/O level.
     */
    @SuppressWarnings("unchecked") // ServletFileUpload#parseRequest() isn't parameterized.
    public MultipartMap(HttpServletRequest multipartRequest, String location)
        throws ServletException, IOException
    {
        multipartRequest.setAttribute(ATTRIBUTE_NAME, this);

        this.encoding = multipartRequest.getCharacterEncoding();
        if (this.encoding == null) {
            multipartRequest.setCharacterEncoding(this.encoding = DEFAULT_ENCODING);
        }
        this.location = location;

        try {
            List<FileItem> parts = new ServletFileUpload(new DiskFileItemFactory()).parseRequest(multipartRequest);
            for (FileItem part : parts) {
                if (part.isFormField()) {
                    processFormField(part);
                } else if (!part.getName().isEmpty()) {
                    processFileField(part);
                }
            }
        } catch (FileUploadException e) {
            throw new ServletException("Parsing multipart/form-data request failed.", e);
        }
    }

    // Actions ------------------------------------------------------------------------------------

    @Override
    public Object get(Object key) {
        Object value = super.get(key);
        if (value instanceof String[]) {
            String[] values = (String[]) value;
            return values.length == 1 ? values[0] : Arrays.asList(values);
        } else {
            return value; // Can be File or null.
        }
    }

    /**
     * @see ServletRequest#getParameter(String)
     * @throws IllegalArgumentException If this field is actually a File field.
     */
    public String getParameter(String name) {
        Object value = super.get(name);
        if (value instanceof File) {
            throw new IllegalArgumentException("This is a File field. Use #getFile() instead.");
        }
        String[] values = (String[]) value;
        return values != null ? values[0] : null;
    }

    /**
     * @see ServletRequest#getParameterValues(String)
     * @throws IllegalArgumentException If this field is actually a File field.
     */
    public String[] getParameterValues(String name) {
        Object value = super.get(name);
        if (value instanceof File) {
            throw new IllegalArgumentException("This is a File field. Use #getFile() instead.");
        }
        return (String[]) value;
    }

    /**
     * @see ServletRequest#getParameterNames()
     */
    public Enumeration<String> getParameterNames() {
        return Collections.enumeration(keySet());
    }

    /**
     * @see ServletRequest#getParameterMap()
     */
    public Map<String, String[]> getParameterMap() {
        Map<String, String[]> map = new HashMap<String, String[]>();
        for (Entry<String, Object> entry : entrySet()) {
            Object value = entry.getValue();
            if (value instanceof String[]) {
                map.put(entry.getKey(), (String[]) value);
            } else {
                map.put(entry.getKey(), new String[] { ((File) value).getName() });
            }
        }
        return map;
    }

    /**
     * Returns uploaded file associated with given request parameter name.
     * @param name Request parameter name to return the associated uploaded file for.
     * @return Uploaded file associated with given request parameter name.
     * @throws IllegalArgumentException If this field is actually a Text field.
     */
    public File getFile(String name) {
        Object value = super.get(name);
        if (value instanceof String[]) {
            throw new IllegalArgumentException("This is a Text field. Use #getParameter() instead.");
        }
        return (File) value;
    }

    // Helpers ------------------------------------------------------------------------------------

    /**
     * Process given part as Text part.
     */
    private void processFormField(FileItem part) {
        String name = part.getFieldName();
        String[] values = (String[]) super.get(name);

        if (values == null) {
            // Not in parameter map yet, so add as new value.
            put(name, new String[] { part.getString() });
        } else {
            // Multiple field values, so add new value to existing array.
            int length = values.length;
            String[] newValues = new String[length + 1];
            System.arraycopy(values, 0, newValues, 0, length);
            newValues[length] = part.getString();
            put(name, newValues);
        }
    }

    /**
     * Process given part as File part which is to be saved in temp dir with the given filename.
     */
    private void processFileField(FileItem part) throws IOException {

        // Get filename prefix (actual name) and suffix (extension).
        String filename = FilenameUtils.getName(part.getName());
        String prefix = filename;
        String suffix = "";
        if (filename.contains(".")) {
            prefix = filename.substring(0, filename.lastIndexOf('.'));
            suffix = filename.substring(filename.lastIndexOf('.'));
        }

        // Write uploaded file.
        File file = File.createTempFile(prefix + "_", suffix, new File(location));
        InputStream input = null;
        OutputStream output = null;
        try {
            input = new BufferedInputStream(part.getInputStream(), DEFAULT_BUFFER_SIZE);
            output = new BufferedOutputStream(new FileOutputStream(file), DEFAULT_BUFFER_SIZE);
            IOUtils.copy(input, output);
        } finally {
            IOUtils.closeQuietly(output);
            IOUtils.closeQuietly(input);
        }

        put(part.getFieldName(), file);
        part.delete(); // Cleanup temporary storage.
    }

}

You still need both the MultipartFilter and MultipartRequest classes as described in this article. You only need to remove the @WebFilter annotation and map the filter on an url-pattern of /* along with an <init-param> of location wherein you specify the absolute path where the uploaded files are to be stored. You can use the JSF 2.0 custom file upload component as described in this article unchanged.

BalusC
Thanks BalusC, if you say that replacing the MultipartMap with Apache Commons FileUpload will work then I guess I'll try it out!
Zenzen