Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I upload multiple files with JSF 2.2

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.

like image 347
Ioannis Deligiannis Avatar asked May 30 '13 13:05

Ioannis Deligiannis


People also ask

How can I upload multiple files using asp net FileUpload control?

ASP.NET FileUpload control provides AllowMultiple property to upload multiple files to the server. This property takes either true or false value. The <asp:FileUpload> tag is used to create a browse button that allows us to upload file.


3 Answers

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:

  • What's new in Faces 4.0?
like image 53
BalusC Avatar answered Nov 09 '22 08:11

BalusC


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>
like image 41
blitzen12 Avatar answered Nov 09 '22 09:11

blitzen12


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.

like image 1
Ralph Avatar answered Nov 09 '22 08:11

Ralph