Showing posts with label Upload File. Show all posts
Showing posts with label Upload File. Show all posts

Thursday, September 15, 2016

OmniFaces 2.5.1 released with o:inputFile, @GraphicImageBean and MultiViews

Update 21 sep 2016: Oops, I made a mistake in <o:validateBean> class level validation. OmniFaces 2.5.1 has therefore been released!

OmniFaces 2.5 2.5.1 has been released!

The major visible changes are the <o:inputFile> component which extends and improves <h:inputFile>, the @GraphicImageBean annotation which can be used to mark a bean as a public and dedicated image service, and adoption of "MultiViews" in FacesViews.

The major invisible changes are removal of CDI 1.0 targeted workarounds (including the deprecation of org.omnifaces.config.BeanManager enum), intergration of Travis CI and addition of several integration tests based on JUnit/Arquillian/Selenium/Graphene. As of now, @ViewScoped, @Eager, FullAjaxExceptionHandler, FacesViews and CombinedResourceHandler have IT cases which are run against WildFly 10.0.0, TomEE 7.0.1 and Payara 4.1.1.163.

You can find the complete list of additions, changes and fixes at What's new in OmniFaces 2.5.1? list in showcase.

Installation

Non-Maven users: download OmniFaces 2.5.1 JAR and drop it in /WEB-INF/lib the usual way, replacing the older version if any.

Maven users: use <version>2.5.1</version>.

<dependency>
    <groupId>org.omnifaces</groupId>
    <artifactId>omnifaces</artifactId>
    <version>2.5.1</version>
</dependency>

For CDI-less users, there will be no version 1.15. There are no major 2.x bugfixes anymore which should also end up in 1.1x. Therefore it isn't worth the effort to merge 2.x back to 1.1x again. And, due to major changes in 2.x project structure, merging 2.x branch back to 1.1x branch has become a tedious task which isn't worth the effort anymore. OmniFaces 1.1x is now in maintenance mode. The latest 1.1x version is still 1.14. Of course there may be bugfix releases in the future, but so far there are no major 2.4-2.5 bugs which also affect 1.14, so there's no 1.14.1 yet as of now.

If you need some arguments to move to CDI:

  • Upcoming JSF 2.3 will require a CDI dependency
  • JSF own @ManagedBean and @XxxScope annotations are marked deprecated in JSF 2.3 (already as of 2.3-m06)
  • Spring 3.x already supports javax.inject API from CDI, so Spring services (which are often used to substitute the lack of EJB support in Tomcat) can seamlessly be injected in CDI managed beans.

Multi file upload with builtin file type and size validation

The relatively new <h:inputFile> component, which was introduced only in JSF 2.2, has been extended and enhanced into new <o:inputFile> component with support for multiple file selection and folder selection via new HTML5 multiple and directory attributes.

<h:form enctype="multipart/form-data">
    <o:inputFile value="#{bean.files}" multiple="true" />
    <h:commandButton value="Upload" action="#{bean.upload}" />
</h:form>
private List<Part> files; // +getter+setter

public void upload() {
    if (files != null) {
        for (Part file : files) {
            String name = Servlets.getSubmittedFileName(file);
            String type = file.getContentType();
            long size = file.getSize();
            InputStream content = file.getInputStream();
            // ...
        }
    }
}

Also, media type filtering via new HTML5 accept attribute has been added, along with built-in server side validation based on file extension. The below example will in modern browsers only show image files in file browse dialog.

<o:inputFile value="#{bean.file}" accept="image/*" />

And, file size validation via a custom maxsize attribute has been added which runs in both client and server side. The below example sets the file size limit to 10MiB.

<o:inputFile value="#{bean.file}" maxsize="#{10 * 1024 * 1024}" />

In client side JavaScript, the new HTML5 File API will be used to check the file size and a special ajax request will be sent to trigger a JSF faces message. This all will take place without that the whole file needs to be sent to the server side. Instant feedback thus. As fallback for older non-HTML5 clients and as safeguard against spoofed requests, the server side will validate the file size once again after the file is arrived over there.

Independent image service

The <o:graphicImage> component, which was added in OmniFaces 2.0, had a builtin security restriction to prevent users from being able to invoke arbitrary bean methods by manipulating the GET request URL path. One of the consequences of this security restriction is that images served by <o:graphicImage> component aren't consistently hotlinkable. It works only if the page referencing the <o:graphicImage> has been invoked at least once in application's lifetime.

Therefore a new managed bean annotation has been introduced which should mark a bean as a dedicated and public graphic image service. When putting @GraphicImageBean annotation on the bean, then all of bean's public methods which return either InputStream or byte[] will become accessible by a direct GET request URL.

import org.omnifaces.cdi.GraphicImageBean;

@GraphicImageBean
public class Images {

    @Inject
    private ImageService service;

    public byte[] get(Long id) {
        return service.getContent(id);
    }

}

This also allowed the creation of a bunch of new EL functions such as #{of:graphicImageURL(...)} which merely print the graphic image URL without the need for a whole <o:graphicImage> component, which finally makes the below case possible:

<a href="#{of:graphicImageURL('images.full(product.imageId)')}">
    <o:graphicImage value="#{images.thumb(product.imageId)}" />
</a>
import org.omnifaces.cdi.GraphicImageBean;

@GraphicImageBean
public class Images {

    @Inject
    private ImageService service;

    public byte[] full(Long id) {
        return service.getFullContent(id);
    }

    public byte[] thumb(Long id) {
        return service.getThumbContent(id);
    }

}

