14

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.

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
Ioannis Deligiannis
  • 2,679
  • 5
  • 25
  • 48
  • It seems to me that the `multiple` attribute & JSF implementation did not make it to the spec [spec](https://java.net/jira/browse/JAVASERVERFACES_SPEC_PUBLIC-802) 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 Jun 03 '13 at 14:17

4 Answers4

14

This is not natively supported by <h:inputFile> until Faces version 4.0. It's introduced in Faces 4.0 as per spec issue 1555 (by yours truly):

<html ... xmlns:h="jakarta.faces.html">
...
<h:form enctype="multipart/form-data">
    <h:inputFile value="#{bean.files}" multiple="true" />
    <h:commandButton value="submit" action="#{bean.submit}" />
</h:form>
private List<Part> files;

public void submit() {
    for (Part file : files) {
        String name = Paths.get(part.getSubmittedFileName()).getFileName().toString();
        long size = part.getSize();
        // ...
    }
}

In case you're not on Faces 4.0 yet, then there are 2 alternative options:

  1. Set the multiple attribute as a passthrough attributes (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.

  2. Or, use the JSF utility library OmniFaces. Since OmniFaces 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
    

    This component was the base for the new Faces 4.0 feature.

See also:

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
2

Since the question was asked a very long time ago, I would like to give an update here. If you are working with the new Jakarta EE Faces 4.0 specification it becomes quite simple to support multiple file upload:

As already mentioned the h:from has to be extended with the enctype "multipart/form-data". And the h:inputFile needs the passthrough attribute multiple=true:

<ui:composition template="/WEB-INF/templates/layout.xhtml"
    xmlns:faces="jakarta.faces" xmlns:f="jakarta.faces.core"
    xmlns:h="jakarta.faces.html" xmlns:ui="jakarta.faces.facelets"
    xmlns:pt="jakarta.faces.passthrough">
    <f:view>
      <h:form id="models_form_id" enctype="multipart/form-data">
        .....
        <h:inputFile id="file" value="#{myBean.files}" pt:multiple="true"/>
        ....
        <h:commandButton id="submit" value="Submit" action="#{myBean.submit}" />
      </h:form>
    </f:view>
</ui:composition>

Your bean code just need to support the 'files' property as a List of jakarta.servlet.http.Part elements:

@Named
@RequestScoped
public class MyBean implements Serializable {
    private List<Part> files;
    public List<Part> getFiles() {
        return files;
    }
    public void setFiles(List<Part> files) {
        this.files = files;
    }

    public void submit() throws IOException {
        if (files != null) {
            System.out.println(" uploading " + files.size() + " files");
            for (Part file : files) {
                System.out.println("name: " + file.getSubmittedFileName());
                System.out.println("type: " + file.getContentType());
                System.out.println("size: " + file.getSize());
                InputStream content = file.getInputStream();
                // Write content to disk or DB.
            }
        }
    }
}
  ....

That's it. Now you can process uploaded files as any other data in your form.

Ralph
  • 4,500
  • 9
  • 48
  • 87
1

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>
Adam
  • 1,926
  • 23
  • 21
blitzen12
  • 1,350
  • 3
  • 24
  • 33
0

Even it's quite some time ago: Considering your own comment I would recommend a component like PrimeFaces fileUploadMultiple, mentioning not to forget the needed changes in web.xml and all needed libs for uploading. See it as a workaround or complete solution, based on your needs. PrimeFaces is a quite nice component-lib

JavaKaffee
  • 67
  • 2
  • 8
  • 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. – Ioannis Deligiannis Aug 16 '13 at 13:15