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.
Related
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>
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>
I have a page containing a p:commandLink that calls a very simple method on a session-scoped backing bean. The backing bean method simply logs a message to the console and redirects to a welcome page.
When run, the link does nothing. There are no errors reported in h:messages and no stack traces on the server console. However, if I use the exact same code on another page then the link works fine.
The main difference between the pages is that the page containing the 'dead' link is protected by a login filter (the page is in the 'restricted' folder) whereas the page that contains the 'working' link is not in the restricted folder.
I've followed the advice in several other threads about action links that don't work. I don't have nested forms or rendering problems, etc. I'm wondering if the login filter may be having some effect on the link.
Here's the page containing the link that doesn't work (it resides in location: restricted/secret.xhtml). I've deliberately minimised the contents to eliminate other potential causes.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.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:p="http://primefaces.org/ui">
<h:head>
<title>Restricted Page</title>
</h:head>
<h:body>
<h:form>
<h:messages />
<br/>
<p:commandLink action="#{loginBean.testMethod}" value="Test" />
</h:form>
</h:body>
</html>
Here's the method in the backing bean (which DOES work if called using the exact same commandLink code on an unprotected page:
public String testMethod() {
logger.info("xxxxxxxxxxxxxx Test method called xxxxxxxxxxxxxx");
return("WelcomeMember.xhtml");
}
The login filter is as follows:
package com.mymato.coop;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
#WebFilter("/restricted/*")
public class AuthenticationFilter implements Filter {
#SuppressWarnings("unused")
private FilterConfig config;
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
if (((HttpServletRequest) req).getSession().getAttribute(
LoginBean.AUTH_KEY) == null) {
((HttpServletResponse) resp).sendRedirect("../login.xhtml");
} else {
chain.doFilter(req, resp);
}
}
public void init(FilterConfig config) throws ServletException {
this.config = config;
}
public void destroy() {
config = null;
}
}
For future reference, is there a good way to debug this type of problem? I've run into the dead link problem a few times now. It's very difficult to solve because it fails silently. At least if there was an error message then that would be a clue.
I have read an excellent answer when view scoped bean is destroyed. (see How and when is a #ViewScoped bean destroyed in JSF?) and I automatically assumed that destroyed bean is also removed from the view scope cache. But I could see that bean is still in the cache, so I would like to know if destroyed view scoped bean should be also removed from the LRU view scope cache, if ever?
In our application we open all details in sepeare tabs/windows. After some opening/closing (depends on numberOfViewsInSession) we could see ViewExpiredException in case when the first detail window is still opened and user has been opening and closing another detail windows and after some time he wants to do some operation in the first window. I have done some debugging and I can see that closed view wasn't removed from the LRU cache.
So is it expected behaviour or is there something wrong in my code? And if it is expected behaviour, is there any useful strategy how to work with multitabs/multiwindow without lot of ViewExpiredException caused by the LRU cache?. I know I can change numberOfViewsInSession but it is the last choice that I want to use.
I prepared a simple testcase and when I'm opening/closing view.xhtml more times, I can see that LRUMap is growing.
Environment : JDK7, mojarra 2.2.4, tomcat 7.0.47
Thanks in advance
view.xhtml
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://xmlns.jcp.org/jsf/html">
<head>
<title>View Bean</title>
<meta charset="UTF-8"/>
</head>
<body>
<h:form id="viewForm">
<div>#{viewBean.text}</div>
<h:commandButton id="closeButton" value="Close" action="/ClosePage.xhtml"/>
</h:form>
</body>
</html>
index.xhtml
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html">
<h:head>
<title>Session bean</title>
</h:head>
<h:body>
<h:form id="sessionForm">
<h:outputText value="#{sessionBean.text}"/>
<br/>
<h:link id="linkView" value="Open view.xhmtl" outcome="/view.xhtml" target="_blank"/>
</h:form>
</h:body>
</html>
ClosePage.xhmtl
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head><title>Window will be closed</title></head>
<body>
<script>window.close();</script>
</body>
</html>
ViewBean.java
package com.mycompany.mavenproject2;
import com.sun.faces.util.LRUMap;
import java.io.Serializable;
import java.util.Map;
import javax.annotation.PreDestroy;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
import javax.faces.context.FacesContext;
#ManagedBean(name = "viewBean")
#ViewScoped
public class ViewBean implements Serializable {
private static final long serialVersionUID = 13920902390329L;
private int lruMapSize;
/**
* Creates a new instance of ViewBean
*/
public ViewBean() {
Map<String, Object> sessionMap = FacesContext.getCurrentInstance().getExternalContext().getSessionMap();
LRUMap<String, LRUMap> lruMap = (LRUMap) sessionMap.get("com.sun.faces.renderkit.ServerSideStateHelper.LogicalViewMap");
lruMapSize = lruMap == null ? 0 : lruMap.size();
}
#PreDestroy
void destroyed() {
System.out.println("View bean destroyed");
}
public String getText() {
return "ViewBean LRU cache size:" + Integer.toString(lruMapSize);
}
}
SessionBean.java
package com.mycompany.mavenproject2;
import java.io.Serializable;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
#ManagedBean(name = "sessionBean")
#SessionScoped
public class SessionBean implements Serializable {
private static final long serialVersionUID = 1777489347L;
/**
* Creates a new instance of SessionBean
*/
public SessionBean() {
}
public String getText() {
return "Session bean text";
}
}
I think every JSF developer runs up against this eventually. The true problem lies in the fact that you can't devise a truly reliable stateful system in which the browser will signal back to the ViewScoped bean that it is done with the page, allowing the backing bean to destroy itself. This is why JSF implementations have LRU caches to limit the memory used by a session, which is a great catch-all solution for everyday apps.
There are a few cases in which you know that you are done with the ViewScoped bean, such as a redirect from that bean. For these, you could write your own view handler to perform a smarter caching system, but that's not a trivial task, and frankly, not worth the effort.
The simplest solution I came up with is to use a javascript timer to execute an ajax postback to the server on every page with a ViewScoped bean. (Setting this timer to execute every 30 seconds seems reasonable.) This will move the ViewScoped bean(s) associated with the page to the bottom of the LRU cache, ensuring they aren't expired.
In particular, I use a primefaces poll component to pull this off and stick in a template to be used by all ViewScoped beans. By placing this component in its own form, the request size remains small.
This is my xhtml:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<h:head>
<title>Keep alive</title>
</h:head>
<f:metadata>
<f:viewParam name="value" id="value" value="#{myBean.val}" ></f:viewParam>
</f:metadata>
<h:body>
Hello.<h:form><h:outputLabel value="#{myBean.val}"></h:outputLabel></h:form>
</h:body>
</html>
And this is my bean:
import javax.faces.bean.RequestScoped;
import javax.faces.bean.ManagedBean;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
#RequestScoped
#ManagedBean
public class MyBean {
#PersistenceContext(unitName = "myPUhere")
private EntityManager em;
/**
* Creates a new instance of myBean
*/
public MyBean() {
System.out.println("mybeanload");
if (getWaarde() == "yes") {
System.out.println("IT WORKS!!");
}
}
private String val;
public String getVal() {
System.out.println("getting value");
return val;
}
public void setVal(String value) {
System.out.println("setting value to " + value);
this.val = value;
}
}
My Bean does not respond to this, what don't I see here? It does not display the value I enter in the URL, nor it displays my outputLabel.
So, you're retrieving the raw JSF source code in the browser instead of its generated HTML output. Browsers obviously don't understand JSF code (like as it doesn't understand JSP/PHP/ASP/etc code), but it only understands HTML code. This can happen when the FacesServlet hasn't been invoked, it's namely the one responsible for all the JSF works.
Perhaps your FacesServlet is based on some tutorial or IDE-autogenerated code been mapped on an URL pattern different than *.xhtml, such as *.jsf or *.faces. In that case, you've 2 options:
Fix the request URL in your browser's address bar to match exactly that URL pattern. So, assuming that it's *.jsf, then don't open the page by
http://localhost:8080/context/index.xhtml
but instead by
http://localhost:8080/context/index.jsf
Fix the URL pattern to be *.xhtml directly. This wasn't possible back in JSF 1.x as the FacesServlet would otherwise call itself in an infinite loop, but this is quite possible in JSF 2.x and a lot of books/tutorials/resources/IDEs didn't take this into account.
<url-pattern>*.xhtml</url-pattern>
See also:
JSF Facelets: Sometimes I see the URL is .jsf and sometimes .xhtml. Why?