MultiViews

Ones who are familiar with Apache HTTPD+PHP world are probably aware of the age-old but very useful MultiViews feature of Apache HTTPD which allowed usage of clean URLs such as http://example.com/foo/bar/baz which searches for respectively /foo/bar/baz.php, /foo/bar.php and /foo.php until the first one is found, and then passes the rest of the URL as PATH_INFO variable.

The OmniFaces-builtin extensionless URL feature known as FacesViews has been enhanced to support exactly this MultiViews feature too. It's a matter of adding /* suffix to the org.omnifaces.FACES_VIEWS_SCAN_PATHS context parameter value. The below context parameter makes all files in the current webapp available via extensionless URLs with MultiViews support.

<context-param>
    <param-name>org.omnifaces.FACES_VIEWS_SCAN_PATHS</param-name>
    <param-value>/*.xhtml/*</param-value>
</context-param>

With above configuration, an URL such as http://example.com/contextpath/foo/bar/baz will search for /foo/bar/baz.xhtml, /foo/bar.xhtml and /foo.xhtml in this order until the first one is found and then invoke it. The existing @Param annotation has been enhanced to inject path parameters by their index. Assuming that you've a /foo.xhtml, then the path parameters bar and baz can be injected in the managed bean associated with /foo.xhtml as below:

@Inject @Param(pathIndex=0)
private String bar;

@Inject @Param(pathIndex=1)
private String baz;

Maven download stats

Here are the Maven download stats after previous release:

  • July 2016: 8560
  • August 2016: 8660
  • September 2016: 9341

Sunday, December 27, 2009

Uploading files with JSF 2.0 and Servlet 3.0

WARNING - OUTDATED CONTENT!

This article is targeted on JSF 2.0/2.1. Since JSF 2.2 there's finally native file upload component in flavor of <h:inputFile> whose value can be tied to a javax.servlet.http.Part property. It's recommended to make use of it directly. See also this question & answer.

Introduction

The new Servlet 3.0 specification made uploading files really easy. However, because JSF 2.0 isn't initially designed to be primarily used on top of Servlet 3.0 and should be backwards compatible with Servlet 2.5, it lacks a standard file upload component. Until now you could have used among others Tomahawk's t:inputFileUpload for that. But as of now (December 2009) Tomahawk appears not to be "JSF 2.0 ready" yet and has problems here and there when being used on a JSF 2.0 environment. When you're targeting a Servlet 3.0 compatible container such as Glassfish v3, then you could also just create a custom JSF file upload component yourself.

To prepare, you need to have a Filter which puts the parts of a multipart/form-data request into the request parameter map before the FacesServlet kicks in. The FacesServlet namely doesn't have builtin facilities for this relies on the availablilty of the submitted input component values in the request parameter map. You can find it all here. Put the three classes MultipartMap, MultipartFilter and MultipartRequest in the classpath. The renderer of the custom file upload component relies on them in case of multipart/form-data requests.

Back to top

Custom component and renderer

With the new JSF 2.0 annotations it's now more easy to create custom components yourself. You don't need to hassle with somewhat opaque XML configurations anymore. I however only had a little hard time in figuring the best way to create custom components with help of annotations, because it's nowhere explained in the Java EE 6 tutorial nor the JSR314 - JSF 2.0 Specification. I am sure that the Sun JSF guys are also reading here, so here it is: Please work on that, it was already opaque in JSF 1.x and it should not be that more opaque in JSF 2.0!

At any way, I finally figured it with little help of Jim Driscoll's blog and exploring the JSF 2.0 source code.

First, let's look what we need: in the line of h:inputText component which renders a HTML input type="text" element, we would like to have a fictive h:inputFile component which renders a HTML input type="file" element. As h:inputText component is represented by a HtmlInputText class, we would thus like to have a HtmlInputFile class which extends HtmlInputText and overrides the renderer type so that it generates a HTML input type="file" element instead.

Okay, that's no big deal, so here it is:

/*
 * net/balusc/jsf/component/html/HtmlInputFile.java
 * 
 * Copyright (C) 2009 BalusC
 * 
 * This program is free software: you can redistribute it and/or modify it under the terms of the
 * GNU Lesser General Public License as published by the Free Software Foundation, either version 3
 * of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License along with this library.
 * If not, see <http://www.gnu.org/licenses/>.
 */

package net.balusc.jsf.component.html;

import javax.faces.component.FacesComponent;
import javax.faces.component.html.HtmlInputText;

/**
 * Faces component for <code>input type="file"</code> field.
 * 
 * @author BalusC
 * @link http://balusc.blogspot.com/2009/12/uploading-files-with-jsf-20-and-servlet.html
 */
@FacesComponent(value = "HtmlInputFile")
public class HtmlInputFile extends HtmlInputText {

    // Getters ------------------------------------------------------------------------------------
    
    @Override
    public String getRendererType() {
        return "javax.faces.File";
    }

}

The nice thing is that this component inherits all of the standard attributes of HtmlInputText so that you don't need to redefine them (fortunately not; it would have been a fairly tedious task and a lot of code).

The value of the @FacesComponent annotation represents the component-type which is to be definied in the taglib xml file (shown later). The getRendererType() should return the renderer-type of the renderer class which is to be annotated using @FacesRenderer.

Extending the renderer is however quite a work when you want to be implementation independent, you need to take all possible attributes into account here as well. In this case we assume that you're going to use and stick to Mojarra 2.x forever (and thus not replace by another JSF implementation such as MyFaces sooner or later). Analogous with extending HtmlInputText to HtmlInputFile we thus want to extend its Mojarra-specific renderer TextRenderer to FileRenderer so that it renders a HTML input type="file" element instead.

