How can I upload multiple files with JSF 2.2

Ioannis Deligiannis picture Ioannis Deligiannis · May 30, 2013 · Viewed 11.5k times · Source

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.

Answer

BalusC picture BalusC · Apr 8, 2016

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