I need to display images which reside outside of deploy folder in web application using JSF <h:graphicimage> tag or HTML <img> tag. How can I achieve that?
To the point, it has to be accessible by a public URL. Thus, the <img src> must ultimately refer a http:// URI, not something like a file:// URI or so. Ultimately, the HTML source is executed at enduser's machine and images are downloaded individually by the webbrowser during parsing the HTML source. When the webbrowser encounters a file:// URI such as C:\path\to\image.png, then it will look in enduser's own local disk file system for the image instead of the webserver's one. This is obviously not going to work if the webbrowser runs at a physically different machine than the webserver.
There are several ways to achieve this:
If you have full control over the images folder, then just drop the folder with all images, e.g. /images directly in servletcontainer's deploy folder, such as the /webapps folder in case of Tomcat and /domains/domain1/applications folder in case of GlassFish. No further configuration is necessary.
Or, add a new webapp context to the server which points to the absolute disk file system location of the folder with those images. How to do that depends on the container used. The below examples assume that images are located in /path/to/images and that you'd like to access them via http://.../images.
In case of Tomcat, add the following new entry to Tomcat's /conf/server.xml inside <Host>:
<Context docBase="/path/to/images" path="/images" />
In case of GlassFish, add the following entry to /WEB-INF/glassfish-web.xml:
<property name="alternatedocroot_1" value="from=/images/* dir=/path/to" />
In case of WildFly, add the following entry inside <host name="default-host"> of /standalone/configuration/standalone.xml ...
<location name="/images" handler="images-content" />
... and further down in <handlers> entry of the very same <subsystem> as above <location>:
<file name="images-content" path="/path/to/images" />
Or, create a Servlet which streams the image from disk to response:
#WebServlet("/images/*")
public class ImageServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String filename = request.getPathInfo().substring(1);
File file = new File("/path/to/images", filename);
response.setHeader("Content-Type", getServletContext().getMimeType(filename));
response.setHeader("Content-Length", String.valueOf(file.length()));
response.setHeader("Content-Disposition", "inline; filename=\"" + filename + "\"");
Files.copy(file.toPath(), response.getOutputStream());
}
}
If you happen to use OmniFaces, then the FileServlet may be useful as it also takes into account head, caching and range requests.
Or, use OmniFaces <o:graphicImage> which supports a bean property returning byte[] or InputStream:
#Named
#ApplicationScoped
public class Bean {
public InputStream getImage(String filename) {
return new FileInputStream(new File("/path/to/images", filename));
}
}
Or, use PrimeFaces <p:graphicImage> which supports a bean method returning PrimeFaces-specific StreamedContent.
#Named
#ApplicationScoped
public class Bean {
public StreamedContent getImage() throws IOException {
FacesContext context = FacesContext.getCurrentInstance();
if (context.getCurrentPhaseId() == PhaseId.RENDER_RESPONSE) {
// So, we're rendering the view. Return a stub StreamedContent so that it will generate right URL.
return new DefaultStreamedContent();
}
else {
// So, browser is requesting the image. Return a real StreamedContent with the image bytes.
String filename = context.getExternalContext().getRequestParameterMap().get("filename");
return new DefaultStreamedContent(new FileInputStream(new File("/path/to/images", filename)));
}
}
}
For the first way and the Tomcat and WildFly approaches in second way, the images will be available by http://example.com/images/filename.ext and thus referencable in plain HTML as follows
<img src="/images/filename.ext" />
For the GlassFish approach in second way and the third way, the images will be available by http://example.com/context/images/filename.ext and thus referencable in plain HTML as follows
<img src="#{request.contextPath}/images/filename.ext" />
or in JSF as follows (context path is automatically prepended)
<h:graphicImage value="/images/filename.ext" />
For the OmniFaces approach in fourth way, reference it as follows
<o:graphicImage value="#{bean.getImage('filename.ext')}" />
For the PrimeFaces approach in fifth way, reference it as follows:
<p:graphicImage value="#{bean.image}">
<f:param name="filename" value="filename.ext" />
</p:graphicImage>
Note that the example #{bean} is #ApplicationScoped as it basically represents a stateless service. You can also make it #RequestScoped, but then the bean would be recreated on every single request, for nothing. You cannot make it #ViewScoped, because at the moment the browser needs to download the image, the server doesn't create a JSF page. You can make it #SessionScoped, but then it's saved in memory, for nothing.
See also:
Recommended way to save uploaded files in a servlet application
Simplest way to serve static data from outside the application server in a Java web application
Abstract template for a static resource servlet (supporting HTTP caching)
Show image as byte[] from database as graphic image in JSF page
Display dynamic image from database with p:graphicImage and StreamedContent
How to choose the right bean scope?
In order to achieve what you need using <h:graphicImage> or <img> tags, you require to create a Tomcat v7 alias in order to map the external path to your web app's context.
To do so, you will need to specify your web app's context. The easiest would be to define a META-INF/context.xml file with the following content:
<Context path="/myapp" aliases="/images=/path/to/external/images">
</Context>
Then after restarting your Tomcat server, you can access your images files using <h:graphicImage> or <img> tags as following:
<h:graphicImage value="/images/my-image.png">
or
<img src="/myapp/images/my-image.png">
*Note the context path is necessary for the tag but not for the
Another possible approach if you don't require the images to be available through HTTP GET method, could be to use Primefaces <p:fileDownload> tag (using commandLink or commandButton tags - HTTP POST method).
In your Facelet:
<h:form>
<h:commandLink id="downloadLink" value="Download">
<p:fileDownload value="#{fileDownloader.getStream(file.path)}" />
</h:commandLink>
</h:form
In your bean:
#ManagedBean
#ApplicationScope
public class FileDownloader {
public StreamedContent getStream(String absPath) throws Exception {
FileInputStream fis = new FileInputStream(absPath);
BufferedInputStream bis = new BufferedInputStream(fis);
StreamedContent content = new DefaultStreamedContent(bis);
return content;
}
}
}
In PrimeFaces you can implement your bean in this way:
private StreamedContent image;
public void setImage(StreamedContent image) {
this.image = image;
}
public StreamedContent getImage() throws Exception {
return image;
}
public void prepImage() throws Exception {
File file = new File("/path/to/your/image.png");
InputStream input = new FileInputStream(file);
ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();
setImage(new DefaultStreamedContent(input,externalContext.getMimeType(file.getName()), file.getName()));
}
In your HTML Facelet:
<body onload="#{yourBean.prepImage()}"></body>
<p:graphicImage value="#{youyBean.image}" style="width:100%;height:100%" cache="false" >
</p:graphicImage>
I suggest to set the attribute cache="false" in the graphicImage component.
In JSP
<img src="data:image/jpeg;base64,
<%= new String(Base64.encode(Files.readAllBytes(Paths.get("C:\\temp\\A.jpg"))))%>"/>
Packages are com.sun.jersey.core.util.Base64, java.nio.file.Paths and java.nio.file.Files.
Related
I'm trying to display image bytes which is saved in database as a StreamedContent in the <p:graphicImage> as follows:
<p:graphicImage value="#{item.imageF}" width="50" id="grpImage" height="80"/>
private StreamedContent content; // getter and setter
public StreamedContent getImageF() {
if (student.getImage() != null) {
InputStream is = new ByteArrayInputStream(student.getImage());
System.out.println("Byte :"+student.getImage());
content = new DefaultStreamedContent(is, "", student.getStuID());
System.out.println("ddd ------------------------------- " + content);
return content;
}
return content;
}
This returns a blank image. How is this caused and how can I solve it?
The stdout prints the following:
INFO: Byte :[B#a2fb48
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent#b0887b
INFO: Byte :[B#a2fb48
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent#1d06a92
INFO: Byte :[B#d52f0b
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent#39a60
INFO: Byte :[B#d52f0b
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent#8c3daa
INFO: Byte :[B#124728a
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent#1dbe05b
INFO: Byte :[B#124728a
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent#66a266
INFO: Byte :[B#a2fb48
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent#1293976
INFO: Byte :[B#a2fb48
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent#17b7399
INFO: Byte :[B#d52f0b
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent#1e245a5
INFO: Byte :[B#d52f0b
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent#4a7153
INFO: Byte :[B#124728a
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent#1561bfd
INFO: Byte :[B#124728a
INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent#47a8c2
The <p:graphicImage> requires a special getter method. It will namely be invoked twice per generated image, each in a completely different HTTP request.
The first HTTP request, which has requested the HTML result of a JSF page, will invoke the getter for the first time in order to generate the HTML <img> element with the right unique and auto-generated URL in the src attribute which contains information about which bean and getter exactly should be invoked whenever the webbrowser is about to request the image. Note that the getter does at this moment not need to return the image's contents. It would not be used in any way as that's not how HTML works (images are not "inlined" in HTML output, but they are instead requested separately).
Once the webbrowser retrieves the HTML result as HTTP response, it will parse the HTML source in order to present the result visually to the enduser. Once the webbrowser encounters an <img> element during parsing the HTML source, then it will send a brand new HTTP request on the URL as specified in its src attribute in order to download the content of that image and embed it in the visual presentation. This will invoke the getter method for the second time which in turn should return the actual image content.
In your particular case PrimeFaces was apparently either unable to identify and invoke the getter in order to retrieve the actual image content, or the getter didn't return the expected image content. The usage of #{item} variable name and the lot of calls in the log suggests that you were using it in an <ui:repeat> or a <h:dataTable>. Most likely the backing bean is request scoped and the datamodel isn't properly preserved during the request for the image and JSF won't be able to invoke the getter during the right iteration round. A view scoped bean would also not work as the JSF view state is nowhere available when the browser actually requests the image.
To solve this problem, your best bet is to rewrite the getter method as such so that it can be invoked on a per-request basis wherein you pass the unique image identifier as a <f:param> instead of relying on some backing bean properties which may go "out of sync" during subsequent HTTP requests. It would make completely sense to use a separate application scoped managed bean for this which doesn't have any state. Moreover, an InputStream can be read only once, not multiple times.
In other words: never declare StreamedContent nor any InputStream or even UploadedFile as a bean property; only create it brand-new in the getter of a stateless #ApplicationScoped bean when the webbrowser actually requests the image content.
E.g.
<p:dataTable value="#{bean.students}" var="student">
<p:column>
<p:graphicImage value="#{studentImages.image}">
<f:param name="studentId" value="#{student.id}" />
</p:graphicImage>
</p:column>
</p:dataTable>
Where the StudentImages backing bean can look like this:
#Named // Or #ManagedBean
#ApplicationScoped
public class StudentImages {
#EJB
private StudentService service;
public StreamedContent getImage() throws IOException {
FacesContext context = FacesContext.getCurrentInstance();
if (context.getCurrentPhaseId() == PhaseId.RENDER_RESPONSE) {
// So, we're rendering the HTML. Return a stub StreamedContent so that it will generate right URL.
return new DefaultStreamedContent();
}
else {
// So, browser is requesting the image. Return a real StreamedContent with the image bytes.
String studentId = context.getExternalContext().getRequestParameterMap().get("studentId");
Student student = studentService.find(Long.valueOf(studentId));
return new DefaultStreamedContent(new ByteArrayInputStream(student.getImage()));
}
}
}
Please note that this is a very special case wherein performing business logic in a getter method is completely legit, considering how the <p:graphicImage> works under the covers. Invoking business logic in getters is namely usually frowned upon, see also Why JSF calls getters multiple times. Don't use this special case as excuse for other standard (non-special) cases. Please also note that you can't make use of EL 2.2 feature of passing method arguments like so #{studentImages.image(student.id)} because this argument won't end up in the image URL. Thus you really need to pass them as <f:param>.
If you happen to use OmniFaces 2.0 or newer, then consider using its <o:graphicImage> instead which can be used more intuitively, with an application scoped getter method directly delegating to the service method and supporting EL 2.2 method arguments.
Thus so:
<p:dataTable value="#{bean.students}" var="student">
<p:column>
<o:graphicImage value="#{studentImages.getImage(student.id)}" />
</p:column>
</p:dataTable>
With
#Named // Or #ManagedBean
#ApplicationScoped
public class StudentImages {
#EJB
private StudentService service;
public byte[] getImage(Long studentId) {
return studentService.find(studentId).getImage();
}
}
See also the blog on the subject.
Try including a mime type. In your posted example, you have it as "". The blank image may be because it doesn't recognize the stream as a image file since you made that field an empty string. So add a mime type of image/png or image/jpg and see if that works:
String mimeType = "image/jpg";
StreamedContent file = new DefaultStreamedContent(bytes, mimeType, filename);
There's a couple possibilities here (and please post the entire class if this isn't it).
1) You're not initializing the image properly
2) Your stream is empty so you're getting nothing
I'm assuming student.getImage() has a signature of byte[] so first make sure that that data is actually intact and represents an image. Secondly--you're not specifying a content-type which should be "image/jpg" or whatever you're using.
Here's some boilerplate code to check it with, I'm using Primefaces 2 for this.
/** 'test' package with 'test/test.png' on the path */
#RequestScoped
#ManagedBean(name="imageBean")
public class ImageBean
{
private DefaultStreamedContent content;
public StreamedContent getContent()
{
if(content == null)
{
/* use your database call here */
BufferedInputStream in = new BufferedInputStream(ImageBean.class.getClassLoader().getResourceAsStream("test/test.png"));
ByteArrayOutputStream out = new ByteArrayOutputStream();
int val = -1;
/* this is a simple test method to double check values from the stream */
try
{
while((val = in.read()) != -1)
out.write(val);
}
catch(IOException e)
{
e.printStackTrace();
}
byte[] bytes = out.toByteArray();
System.out.println("Bytes -> " + bytes.length);
content = new DefaultStreamedContent(new ByteArrayInputStream(bytes), "image/png", "test.png");
}
return content;
}
}
and some markup...
<html
xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:p="http://primefaces.prime.com.tr/ui"
>
<h:head>
</h:head>
<h:body>
<p:graphicImage value="#{imageBean.content}" />
</h:body>
</html>
If that code works then you're set up properly. Despite the fact it is garbage code for the streams (don't use it in production) it should give you a point to troubleshoot from. My guess is that you might have something happening in your JPA or other Database framework where you're byte[] is empty or it is formatted wrong. Alternatively you could just have a content-type problem.
Lastly, I would clone the data from the bean so that student.getImage() would only be copied into a new array and then used. This way if you have something unknown going on (something else moving the object or changing the byte[] you're not messing with your streams.
Do something like:
byte[] data = new byte[student.getImage().length]
for(int i = 0; i < data.length; i++)
data[i] = student.getImage()[i];
so that your bean has a copy (or Arrays.copy()--whatever floats your boat). I can't stress enough how something simple like this/content type is usually what's wrong. Good luck with it.
The answer from BalusC is (as usual) the correct one.
But keep one thing (as already stated by him) in mind. The final request is done from the browser to get the URL from the constructed <img> tag. This is not done in a 'jsf context'.
So if you try to e.g. access the phaseId (logging or whatever reason)
context.getCurrentPhaseId().getName()
This will result in a NullPointerException and the somehow misleading error message you will get is:
org.primefaces.application.resource.StreamedContentHandler () - Error in streaming dynamic resource. Error reading 'image' on type a.b.SomeBean
It took me quite some time to figure out what was the problem.
I am using PrimeFaces 5.3 <p:fileUpload> to upload a PNG image and I would like to show a preview of it in <p:graphicImage> before saving in database.
Here's a MCVE:
<h:form enctype="multipart/form-data">
<p:fileUpload value="#{bean.uploadedFile}" mode="simple" />
<p:graphicImage value="#{bean.image}" />
<p:commandButton action="#{bean.preview}" ajax="false" value="Preview" />
</h:form>
private UploadedFile uploadedFile;
public UploadedFile getUploadedFile() {
return uploadedFile;
}
public void setUploadedFile(UploadedFile uploadedFile) {
this.uploadedFile = uploadedFile;
}
public void preview() {
// NOOP for now.
}
public StreamedContent getImage() {
if (uploadedFile == null) {
return new DefaultStreamedContent();
} else {
return new DefaultStreamedContent(new ByteArrayInputStream(uploadedFile.getContents()), "image/png");
}
}
No error occurring on the backing bean, and the image won't be load and display at front-end. The client mentions that the image returned a 404 not found error.
Your problem is two-fold. It failed because the uploaded file contents is request scoped and because the image is requested in a different HTTP request. To better understand the inner working, carefully read the answers on following closely related Q&A:
Display dynamic image from database with p:graphicImage and StreamedContent
How to choose the right bean scope?
To solve the first problem, you need to read the uploaded file contents immediately in the action method associated with the form submit. In your specific case, that would look like:
private UploadedFile uploadedFile;
private byte[] fileContents;
public void preview() {
fileContents = uploadedFile.getContents();
}
// ...
To solve the second problem, your best bet is to use the data URI scheme. This makes it possible to render the image directly in the very same response and therefore you can safely use a #ViewScoped bean without facing "context not active" issues or saving the byte[] in session or disk in order to enable serving the image in a different request. Browser support on data URI scheme is currently pretty good. Replace the entire <p:graphicImage> with below:
<ui:fragment rendered="#{not empty bean.uploadedFile}">
<img src="data:image/png;base64,#{bean.imageContentsAsBase64}" />
</ui:fragment>
public String getImageContentsAsBase64() {
return Base64.getEncoder().encodeToString(imageContents);
}
Note: I assume that Java 8 is available to you as java.util.Base64 was only introduced in that version. In case you're using an older Java version, use DatatypeConverter.printBase64Binary(imageContents) instead.
In case you happen to use JSF utility library OmniFaces, you can also just use its <o:graphicImage> component instead which is on contrary to <p:graphicImage> capable of directly referencing a byte[] and InputStream bean property and rendering a data URI.
<o:graphicImage value="#{bean.imageContents}" dataURI="true" rendered="#{not empty bean.imageContents}">
Displaying a BLOB image using <p:graphicImage> as follows.
<p:graphicImage value="#{categoryBean.image}">
<f:param name="id" value="7"/>
</p:graphicImage>
Where CategoryBean has been defined as follows.
#Named
#ApplicationScoped
public class CategoryBean {
#Inject
private CategoryService service;
public CategoryBean() {}
public StreamedContent getImage() throws IOException {
FacesContext context = FacesContext.getCurrentInstance();
if (context.getCurrentPhaseId() == PhaseId.RENDER_RESPONSE) {
return new DefaultStreamedContent();
} else {
String id = context.getExternalContext().getRequestParameterMap().get("id");
byte[] bytes = Utils.isNumber(id) ? service.findImageById(Long.parseLong(id)) : null;
return bytes == null ? null : new DefaultStreamedContent(new ByteArrayInputStream(bytes));
}
}
}
Regarding the above approach, the following custom tag should work flawlessly but it fails to display the image on <p:graphicImage> with no error / exception.
<my:image bean="#{categoryBean}" property="image" paramName="id" paramValue="7"/>
The tag file is located under /WEB-INF/tags/image.xhtml.
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:p="http://primefaces.org/ui"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:f="http://xmlns.jcp.org/jsf/core">
<p:graphicImage value="#{bean[property]}">
<f:param name="#{paramName}" value="#{paramValue}"/>
</p:graphicImage>
</ui:composition>
The generated <img> tag seems to look fine :
<img id="form:j_idt4"
src="/ContextPath/javax.faces.resource/dynamiccontent.properties.xhtml?ln=primefaces&v=5.3&pfdrid=IA0%2F7ZuBnGS%2BSzeb%2BHyPOTo4Pxp4hjI6&pfdrt=sc&id=7&pfdrid_c=true"
alt=""/>
It only returns a HTTP 404 error.
Is there any flaw in the definition of the custom tag given?
It's caused by the way how PrimeFaces <p:graphicImage> identifies image requests. Basically, it converts the exact value expression #{bean[property]} to string, encrypts it and then passes it as pfdrid value. When the webbrowser needs to download the image by a brand new HTTP request, that value expression is decrypted and evaluated in the "current" EL context. However, during that moment, there's nowhere a #{bean} nor #{property} available anywhere in the EL context because there's no means of a JSF view with tagfiles and all. Only request, session and application scoped beans are available in EL context.
There's nothing to do against this other than reporting an issue at PrimeFaces guys.
As to alternate solutions, OmniFaces <o:graphicImage> does a better job in this by inspecting the target bean/method during render response already instead of during streaming the image. It immediately inspects #{bean[property]}, discovers that it actually represents #{categoryBean.image}, and then succeeds. Just to be sure I tested it in a tagfile like you have and it works fine for me whereas the PF one indeed fails as described.
<o:graphicImage value="#{bean[property](paramValue)}" />
public byte[] getImage(Long id) throws IOException {
return service.findImageById(id);
}
Current versions: JSF 2.1, RichFaces 4.5.2
I got into the following problem:
I have an upload component in one of my web-pages which is going to upload images:
<h:graphicImage id="uploadedPict" value="__WHAT__?" alt="picture />
<rich:fileUpload id="imageStartUploadComponent"
fileUploadListener="#{MyBean.uploadStartImage}"
acceptedTypes="png,jpg"
immediateUpload="false">
<a4j:ajax event="uploadcomplete" render="uploadedPict" />
</rich:fileUpload>
and the manged bean itself:
public class MyBean{
private UploadedFile detailsImage;
public void uploadStartImage(){
//SETTING detailsImage here
}
//GET, SET, OTHER STUFF
}
I could invoke UploadedFile::getData() method to retrieve binary image data. But how can I render it into the web-page? value attribute is supposed to be assigned with an actual picture's URL. So it seems I'm going to have to write my own servlet...
QUESTION: Is there a way to avoid writing a servlet and render the picture right from managed bean's method returning its binary data?
UPDATE: I need to do this for showing uploaded picture's preview. Maybe there is another way to do that correctly?
You can use <a4j:mediaOutput>:
<a4j:mediaOutput element="img" mimeType="image/jpeg"
createContent="#{bean.paint}" value="#{bean.file}">
</a4j:mediaOutput>
and the paint method:
public void paint(OutputStream stream, Object object) throws IOException {
stream.write(((UploadedFile) object).getData());
stream.close();
}
(check the showcase example)
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