/*
 * net/balusc/jsf/renderer/html/FileRenderer.java
 * 
 * Copyright (C) 2009 BalusC
 * 
 * This program is free software: you can redistribute it and/or modify it under the terms of the
 * GNU Lesser General Public License as published by the Free Software Foundation, either version 3
 * of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License along with this library.
 * If not, see <http://www.gnu.org/licenses/>.
 */

package net.balusc.jsf.renderer.html;

import java.io.File;
import java.io.IOException;

import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.convert.ConverterException;
import javax.faces.render.FacesRenderer;

import net.balusc.http.multipart.MultipartRequest;

import com.sun.faces.renderkit.Attribute;
import com.sun.faces.renderkit.AttributeManager;
import com.sun.faces.renderkit.RenderKitUtils;
import com.sun.faces.renderkit.html_basic.TextRenderer;

/**
 * Faces renderer for <code>input type="file"</code> field.
 * 
 * @author BalusC
 * @link http://balusc.blogspot.com/2009/12/uploading-files-with-jsf-20-and-servlet.html
 */
@FacesRenderer(componentFamily = "javax.faces.Input", rendererType = "javax.faces.File")
public class FileRenderer extends TextRenderer {

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

    private static final String EMPTY_STRING = "";
    private static final Attribute[] INPUT_ATTRIBUTES =
        AttributeManager.getAttributes(AttributeManager.Key.INPUTTEXT);

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

    @Override
    protected void getEndTextToRender
        (FacesContext context, UIComponent component, String currentValue)
            throws IOException
    {
        ResponseWriter writer = context.getResponseWriter();
        writer.startElement("input", component);
        writeIdAttributeIfNecessary(context, writer, component);
        writer.writeAttribute("type", "file", null);
        writer.writeAttribute("name", (component.getClientId(context)), "clientId");

        // Render styleClass, if any.
        String styleClass = (String) component.getAttributes().get("styleClass");
        if (styleClass != null) {
            writer.writeAttribute("class", styleClass, "styleClass");
        }

        // Render standard HTMLattributes expect of styleClass.
        RenderKitUtils.renderPassThruAttributes(
            context, writer, component, INPUT_ATTRIBUTES, getNonOnChangeBehaviors(component));
        RenderKitUtils.renderXHTMLStyleBooleanAttributes(writer, component);
        RenderKitUtils.renderOnchange(context, component, false);

        writer.endElement("input");
    }

    @Override
    public void decode(FacesContext context, UIComponent component) {
        rendererParamsNotNull(context, component);
        if (!shouldDecode(component)) {
            return;
        }
        String clientId = decodeBehaviors(context, component);
        if (clientId == null) {
            clientId = component.getClientId(context);
        }
        File file = ((MultipartRequest) context.getExternalContext().getRequest()).getFile(clientId);

        // If no file is specified, set empty String to trigger validators.
        ((UIInput) component).setSubmittedValue((file != null) ? file : EMPTY_STRING);
    }

    @Override
    public Object getConvertedValue(FacesContext context, UIComponent component, Object submittedValue)
        throws ConverterException
    {
        return (submittedValue != EMPTY_STRING) ? submittedValue : null;
    }

}

Note that the @FacesRenderer annotation also specifies a component family of "javax.faces.Input" and that this is nowhere specified in our HtmlInputFile. That's also not needed, it's already inherited from HtmlInputText.

Now, to use the custom JSF 2.0 file upload component in Facelets we really need to define another XML file. It's however not a big deal. You fortunately don't need to define all the tag attributes as you should have done in case of JSP. Just define the namespace (which you need to specify in the xmlns attribute of the <html> tag), the tag name (to identify the tag in XHTML) and the component type (as definied in the @FacesComponent of the associated component class).

Create a new XML file at /WEB-INF/balusc.taglib.xml and fill it as follows:

<?xml version="1.0" encoding="UTF-8"?>
<facelet-taglib
    xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
        http://java.sun.com/xml/ns/javaee/web-facelettaglibrary_2_0.xsd"
    version="2.0">

    <namespace>http://balusc.net/jsf/html</namespace>
    <tag>
        <tag-name>inputFile</tag-name>
        <component>
            <component-type>HtmlInputFile</component-type>
        </component>
    </tag>
</facelet-taglib>

You need to familarize Facelets with the new taglib in web.xml as follows:


    <context-param>
        <param-name>javax.faces.FACELETS_LIBRARIES</param-name>
        <param-value>/WEB-INF/balusc.taglib.xml</param-value>
    </context-param>

Note, if you have multiple Facelets taglibs, then you can separate the paths with a semicolon ;.

Back to top

Basic use example

Here is a basic use example of a JSF managed bean and a Facelets page which demonstrates the working of all of the stuff. First the managed bean UploadBean:

package net.balusc.example.upload;

import java.io.File;
import java.util.Arrays;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;

@ManagedBean
@RequestScoped
public class UploadBean {

    private String text;
    private File file;
    private String[] check;

    public void submit() {
        // Now do your thing with the obtained input.
        System.out.println("Text: " + text);
        System.out.println("File: " + file);
        System.out.println("Check: " + Arrays.toString(check));
    }

    public String getText() {
        return text;
    }

    public File getFile() {
        return file;
    }

    public String[] getCheck() {
        return check;
    }

    public void setText(String text) {
        this.text = text;
    }

