How can I upload multiple files with JSF 2.2

12,449

Solution 1

How can I upload multiple files with JSF 2.2

You can indeed achieve this with another JSF 2.2 feature: passthrough attributes. Set the multiple attribute as a passthrough attribute (browser support is currently quite broad).

<html ... xmlns:a="http://xmlns.jcp.org/jsf/passthrough">
...
<h:inputFile ... a:multiple="true" />

However, the <h:inputFile> component itself doesn't support grabbing multiple Parts from the request and setting it as an array or Collection bean property. It would only set the last part matching the input field name. Basically, to support multiple parts, a custom renderer needs to be created (and you should immediately take the opportunity to just support multiple attribute right away without resorting to passthrough attributes).

For the sake of having a "workaround" without creating a whole renderer, you could however manually grab all the parts via HttpServletRequest with help of below little utility method:

public static Collection<Part> getAllParts(Part part) throws ServletException, IOException {
    HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
    return request.getParts().stream().filter(p -> part.getName().equals(p.getName())).collect(Collectors.toList());
}

So, the below construct should work with above utility method:

<h:inputFile value="#{bean.part}" a:multiple="true" />
<h:commandButton ... action="#{bean.submit}" />

private Part file;

public void submit() throws ServletException, IOException {
    for (Part part : getAllParts(file)) {
        String fileName = part.getSubmittedFileName();
        InputStream fileContent = part.getInputStream();
        // ... 
        // Do your thing with it.
        // E.g. https://stackoverflow.com/q/14211843/157882
    }
}

public Part getFile() {
    return null; // Important!
}

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

Do note that the getter can for safety and clarity better always return null. Actually, the entire getter method should have been unnecessary, but it is what it is.

On the more modern browsers you can even select whole folders. This only requires a yet newer directory attribute. This is supported since Firefox 46 (already since 42, but needs to be explicitly enabled in about:config). Webkit based browsers (Chrome 11+, Safari 4+ and Edge) support this via the proprietary webkitdirectory attribute. So if you specify both attributes, you're generally safe.

<h:inputFile ... a:multiple="true" a:directory="true" a:webkitdirectory="true" />

Do note that this does not send physical folders, but only files contained in those folders.


Update: if you happen to use JSF utility library OmniFaces, since version 2.5 the <o:inputFile> is offered which should make multiple and directory selection less tedious.

<o:inputFile value="#{bean.files}" multiple="true" />

<o:inputFile value="#{bean.files}" directory="true" />

The value can be bound to a List<Part>.

private List<Part> files; // +getter+setter

Solution 2

I think it is possible to use multiple file upload using the standard JSF 2.2 using the passthrough tag.

Step 1:

<html xmlns="http://www.w3.org/1999/xhtml"
  xmlns:h="http://xmlns.jcp.org/jsf/html"
  xmlns:pt="http://xmlns.jcp.org/jsf/passthrough">
...

<h:form id="form" enctype="multipart/form-data">
    <h:inputFile id="file" value="#{fileUploadBean.uploadedFile}" pt:multiple="multiple" />
    ...

Step 2:

The JSF renderer class FileRenderer for the javax.faces.File type of the javax.faces.Input family of components doesn't handle this case correctly.

Instead, as it iterates through the parts of the form, it just overwrites the preceding part with each file in the uploaded collection.

I think a better strategy is to always have the value of the component be a List<Part> instead of just Part as suggested here and implemented here.

Step 3:

The last thing to make it work is to configure such modified multiple file renderer class in faces-config.xml adding the following to the <faces-config> root element:

<render-kit>
    <renderer>
        <description>Multiple File Renderer</description>
        <component-family>javax.faces.Input</component-family>
        <renderer-type>javax.faces.File</renderer-type>
        <renderer-class>com.example.MultipleFileRenderer</renderer-class>
    </renderer>
</render-kit>
Share:
12,449

Related videos on Youtube

Ioannis Deligiannis
Author by

Ioannis Deligiannis

Updated on June 04, 2022

Comments

  • Ioannis Deligiannis
    Ioannis Deligiannis over 1 year

    I am trying to add a multiple file upload using h:inputFile. I had a quick look through the source code and it appears that it does not have the option to render multiple="multiple". Is there a way around this without writing a custom component? If not, is there a suggested custom JSF2.2 component available that can handle multiple Ajax file uploads?

    Update: I have passed the multiple="multiple" using passthrough tag, but when I debugged the FileRenderer the relevant piece of code overwrites the first file with the second:

    for (Part cur : parts) {
      if (clientId.equals(cur.getName())) {
        component.setTransient(true);
        setSubmittedValue(component, cur);
      }
    }
    

    As you can see, since there are two Parts with the same clientId, it always use the last instead of passing a list.

    Please recommend an alternative if there is one.

    • Ioannis Deligiannis
      Ioannis Deligiannis over 10 years
      It seems to me that the multiple attribute & JSF implementation did not make it to the spec spec which seems very odd to me. As I can't find a logical reason for ommitting it, does anyone know why? For now I will implement a custom component using one of the existing JS/JQuery fileUpload libs or maybe go for a JSF file upload like tomahawk or prime/richfaces lib.
  • Ioannis Deligiannis
    Ioannis Deligiannis over 10 years
    Thank you for your reply. I am using jsf-2.2 which has quite a few issues already, so I am reluctant to use 3rd party tools. I have extended JSF though to support multiple uploads and works just fine.
  • Matt Vegas
    Matt Vegas almost 5 years
    it's working with 3.1.0 version mvnrepository.com/artifact/javax.servlet/javax.servlet-api/… and jdk 1.8
  • Matt Vegas
    Matt Vegas almost 5 years
    the file is always the same when is doing the for cycle, do you know why?