Providing file download in JSF like download.jsf?id=123 - jsf

I'm trying to provide download links (that should also work for images in an img tag) in the style of
http://www.domain.example/download.jsf?id=123
and my solution so far looks like this: (copied from here: How to provide a file download from a JSF backing bean? )
GetFile.java
#ManagedBean
#ViewScoped
public class GetFile{
// property and getter
public void setId(String id) {
FacesContext fc = FacesContext.getCurrentInstance();
ExternalContext ec = fc.getExternalContext();
ec.responseReset();
ec.setResponseContentType(contentType);
ec.setResponseContentLength(contentLength);
ec.setResponseHeader("Content-Disposition", "attachment; filename=\"" + filename + "\"");
try (OutputStream output = ec.getResponseOutputStream()) {
// Now you can write the InputStream of the file to the above
// OutputStream the usual way.
// ...
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
fc.responseComplete();
}
and my download.xhtml looks like this:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core">
<f:metadata>
<f:viewParam name="id" value="#{getFile.id}" />
</f:metadata>
</html>
That works so far. But when I want to add additional parameters like
http://www.domain.example/download.jsf?id=123&format=txt
then I would have to consider the order in which the parameters are set and do this in the last setter. That would work but I don't find that a very pretty solution, so my question is, is there a better way to achieve this?
Any hints are very appreciated!

Initiating the download in a setter is in general not a very good design and should only do its main purpose, to set a variable (and maybe some checking/conversion/etc related to setting).
To start the download you should create a new method in your backing bean that handles the download and use that method. So your bean should look something like this:
GetFile.java
#ManagedBean
#ViewScoped
public class GetFile {
// properties, getter and setter
public void download() {
// your download code goes here
}
}
This way you can use as many parameters as you want and pass them the way you did. In your download.xhtml you can call your bean method after passing the arguments like this:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core">
<f:metadata>
<f:viewParam name="id" value="#{getFile.id}" />
<!-- your other parameters come here -->
</f:metadata>
#{getFile.download()}
</html>

Related

Can you set an Ajax response action to a PrimeFace component which's html is generated in a Bean?

Dear friendly strangers,
using PrimeFaces 7.0 on JSF 2.2 I'm generating html-Code in my Bean and inject it in my xhtml with <h:outputText value="#{myBean.myHtml}" escape="false"/>. This naturally doesn't work with <p:.../> components, as they themselves generate/render actual html. The way I alter the data from my Database to get the final html is too complicated for html functions though, so I still wanna do it in my Java-Beans instead of using lots of ui:repeat and hypercomplex custom styles - even though I know this is not how jsf/PrimeFaces is meant to be used. Now checking the actual rendered html e.g. of a p:commandLink it gives
<a id="myContainerID:myComponentID" href="#" class="ui-commandlink ui-widget" onclick="PrimeFaces.ab({s:"myContainerID:myComponentID",f:"myContainerID"});return false;">myComponentValue</a>
,which I can generate easily, but the response-action called when receiving the component's Ajax request (s:"myContainerID:myComponentID") will be missing, which seems to be saved somewhere in the moment the actual html is generated with <p:...>.
Is there a way to manually set that response-action, if so how/where?
EDIT: Since (quoting PrimeFaces.ab function)
//ajax shortcut
ab: function(cfg, ext) {
return PrimeFaces.ajax.AjaxRequest(cfg, ext);
}
The PrimeFaces.ajax.AjaxRequest can be asynchronous or synchronous. The AjaxRequest uses the AjaxUtils, which handles all
send, process, response, and update.
PrimeFaces.ajax.AjaxRequest = function(cfg, ext) {
cfg.ext = ext;
if(cfg.async) {
return PrimeFaces.ajax.AjaxUtils.send(cfg);
}
else {
return PrimeFaces.ajax.Queue.offer(cfg);
} }
I suppose the answer, if there is any, should lay somewhere in AjaxUtils, but couldn't find it yet.
Thanks helluvalot for any suggestion/help.
EDIT 2: I did eventually manage to transcribe it all to the xhtml with nested ui:repeats and lots of custom styles, I'm still curious though whether there's a way to do it with in-Bean-generated html.
ExampleCode
myBean:
#ManagedBean(name = "myBean")
#SessionScoped
public class myBean {
private String html1;
private String html2;
#PostConstruct
public void init() {
html1 = "<p:commandLink id=\"myComponentID\" value=\"myComponentValue\" "
+ "action=\"#{someBean.doSomething()}\"";
html2 = "<a id=\"myContainerID:myComponentID\" "
+ "href=\"#\" class=\"ui-commandlink ui-widget\" "
+ "onclick=\"PrimeFaces.ab({s:\"myContainerID:myComponentID\","
+ "f:\"myContainerID\"});"
+ "return false;\">1. myComponentValue</a>";
}
public String getHtml1() {
return html1;
}
public void setHtml1(String html1) {
this.html1 = html1;
}
public String getHtml2() {
return html2;
}
public void setHtml2(String html2) {
this.html2 = html2;
}
}
myIndex.xhtml:
<h:html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:p="http://primefaces.org/ui"
>
<h:head>
</h:head>
<h:body>
<h:form id="myContainerID">
<h:outputText value="#{myBean.html1}" escape="false" />
<h:outputText value="#{myBean.html2}" escape="false" />
</h:form>
</h:body>
</h:html>

Script is not rendered after postback in a composite component added programmatically

I have this composite component:
inputMask.xhtml
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:composite="http://xmlns.jcp.org/jsf/composite"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core">
<composite:interface>
<composite:attribute name="value" />
<composite:attribute name="mask" type="java.lang.String" required="true" />
<composite:attribute name="converterId" type="java.lang.String" default="br.edu.ufca.eventos.visao.inputmask.inputMask" />
</composite:interface>
<composite:implementation>
<h:outputScript library="script" name="inputmask.js" target="head" />
<h:inputText id="mascara">
<c:if test="#{cc.getValueExpression('value') != null}">
<f:attribute name="value" value="#{cc.attrs.value}" />
</c:if>
<f:converter converterId="#{cc.attrs.converterId}" />
<f:attribute name="mask" value="#{cc.attrs.mask}" />
</h:inputText>
<h:outputScript target="body">
defineMask("#{cc.clientId}", "#{cc.attrs.mask}");
</h:outputScript>
</composite:implementation>
</html>
In my last question:
Error trying to add composite component programmatically ("no tag was defined for name")
I was getting this error:
javax.faces.view.facelets.TagException: //C:/wildfly-10/standalone/tmp/eventos.ear.visao.war/mojarra7308315477323852505.tmp #2,127 <j:inputMask.xhtml> Tag Library supports namespace: http://xmlns.jcp.org/jsf/composite/componente, but no tag was defined for name: inputMask.xhtml
when trying to add the above composite component programmatically with this code:
Map<String, String> attributes = new HashMap<>();
attributes.put("mask", "999.999");
Components.includeCompositeComponent(Components.getCurrentForm(), "componente", "inputMask.xhtml", "a123", attributes);
but I managed to solve this problem this way:
The implementation of the method Components#includeCompositeComponent from OmniFaces 2.4 (the version I was using) is this:
public static UIComponent includeCompositeComponent(UIComponent parent, String libraryName, String tagName, String id, Map<String, String> attributes) {
String taglibURI = "http://xmlns.jcp.org/jsf/composite/" + libraryName;
Map<String, Object> attrs = (attributes == null) ? null : new HashMap<String, Object>(attributes);
FacesContext context = FacesContext.getCurrentInstance();
UIComponent composite = context.getApplication().getViewHandler()
.getViewDeclarationLanguage(context, context.getViewRoot().getViewId())
.createComponent(context, taglibURI, tagName, attrs);
composite.setId(id);
parent.getChildren().add(composite);
return composite;
}
So I decided to give a try to the code from an earlier version of OmniFaces (with some change adding the attributes parameter from me) of this method:
public static UIComponent includeCompositeComponent(UIComponent parent, String libraryName, String resourceName, String id, Map<String, String> attributes) {
// Prepare.
FacesContext context = FacesContext.getCurrentInstance();
Application application = context.getApplication();
FaceletContext faceletContext = (FaceletContext) context.getAttributes().get(FaceletContext.FACELET_CONTEXT_KEY);
// This basically creates <ui:component> based on <composite:interface>.
Resource resource = application.getResourceHandler().createResource(resourceName, libraryName);
UIComponent composite = application.createComponent(context, resource);
composite.setId(id); // Mandatory for the case composite is part of UIForm! Otherwise JSF can't find inputs.
// This basically creates <composite:implementation>.
UIComponent implementation = application.createComponent(UIPanel.COMPONENT_TYPE);
implementation.setRendererType("javax.faces.Group");
composite.getFacets().put(UIComponent.COMPOSITE_FACET_NAME, implementation);
if (!attributes.isEmpty()) {
ExpressionFactory factory = application.getExpressionFactory();
ELContext ctx = context.getELContext();
for (Map.Entry<String, String> entry : attributes.entrySet()) {
ValueExpression expr = factory.createValueExpression(ctx, entry.getValue(), Object.class);
composite.setValueExpression(entry.getKey(), expr);
}
}
// Now include the composite component file in the given parent.
parent.getChildren().add(composite);
parent.pushComponentToEL(context, composite); // This makes #{cc} available.
try {
faceletContext.includeFacelet(implementation, resource.getURL());
} catch (IOException e) {
throw new FacesException(e);
} finally {
parent.popComponentFromEL(context);
}
return composite;
}
And finally the error was gone. The composite component was dynamically added to the page.
But another problem appeared.
The action in a button to add the component is more or less like this:
if (Components.findComponent("form:a123") == null)
{
Map<String, String> attributes = new HashMap<>();
attributes.put("value", "#{bean.cpf}");
attributes.put("mask", "999.999.999-99");
includeCompositeComponent(Components.getCurrentForm(), "componente", "inputMask.xhtml", "a123", attributes);
}
As you can see, the composite component is only added once.
When the component is first added, the script code that is in the component:
<h:outputScript target="body">
defineMask("#{cc.clientId}", "#{cc.attrs.mask}");
</h:outputScript>
is added to the page. I can see it when I visualize the html source code in the browser. But on postbacks, this script code is not rendered anymore. It's not in the genereted html page. The <h:outputScript> with target="head" is rendered everytime, as expected, but not this one.
From my point of view, maybe there's still someting missing in the assembling of the composite component code in the method above to fix the script code even on postbacks on the page. I really don't know. It's just a guess.
Do you know what's going on or what's missing?
---- UPDATE 1 ----
I think that I really found the source of the problem. It seems that it's a bug in JSF related with scripts in composite components included programatically.
Here's what I found:
I noticed that the correct code from OmniFaces to include my composite component is this:
Components.includeCompositeComponent(Components.getCurrentForm(), "componente", "inputMask", "a123", attributes);
The correct is "inputMask", not "inputMask.xhtml". But as I told you before, when I use this code I get this error instead:
Caused by: javax.faces.FacesException: Cannot remove the same component twice: form:a123:j_idt2
So I suspected that the component with the id form:a123:j_idt2 was one of the h:outputScript present in the composite component. So I changed the composite component code to this:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:composite="http://xmlns.jcp.org/jsf/composite"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core">
<composite:interface componentType="inputMask">
<composite:attribute name="value" />
<composite:attribute name="mask" type="java.lang.String" required="true" />
<composite:attribute name="converterId" type="java.lang.String" default="br.edu.ufca.eventos.visao.inputmask.inputMask" />
</composite:interface>
<composite:implementation>
<h:inputText id="mascara">
<c:if test="#{cc.getValueExpression('value') != null}">
<f:attribute name="value" value="#{cc.attrs.value}" />
</c:if>
<f:converter converterId="#{cc.attrs.converterId}" />
<f:attribute name="mask" value="#{cc.attrs.mask}" />
</h:inputText>
<script type="text/javascript">
defineMask("#{cc.clientId}", "#{cc.attrs.mask}");
</script>
</composite:implementation>
</html>
Removing all references to the h:outputScript tag. (Of course, I placed the inputmask.js script outside the composite component for the component to continue to work).
And now when I run the code, the component is finally added to the page without errors. But, as I said before with the code from an earlier version of OmniFaces, the script is still not rendered in postbacks. JSF only renders it when the component is added, loosing it on postbacks. I know this is not an expected behaviour.
So, I ask you: do you know how I can solve this script problem? Or at least any workaround I can use in this case?
Thank you in advance.
---- UPDATE 2 ----
I found a workaround for it. I did this in a backing component for the composite component and it worked, the script is always rendered:
#Override
public void encodeEnd(FacesContext context) throws IOException
{
super.encodeEnd(context);
ResponseWriter writer = context.getResponseWriter();
writer.startElement("script", this);
writer.writeText(String.format("defineMask('%s', '%s');",
getClientId(), getAttributes().get("mask")), null);
writer.endElement("script");
}
but it's kind of ugly and seems unnecessary. Again, if the component is not included programmatically, I don't need the backing component. It seems like a bug in JSF. Could some of you test and confirm this? I mean, test if a composite component with script in it added programmatically loses its script on postback.
P.S.: I'm using OmniFaces 2.4 and Mojarra 2.2.13.
The solution (workaround) is to remove all script from the composite component and create a backing component for it to do precisely what JSF was supposed to do:
package br.edu.company.project.view.inputmask;
import java.io.IOException;
import java.util.Map;
import javax.faces.component.FacesComponent;
import javax.faces.component.NamingContainer;
import javax.faces.component.UIInput;
import javax.faces.component.UINamingContainer;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import org.omnifaces.util.FacesLocal;
#FacesComponent("inputMask")
public class InputMask extends UIInput implements NamingContainer
{
private static final String SCRIPT_FILE_WRITTEN =
"br.edu.company.project.SCRIPT_FILE_WRITTEN";
#Override
public String getFamily()
{
return UINamingContainer.COMPONENT_FAMILY;
}
#Override
public void encodeBegin(FacesContext context) throws IOException
{
writeScriptFileIfNotWrittenYet(context);
super.encodeBegin(context);
}
#Override
public void encodeEnd(FacesContext context) throws IOException
{
super.encodeEnd(context);
writeMaskDefinition(context);
}
private void writeScriptFileIfNotWrittenYet(FacesContext context) throws IOException
{
if (FacesLocal.getRequestMap(context).putIfAbsent(
SCRIPT_FILE_WRITTEN, true) == null)
{
writeScript(context, w -> w.writeAttribute(
"src", "resources/script/inputmask.js", null));
}
}
private void writeMaskDefinition(FacesContext context) throws IOException
{
writeScript(context, w -> w.writeText(String.format(
"defineMask('%s', '%s');", getClientId(),
getAttributes().get("mask")), null));
}
private void writeScript(FacesContext context, WriteAction writeAction)
throws IOException
{
ResponseWriter writer = context.getResponseWriter();
writer.startElement("script", this);
writer.writeAttribute("type", "text/javascript", null);
writeAction.execute(writer);
writer.endElement("script");
}
#FunctionalInterface
private static interface WriteAction
{
void execute(ResponseWriter writer) throws IOException;
}
}
Again, you don't need this if your composite component won't be included programmatically. In this case, JSF works as expected and you don't need the backing component.
If someone have the time, I think it would be nice to file a bug report to the Mojarra team.

How to stream a file download and display a JSF faces message?

We are streaming a binary file to our users, following the procedure elaborated in the SO question How to provide a file download from a JSF backing bean?
In general the workflow works as intended, but during the generation of the export file recoverable errors may occur and we want to display these as a warning to the user. The file itself shall still be generated in that case. So we want that export to continue and display faces messages.
Just to put emphasis on this: Yes, there is something not OK with the data, but our users want the export to continue and receive that flawed file anyway. Then they want to have a look at the file, contact their vendor and send him a message about the flaw.
So I need the export to finish in any case.
But it does not work out as we want it to. I have created a simplified example to illustrate our approach.
As alternative we are considering a Bean that will be hold the messages and display them after the export. But probably there is a way with JSF built-in mechanisms to achieve this.
Controller
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.OutputStream;
import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import org.apache.tomcat.util.http.fileupload.util.Streams;
#ManagedBean
#RequestScoped
public class ExportController {
public void export() {
FacesContext fc = FacesContext.getCurrentInstance();
ExternalContext ec = fc.getExternalContext();
byte[] exportContent = "Hy Buddys, thanks for the help!".getBytes();
// here something bad happens that the user should know about
// but this message does not go out to the user
fc.addMessage(null, new FacesMessage("record 2 was flawed"));
ec.responseReset();
ec.setResponseContentType("text/plain");
ec.setResponseContentLength(exportContent.length);
String attachmentName = "attachment; filename=\"export.txt\"";
ec.setResponseHeader("Content-Disposition", attachmentName);
try {
OutputStream output = ec.getResponseOutputStream();
Streams.copy(new ByteArrayInputStream(exportContent), output, false);
} catch (IOException ex) {
ex.printStackTrace();
}
fc.responseComplete();
}
}
JSF Page
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:p="http://primefaces.org/ui">
<f:view contentType="text/html">
<h:body>
<h:form prependId="false">
<h:messages id="messages" />
<h:commandButton id="download" value="Download"
actionListener="#{exportController.export()}" />
</h:form>
</h:body>
</f:view>
</html>
Since you're actually performing a file download response and not a JSF one, it's not possible for your message to be added while the same request happens. The most clean solution for me, avoiding hacky asynchronous requests is to use a #ViewScoped bean and do your task in two steps. So, to have a button for preparing your file, notifying the user later on and allowing him to download it when it's ready:
#ManagedBean
#ViewScoped
public class ExportController implements Serializable {
private byte[] exportContent;
public boolean isReady() {
return exportContent != null;
}
public void export() {
FacesContext fc = FacesContext.getCurrentInstance();
ExternalContext ec = fc.getExternalContext();
ec.responseReset();
ec.setResponseContentType("text/plain");
ec.setResponseContentLength(exportContent.length);
String attachmentName = "attachment; filename=\"export.txt\"";
ec.setResponseHeader("Content-Disposition", attachmentName);
try {
OutputStream output = ec.getResponseOutputStream();
Streams.copy(new ByteArrayInputStream(exportContent), output, false);
} catch (IOException ex) {
ex.printStackTrace();
}
fc.responseComplete();
}
public void prepareFile() {
exportContent = "Hy Buddys, thanks for the help!".getBytes();
// here something bad happens that the user should know about
FacesContext.getCurrentInstance().addMessage(null,
new FacesMessage("record 2 was flawed"));
}
}
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:p="http://primefaces.org/ui">
<f:view contentType="text/html">
<h:body>
<h:form>
<h:messages id="messages" />
<h:commandButton value="Prepare"
action="#{exportController.prepareFile}" />
<h:commandButton id="download" value="Download"
disabled="#{not exportController.ready}"
action="#{exportController.export()}" />
</h:form>
</h:body>
</f:view>
</html>
Note this solution could be valid for small files (their entire content is stored in memory while user keeps in the same view). However, if you're going to use it with large files (or large number of users) your best is to store its content in a temporary file and display a link to it instead of a download button. That's what #BalusC suggests in the reference below.
See also:
File download from JSF with a rendered response
You may try this :
For primefaces, you can use remote command instead of command link, and call it with its name onsuccess. Otherwise give a widget var to the commandlink and call its click method.

#PostConstruct method is not called

I wanted to use #PostConstruct to initialize a bean in my webapp but I can't get it to work.
I've recreated the problem in a new project and it still won't work.
Am I missing something obvious here? As far as I can tell my init() method fulfills all the requirements listed in #PostConstruct API reference.
MyBean.java:
#ManagedBean
#RequestScoped
public class MyBean {
#ManagedProperty(value="15")
private int number = 10;
#PostConstruct
public void init(){
number = 20;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
}
number.xhtml:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html">
<h:head>
<title>Simple JSF Facelets page</title>
</h:head>
<h:body>
Number = #{myBean.number}
</h:body>
</html>
I would expect Number = 20 but I get Number = 15.
#PostConstruct seems to be called before the injection with #ManagedProperty, assuming you have MyFaces 2.0, as they say here.
Make sure you are using Mojarra 2.1 because it should work.
You might try to debug to know if your init() method is called before the injection, or never called.
By default, Spring will not aware of the #PostConstruct and #PreDestroy annotation. To enable it, you have to either register CommonAnnotationBeanPostProcessor or specify the <context:annotation-config /> in bean configuration file.

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.
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:
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.
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?
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.
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>
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

Resources