    public void setFile(File file) {
        this.file = file;
    }

    public void setCheck(String[] check) {
        this.check = check;
    }
    
}

And now the Facelets page upload.xhtml:

<!DOCTYPE html>
<html
    xmlns="http://www.w3.org/1999/xhtml"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:hh="http://balusc.net/jsf/html">
    <h:head>
        <title>JSF 2.0 and Servlet 3.0 file upload test</title>
        <style>label { float: left; display: block; width: 75px; }</style>
    </h:head>
    <h:body>
        <h:form id="form" method="post" enctype="multipart/form-data">
            <h:outputLabel for="text">Text:</h:outputLabel>
            <h:inputText id="text" value="#{uploadBean.text}" />
            <br />
            <h:outputLabel for="file">File:</h:outputLabel>
            <hh:inputFile id="file" value="#{uploadBean.file}" />
            <h:outputText value="File #{uploadBean.file.name} successfully uploaded!" 
                rendered="#{not empty uploadBean.file}" />
            <br />
            <h:selectManyCheckbox id="check" layout="pageDirection" value="#{uploadBean.check}">
                <f:selectItem itemLabel="Check 1:" itemValue="check1" />
                <f:selectItem itemLabel="Check 2:" itemValue="check2" />
            </h:selectManyCheckbox>
            <h:commandButton value="submit" action="#{uploadBean.submit}" />
            <h:messages />
        </h:form>
    </h:body>
</html>

Copy'n'paste the stuff and run it at http://localhost:8080/playground/upload.jsf (assuming that your local development server runs at port 8080 and that the context root of your playground web application project is called 'playground' and that you have the FacesServlet in web.xml mapped on *.jsf) and see it working! And no, you don't need to do anything with faces-config.xml, the managed bean is automagically found and initialized with help of the new JSF 2.0 annotations.

Note: this all is developed and tested with Eclipse 3.5 and Glassfish v3.

Back to top

Validate uploaded file

The lack of the support of @MultipartConfig annotation in the filter and JSF also implies that the size of the uploaded file can't be restricted by the maxFileSize annotation field. It is however possible to attach a simple validator to the custom component. Here's an example:

package net.balusc.example.upload;

import java.io.File;

import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.validator.FacesValidator;
import javax.faces.validator.Validator;
import javax.faces.validator.ValidatorException;

@FacesValidator(value = "fileValidator")
public class FileValidator implements Validator {

    private static final long MAX_FILE_SIZE = 10485760L; // 10MB.
    
    @Override
    public void validate(FacesContext context, UIComponent component, Object value)
        throws ValidatorException
    {
        File file = (File) value;
        if (file != null && file.length() > MAX_FILE_SIZE) {
            file.delete(); // Free resources!
            throw new ValidatorException(new FacesMessage(String.format(
                "File exceeds maximum permitted size of %d bytes.", MAX_FILE_SIZE)));
        }
    }

}

You can attach it as follows:


    <hh:inputFile validator="fileValidator" />

You can also use a f:validator instead:


    <hh:inputFile>
        <f:validator validatorId="fileValidator" />
    </hh:inputFile>

That should be it. Also no faces-config stuff is needed here thanks to the annotations.

Back to top

Copyright - GNU Lesser General Public License

(C) December 2009, BalusC

Uploading files in Servlet 3.0

Introduction

The new Servlet 3.0 specification includes among others support for parsing multipart/form-data requests. All you basically need to do is to annotate the Servlet with @MultipartConfig annotation. No need for Apache Commons FileUpload anymore! Interesting detail is however that both Oracle Glassfish v3 and Apache Tomcat 7.0 actually silently uses Apache Commons FileUpload under the covers to fulfill the new Servlet 3.0 feature!


@MultipartConfig
public class UploadServlet extends HttpServlet {}

This way all multipart/form-data parts are available by HttpServletRequest#getParts(). It returns a collection of Part elements. This is to be used instead of the normal getParameter() calls and so on. The Part API itself is however somewhat limited in the degree of abstraction. To find out whether the part represents a normal text field or a file field, you'll have to parse the content-disposition header yourself to find out if the filename parameter is in there. Also, when you want to get the actual parameter value as String, you need to read the Part#getInputStream() into a String yourself. You'll also have to collect multiple parameter values together yourself based on the part name, where you could have used getParameterValues().

All that extra work does not harm if you have only one file upload servlet in your webapplication. But at times you would like to avoid repeating the same code again and again. Or you would like to continue using the getParameter() stuff the same way as for normal request. Or you would like to have all the parts be available as HttpServletRequest#getParameterMap() in Expression Language as you did before by ${param}.

Back to top

MultipartMap

For that I've created the MultipartMap. It simulates the HttpServletRequest#getParameterXXX() methods to ease the processing in @MultipartConfig servlets. You can access the normal request parameters by getParameter() and you can access multiple request parameter values by getParameterValues().

On creation, the MultipartMap will put itself in the request scope, identified by the attribute name parts, so that you can access the parameters in EL by for example ${parts.fieldname} where you would have used ${param.fieldname}. In case of file fields, the ${parts.filefieldname} returns a File object.

It was a design decision to extend HashMap<String, Object> instead of having just Map<String, String[]> and Map<String, File> properties, because of the accessibility in Expression Language. Also, when the value is obtained by get(), as will happen in EL, then multiple parameter values will be converted from String[] to List<String>, so that you can use it in the JSTL fn:contains function.

