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
Related
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.
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);
}
I want to upload many files and I have chosen rich:fileUpload control for this.
My problem is that I need to add more information for each file, for example the title I want to appear in the application for that file. How can I do that, and send to the fileUploadListener method in order to use the id?
Based in your question, the RichFaces FileUpload demo has all the info you need to handle file upload for 1 or more files at the same time.
If you want to add more data (like h:inputText values and others), then you should pass them using valueChangeListener instead value tag attribute, because the fileUploadListener is an event that happens within an ajax call, so your UIComponents won't call the setters for the attributes.
Some code to explain the behavior:
<h:panelGrid cols="2">
<h:outputText value="File Title:">
<h:inputText value="#{fileBean.fileTitle}" immediate="false"
valueChangeListener="#{fileBean.valueChangeFileTitle}" />
<h:outputText value="File:">
<rich:fileUpload
fileUploadListener="#{bean.fileUpload}">
</rich:fileUpload>
</h:panelGrid>
The Bean to handle the requests
public class Bean {
private String fileTitle;
public Bean() {
}
//getters and setters...
public String getFileTitle() {
return this.fileTitle;
}
public void setFileTitle(String fileTitle) {
System.out.println("Calling the setter");
this.fileTitle = fileTitle;
}
public void valueChangeFileTitle(ValueChangeEvent e) {
System.out.println("Calling the ValueChangeListener");
fileTitle = (String)e.getNewValue();
}
//this will be invoked by an ajax call
//the setter of the view won't be invoked for fileTitle
//instead, we should take its value using valueChangeListener
public void fileUpload(UploadEvent ue) {
MyFileManager mfm = MyFileManager.getFileManager();
MyFile myFile = new MyFile();
myFile.setTitle(this.fileTitle);
myFile.setName(ue.getUploadItem().getFileName());
myFile.setData(ue.getUploadItem().getData());
mfm.createFile(myFile);
}
}
Also, avoid to use System.out.println calls in your code, I'm doing it so you can understand what method will be called, instead use a Logger like Log4j.
EDIT: I have this snippet of code:
<h:inputText id="email_id" value="#{CreateUserManager.email}"
styleClass="#{CreateUserManager.emailPrimariaValid ? '' : 'inputErrorClass'}">
<f:validator validatorId="EmailValidator" />
<a4j:support event="onblur" reRender="email_id, messages" oncomplete="setAnchor();"
status="status4divCoverAll" ajaxSingle="true" />
</h:inputText>
This is the managed session bean:
public class CreateUserManager {
...
protected boolean emailPrimariaValid;
public CreateUserManager() {
...
this.emailPrimariaValid = true;
}
public boolean isEmailPrimariaValid() {
FacesContext context = FacesContext.getCurrentInstance();
UIInput input = (UIInput)context.getViewRoot().findComponent(":createUser:email_id");
return input.isValid();
}
public void setEmailPrimariaValid(boolean emailPrimariaValid) {
this.emailPrimariaValid = emailPrimariaValid;
}
}
Keep in mind that I remove this bean from session if I come from another page (url), so the bean execute the constructor again.
The problem: I write an invalid email and it sets correctly the class to inputErrorClass, but if I go to another page (so the input component is still invalid) and then come back to the first one, the class remains to inputErrorClass.
Are you by any chance using Seam? It has some good functionality for styling input elements when there are errors.
In Seam 2, you can use the <s:decorate> tag: http://docs.jboss.org/seam/2.2.0.GA/reference/en-US/html/controls.html#d0e28688
In Seam 3, you can use UIInputContainer and a composite component: http://jerryorr.blogspot.com/2011/10/replacement-for-sdecorate-in-seam-3.html
If you aren't using Seam... well, you can look at the Seam source code to see what they did!
One of the many approaches :
http://mkblog.exadel.com/2011/05/how-to-hightlight-a-field-in-jsf-when-validation-fails/