/*
 * net/balusc/http/multipart/MultipartMap.java
 *
 * Copyright (C) 2009 BalusC
 *
 * This program is free software: you can redistribute it and/or modify it under the terms of the
 * GNU Lesser General Public License as published by the Free Software Foundation, either version 3
 * of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along with this library.
 * If not, see <http://www.gnu.org/licenses/>.
 */

package net.balusc.http.multipart;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import javax.servlet.MultipartConfigElement;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.Part;

/**
 * The MultipartMap. It simulates the <code>HttpServletRequest#getParameterXXX()</code> methods to
 * ease the processing in <code>@MultipartConfig</code> servlets. You can access the normal request
 * parameters by <code>{@link #getParameter(String)}</code> and you can access multiple request
 * parameter values by <code>{@link #getParameterValues(String)}</code>.
 * <p>
 * On creation, the <code>MultipartMap</code> will put itself in the request scope, identified by
 * the attribute name <code>parts</code>, so that you can access the parameters in EL by for example
 * <code>${parts.fieldname}</code> where you would have used <code>${param.fieldname}</code>. In
 * case of file fields, the <code>${parts.filefieldname}</code> returns a <code>{@link File}</code>.
 * <p>
 * It was a design decision to extend <code>HashMap&lt;String, Object&gt;</code> instead of having
 * just <code>Map&lt;String, String[]&gt;</code> and <code>Map&lt;String, File&gt;</code>
 * properties, because of the accessibility in Expression Language. Also, when the value is obtained
 * by <code>{@link #get(Object)}</code>, as will happen in EL, then multiple parameter values will
 * be converted from <code>String[]</code> to <code>List&lt;String&gt;</code>, so that you can use
 * it in the JSTL <code>fn:contains</code> function.
 *
 * @author BalusC
 * @link http://balusc.blogspot.com/2009/12/uploading-files-in-servlet-30.html
 */
public class MultipartMap extends HashMap<String, Object> {

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

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

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

    private String encoding;
    private String location;
    private boolean multipartConfigured;

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

    /**
     * Construct multipart map based on the given multipart request and the servlet associated with
     * the request. The file upload location will be extracted from <code>@MultipartConfig</code>
     * of the servlet. 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 servlet The servlet which is responsible for the given request.
     * @throws ServletException If something fails at Servlet level.
     * @throws IOException If something fails at I/O level.
     */
    public MultipartMap(HttpServletRequest multipartRequest, Servlet servlet)
        throws ServletException, IOException
    {
        this(multipartRequest, new MultipartConfigElement(
            servlet.getClass().getAnnotation(MultipartConfig.class)).getLocation(), true);
    }

    /**
     * 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.
     */
    public MultipartMap(HttpServletRequest multipartRequest, String location)
        throws ServletException, IOException
    {
        this(multipartRequest, location, false);
    }

    /**
     * Global constructor.
     */
    private MultipartMap
        (HttpServletRequest multipartRequest, String location, boolean multipartConfigured)
            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;
        this.multipartConfigured = multipartConfigured;

        for (Part part : multipartRequest.getParts()) {
            String filename = getFilename(part);
            if (filename == null) {
                processTextPart(part);
            } else if (!filename.isEmpty()) {
                processFilePart(part, filename);
            }
        }
    }

    // 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)
     */
    public String getParameter(String name) {
        Object value = super.get(name);
        if (value instanceof File) {
            return ((File) value).getName();
        }
        String[] values = (String[]) value;
        return values != null ? values[0] : null;
    }

    /**
     * @see ServletRequest#getParameterValues(String)
     */
    public String[] getParameterValues(String name) {
        Object value = super.get(name);
        if (value instanceof File) {
            return new String[] { ((File) value).getName() };
        }
        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 ------------------------------------------------------------------------------------

    /**
     * Returns the filename from the content-disposition header of the given part.
     */
    private String getFilename(Part part) {
        for (String cd : part.getHeader(CONTENT_DISPOSITION).split(";")) {
            if (cd.trim().startsWith(CONTENT_DISPOSITION_FILENAME)) {
                return cd.substring(cd.indexOf('=') + 1).trim().replace("\"", "");
            }
        }
        return null;
    }

    /**
     * Returns the text value of the given part.
     */
    private String getValue(Part part) throws IOException {
        BufferedReader reader = 
            new BufferedReader(new InputStreamReader(part.getInputStream(), encoding));
        StringBuilder value = new StringBuilder();
        char[] buffer = new char[DEFAULT_BUFFER_SIZE];
        for (int length = 0; (length = reader.read(buffer)) > 0;) {
            value.append(buffer, 0, length);
        }
        return value.toString();
    }

    /**
     * Process given part as Text part.
     */
    private void processTextPart(Part part) throws IOException {
        String name = part.getName();
        String[] values = (String[]) super.get(name);

        if (values == null) {
            // Not in parameter map yet, so add as new value.
            put(name, new String[] { getValue(part) });
        } 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] = getValue(part);
            put(name, newValues);
        }
    }

    /**
     * Process given part as File part which is to be saved in temp dir with the given filename.
     */
    private void processFilePart(Part part, String filename) throws IOException {
        // First fix stupid MSIE behaviour (it passes full client side path along filename).
        filename = filename
            .substring(filename.lastIndexOf('/') + 1)
            .substring(filename.lastIndexOf('\\') + 1);

        // Get filename prefix (actual name) and suffix (extension).
        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));
        if (multipartConfigured) {
            part.write(file.getName()); // Will be written to the very same File.
        } else {
            InputStream input = null;
            OutputStream output = null;
            try {
                input = new BufferedInputStream(part.getInputStream(), DEFAULT_BUFFER_SIZE);
                output = new BufferedOutputStream(new FileOutputStream(file), DEFAULT_BUFFER_SIZE);
                byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
                for (int length = 0; ((length = input.read(buffer)) > 0);) {
                    output.write(buffer, 0, length);
                }
            } finally {
                if (output != null) try { output.close(); } catch (IOException logOrIgnore) { /**/ }
                if (input != null) try { input.close(); } catch (IOException logOrIgnore) { /**/ }
            }
        }

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

}

It is necessary to know the file upload location in the MultipartMap as well, because we can then make use of File#createTempFile() to create files with an unique filename to avoid them being overwritten by another files with a (by coincidence) same name. Once you have the uploaded file at hands in the servlet or bean, you can always make use of File#renameTo() to do a fast rename/move.

Back to top

Basic use example

Here is a basic use example of a servlet and JSP file which demonstrates the working of the MultipartMap.

package net.balusc.example.upload;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;

import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.balusc.http.multipart.MultipartMap;

@WebServlet(urlPatterns = { "/upload" })
@MultipartConfig(location = "/upload", maxFileSize = 10485760L) // 10MB.
public class UploadServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException
    {
        request.getRequestDispatcher("/WEB-INF/upload.jsp").forward(request, response);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException
    {
        MultipartMap map = new MultipartMap(request, this);
        String text = map.getParameter("text");
        File file = map.getFile("file");
        String[] check = map.getParameterValues("check");

        // Now do your thing with the obtained input.
        System.out.println("Text: " + text);
        System.out.println("File: " + file);
        System.out.println("Check: " + Arrays.toString(check));

        request.getRequestDispatcher("/WEB-INF/upload.jsp").forward(request, response);
    }

}

That was the UploadServlet. Note the two annotations. The @WebServlet annotation definies under each the url-pattern, the URL pattern on which the servlet should listen. The @MultipartConfig annotation defines the location at the local disk file system where uploaded files are to be stored. In this case it is the /upload folder. In Windows environments with the application server running on the C:/ disk, this location effectively points to C:/upload. Ensure that you have created this folder beforehand!

Here's the JSP file, the /WEB-INF/upload.jsp:

<%@ page pageEncoding="UTF-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>

<!doctype html>
<html lang="en">
    <head>
        <title>Servlet 3.0 file upload test</title>
        <style>label { float: left; display: block; width: 75px; }</style>
    </head>
    <body>
        <form action="upload" method="post" enctype="multipart/form-data">
            <label for="text">Text:</label>
            <input type="text" id="text" name="text" value="${parts.text}">
            <br>
            <label for="file">File:</label>
            <input type="file" id="file" name="file">
            <c:if test="${not empty parts.file}">
                File ${parts.file.name} successfully uploaded!
            </c:if>
            <br>
            <label for="check1">Check 1:</label>
            <input type="checkbox" id="check1" name="check" value="check1"
                ${fn:contains(parts.check, 'check1') ? 'checked' : ''}>
            <br>
            <label for="check2">Check 2:</label>
            <input type="checkbox" id="check2" name="check" value="check2"
                ${fn:contains(parts.check, 'check2') ? 'checked' : ''}>
            <br>
            <input type="submit" value="submit">
        </form>
    </body>
</html>

Copy'n'paste the stuff and run it at http://localhost:8080/playground/upload (assuming that your local development server runs at port 8080 and that the context root of your playground web application project is called 'playground') and see it working! And no, you don't need to declare the servlet in web.xml, the servlets are automagically loaded and initialized with help of the new Servlet 3.0 annotations.

Note: this all is developed and tested with Eclipse 3.5 and Glassfish v3.

Back to top

More abstraction

As you might have noticed, the MultipartMap class here above has a second public constructor taking the file upload location as String parameter instead of the involved servlet. This is useful in circumstances where you'd like to abstract the entire HttpServletRequest, including the parameter map, away with help of a Filter and a HttpServletRequestWrapper. This way you can just access the request parameters the unchanged EL way by ${param}. This is also useful if you're running a MVC framework on top of the Servlet API which doesn't support the @MultipartConfig annotation, such as JSF 2.0 (here's an article about uploading files in JSF 2.0 + Servlet 3.0). The use of @MultipartConfig annotation is restricted to servlets only, so with a filter you need to specify the file upload location yourself, hence the second constructor of MultipartMap.

Here's the Filter which could be used to process multipart/form-data request transparently:

/*
 * net/balusc/http/multipart/MultipartFilter.java
 * 
 * Copyright (C) 2009 BalusC
 * 
 * This program is free software: you can redistribute it and/or modify it under the terms of the
 * GNU Lesser General Public License as published by the Free Software Foundation, either version 3
 * of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License along with this library.
 * If not, see <http://www.gnu.org/licenses/>.
 */

package net.balusc.http.multipart;

import java.io.IOException;

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.annotation.WebFilter;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.http.HttpServletRequest;

/**
 * This filter detects <tt>multipart/form-data</tt> and <tt>multipart/mixed</tt> POST requests and
 * will then replace the <code>HttpServletRequest</code> by a <code>{@link MultipartRequest}</code>.
 * 
 * @author BalusC
 * @link http://balusc.blogspot.com/2009/12/uploading-files-in-servlet-30.html
 */

@WebFilter(urlPatterns = { "/*" }, initParams = {
    @WebInitParam(name = "location", value = "/upload") })
public class MultipartFilter implements Filter {

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

    private static final String INIT_PARAM_LOCATION = "location";
    private static final String REQUEST_METHOD_POST = "POST";
    private static final String CONTENT_TYPE_MULTIPART = "multipart/";

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

    private String location;

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

    @Override
    public void init(FilterConfig config) throws ServletException {
        this.location = config.getInitParameter(INIT_PARAM_LOCATION);
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
        throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        if (isMultipartRequest(httpRequest)) {
            request = new MultipartRequest(httpRequest, location);
        }
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
        // NOOP.
    }

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

    /**
     * Returns true if the given request is a multipart request.
     * @param request The request to be checked.
     * @return True if the given request is a multipart request.
     */
    public static final boolean isMultipartRequest(HttpServletRequest request) {
        return REQUEST_METHOD_POST.equalsIgnoreCase(request.getMethod())
            && request.getContentType() != null
            && request.getContentType().toLowerCase().startsWith(CONTENT_TYPE_MULTIPART);
    }

}

It is true that the location property is a bit nonsensicial since it is already "hardcoded" by an annotation in the very same filter class. It is however overrideable by a real init param in web.xml!

And now the MultipartRequest which the filter needs to replace the request with:

/*
 * net/balusc/http/multipart/MultipartRequest.java
 * 
 * Copyright (C) 2009 BalusC
 * 
 * This program is free software: you can redistribute it and/or modify it under the terms of the
 * GNU Lesser General Public License as published by the Free Software Foundation, either version 3
 * of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License along with this library.
 * If not, see <http://www.gnu.org/licenses/>.
 */

package net.balusc.http.multipart;

import java.io.File;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.Part;

/**
 * This class represents a multipart request. It not only abstracts the <code>{@link Part}</code>
 * away, but it also provides direct access to the <code>{@link MultipartMap}</code>, so that one 
 * can get the uploaded files out of it.
 * 
 * @author BalusC
 * @link http://balusc.blogspot.com/2009/12/uploading-files-in-servlet-30.html
 */
public class MultipartRequest extends HttpServletRequestWrapper {

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

    private MultipartMap multipartMap;

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

    /**
     * Construct MultipartRequest based on the given HttpServletRequest.
     * @param request HttpServletRequest to be wrapped into a MultipartRequest.
     * @param location The location to save uploaded files in.
     * @throws IOException If something fails at I/O level.
     * @throws ServletException If something fails at Servlet level.
     */
    public MultipartRequest(HttpServletRequest request, String location)
        throws ServletException, IOException
    {
        super(request);
        this.multipartMap = new MultipartMap(request, location);
    }

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

    @Override
    public String getParameter(String name) {
        return multipartMap.getParameter(name);
    }

    @Override
    public String[] getParameterValues(String name) {
        return multipartMap.getParameterValues(name);
    }

    @Override
    public Enumeration<String> getParameterNames() {
        return multipartMap.getParameterNames();
    }

    @Override
    public Map<String, String[]> getParameterMap() {
        return multipartMap.getParameterMap();
    }

    /**
     * @see MultipartMap#getFile(String)
     */
    public File getFile(String name) {
        return multipartMap.getFile(name);
    }

}

That should be it. And no, also no web.xml modifications are needed here. The web.xml is pretty superflous with the new Servlet 3.0 annotations.

When the Filter is in use, then the first lines of UploadServlet#doPost() can now be changed as follows:


    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException
    {
        String text = request.getParameter("text");
        File file = ((MultipartRequest) request).getFile("file");
        String[] check = request.getParameterValues("check");
        ...
    }

This also implies that the @MultipartConfig annotation can be removed from the servlet. You only need to handle file size limits yourself, but that can now be done more nicely (it would by default abort the entire request and show a HTTP 500 error page otherwise, not very good for User eXperience). The ${parts} in the EL throughout the JSP file can also be changed back to the normal ${param}, including the ones for the uploaded files.

Back to top

Copyright - GNU Lesser General Public License

(C) December 2009, BalusC

Saturday, February 16, 2008

Uploading files with JSF

WARNING - OUTDATED CONTENT!

This article is targeted on JSF 1.2.

For JSF 2.0/2.1 with Tomahawk, please checkout my answer on this Stack Overflow question.

For JSF 2.0/2.1 on Servlet 3.0 with a custom component, please checkout this article.

For JSF 2.2, just use its native file upload component in flavor of <h:inputFile> whose value can be tied to a javax.servlet.http.Part property, see also my answer on this Stack Overflow question.

Upload and store files

Downloading files is made relatively easy using a FileServlet, but uploading files is a bit harder. Entering/selecting the raw absolute file path in h:inputText and sending it to the server so that it can be used in a File object isn't going to work, as the server doesn't have access to the client's file system. That will work only if the server as well as the client runs on the same machine and that wouldn't occur in real life.

To browse and select a file for upload you basically need a HTML input type="file" field in the form. As stated in the HTML specification you have to use the POST method and the enctype attribute of the form have to be set to "multipart/form-data". Unfortunately the Sun JSF Reference Implementation Mojarra doesn't provide a component out of the box which renders a input type="file" field. But the MyFaces Tomahawk component library, which can also be integrated in Sun JSF Reference Implementation Mojarra, provides us the t:inputFileUpload component.

Back to top

Integrating Tomahawk in Mojarra

Assuming that you already have a Mojarra environment, you just need to add at least the following JAR's to the classpath, e.g. /WEB-INF/lib. The version numbers doesn't matter that much, as long as you get the newest.

The Tomahawk JAR is the Tomahawk component library itself which under each contains the t:inputFileUpload component and the ExtensionsFilter. The commons-fileupload and commons-io JAR's are required for the file upload. They contains a multipart/form-data parser and several I/O utilities respectively. The commons-logging and commons-el JAR's are required by the core of the Tomahawk component library.

After adding the JAR's, open your web.xml and add the ExtensionsFilter to it. It should filter multipart/form-data requests and make use of the commons-fileupload to parse the request. To get uploading files work in JSF, it should at least be mapped on the servlet name of the FacesServlet, which may differ per environment. By default it is "Faces Servlet". Just check the servlet name in its <servlet> definition.

<filter>
    <filter-name>Extensions Filter</filter-name>
    <filter-class>org.apache.myfaces.webapp.filter.ExtensionsFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>Extensions Filter</filter-name>
    <servlet-name>Faces Servlet</servlet-name>
</filter-mapping>

It is not required, but you can configure the ExtensionsFilter with one or more of the following useful init-param settings which you can put in the <filter> tag:


    <init-param>
        <description>
            Set the size limit for uploaded files.
                Format: 10  - 10 bytes
                        10k - 10 KB
                        10m - 10 MB
                        1g  - 1 GB
        </description>
        <param-name>uploadMaxFileSize</param-name>
        <param-value>100m</param-value>
    </init-param>
    <init-param>
        <description>
            Set the threshold size - files below this limit are stored 
            in memory, files above this limit are stored on disk.
                Format: 10  - 10 bytes
                        10k - 10 KB
                        10m - 10 MB
                        1g  - 1 GB
        </description>
        <param-name>uploadThresholdSize</param-name>
        <param-value>100k</param-value>
    </init-param>
    <init-param>
        <description>
            Set the path where the intermediary files will be stored.
        </description>
        <param-name>uploadRepositoryPath</param-name>
        <param-value>/temp</param-value>
    </init-param>

Back to top

Basic use example

Here is a basic use example of a JSF file and the appropriate backing bean which demonstrates the working of the t:inputFileUpload.

<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://myfaces.apache.org/tomahawk" prefix="t" %>

<!DOCTYPE html>

<f:view>
    <html lang="en">
        <head>
            <title>File upload test</title>
        </head>
        <body>
            <h:form id="uploadForm" enctype="multipart/form-data">
                <h:panelGrid columns="3">
                    <h:outputLabel for="file" value="Select file" />
                    <t:inputFileUpload id="file" value="#{myBean.uploadedFile}" required="true" />
                    <h:message for="file" style="color: red;" />

                    <h:panelGroup />
                    <h:commandButton value="Submit" action="#{myBean.submit}" />
                    <h:message for="uploadForm" infoStyle="color: green;" errorStyle="color: red;" />
                </h:panelGrid>
            </h:form>

            <h:outputLink value="file/#{myBean.fileName}" rendered="#{myBean.fileName != null}">
                Download back
            </h:outputLink>
        </body>
    </html>
</f:view>

Please note the enctype of the h:form. This is required to be able to POST binary data. Also note that the download link makes use of the FileServlet. Make sure that it points to the same directory as where the file is uploaded. You can configure it as an init-param.

Here is how the appropriate backing bean (request scoped) look like:

package mypackage;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.myfaces.custom.fileupload.UploadedFile;

public class MyBean {

    // Init ---------------------------------------------------------------------------------------

    private UploadedFile uploadedFile;
    private String fileName;

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

    public void submit() {

        // Just to demonstrate what information you can get from the uploaded file.
        System.out.println("File type: " + uploadedFile.getContentType());
        System.out.println("File name: " + uploadedFile.getName());
        System.out.println("File size: " + uploadedFile.getSize() + " bytes");

        // Prepare filename prefix and suffix for an unique filename in upload folder.
        String prefix = FilenameUtils.getBaseName(uploadedFile.getName());
        String suffix = FilenameUtils.getExtension(uploadedFile.getName());
        
        // Prepare file and outputstream.
        File file = null;
        OutputStream output = null;
        
        try {
            // Create file with unique name in upload folder and write to it.
            file = File.createTempFile(prefix + "_", "." + suffix, new File("c:/upload"));
            output = new FileOutputStream(file);
            IOUtils.copy(uploadedFile.getInputStream(), output);
            fileName = file.getName();

            // Show succes message.
            FacesContext.getCurrentInstance().addMessage("uploadForm", new FacesMessage(
                FacesMessage.SEVERITY_INFO, "File upload succeed!", null));
        } catch (IOException e) {
            // Cleanup.
            if (file != null) file.delete();

            // Show error message.
            FacesContext.getCurrentInstance().addMessage("uploadForm", new FacesMessage(
                FacesMessage.SEVERITY_ERROR, "File upload failed with I/O error.", null));

            // Always log stacktraces (with a real logger).
            e.printStackTrace();
        } finally {
            IOUtils.closeQuietly(output);
        }
    }

    // Getters ------------------------------------------------------------------------------------

    public UploadedFile getUploadedFile() {
        return uploadedFile;
    }

    public String getFileName() {
        return fileName;
    }

    // Setters ------------------------------------------------------------------------------------

    public void setUploadedFile(UploadedFile uploadedFile) {
        this.uploadedFile = uploadedFile;
    }

}

Note: make "c:/upload" at least configureable. Maybe as a property in a propertiesfile which could also be shared by the FileServlet.

Back to top

Copyright - There is no copyright on the code. You can copy, change and distribute it freely. Just mentioning this site should be fair.

(C) February 2008, BalusC