My main question is : Is there a "good practice" to serve binary files (PDF, docs, etc) using JSF 2 with CDI, and using bookmarkable URLs ?
I've read the JSF 2 spec (JSR 314) and I see it exists a "Resource Handling" paragraph. But it seems to be used only to serve static files put in the war or jar files. I didn't really understood if it exists a way to interact here by registering some specific ResourceHandler ...
Actually, I was used to Seam's 2 way to do that : extending the AbstractResource class with getResource(HttpServletRequest, HttpServletResponse) method and getResourcePath() to declare which path to serve after <webapp>/seam/resource/ URL prefix and declaring the SeamResourceServlet in the web.xml file.
Here is what I did.
I've first saw How to download a file stored in a database with JSF 2.0 and tried to implement it.
<f:view ...
<f:metadata>
<f:viewParam name="key" value="#{containerAction.key}"/>
<f:event listener="#{containerAction.preRenderView}" type="preRenderComponent" />
</f:metadata>
...
<rich:dataGrid columns="1" value="#{containerAction.container.files}" var="file">
<rich:panel>
<h:panelGrid columns="2">
<h:outputText value="File Name:" />
<h:outputText value="#{file.name}" />
</h:panelGrid>
<h:form>
<h:commandButton value="Download" action="#{containerAction.download(file.key)}" />
</h:form>
</rich:panel>
</rich:dataGrid>
And here is the beans :
#Named
#SessionScoped
public class ContainerAction {
private Container container;
/// Injections
#Inject #DefaultServiceInstance
private Instance<ContainerService> containerService;
/// Control methods
public void preRenderView(final ComponentSystemEvent event) {
container = containerService.get().loadFromKey(key);
}
/// Action methods
public void download(final String key) throws IOException {
final FacesContext facesContext = FacesContext.getCurrentInstance();
HttpServletResponse response = (HttpServletResponse) facesContext.getExternalContext().getResponse();
final ContainerFile containerFile = containerService.get().loadFromKey(key);
final InputStream containerFileStream = containerService.get().read(containerFile);
response.setHeader("Content-Disposition", "attachment;filename=\""+containerFile.getName()+"\"");
response.setContentType(containerFile.getContentType());
response.setContentLength((int) containerFile.getSize());
IOUtils.copy(containerFileStream, response.getOutputStream());
response.flushBuffer();
facesContext.responseComplete();
}
/// Getters / setters
public Container getContainer() {
return container;
}
}
Here I had to switch to Tomcat 7 (I was using 6) in order to interpret correctly that EL expression. With #SessionScoped it worked, but not with #RequestScoped (when I clicked the button, nothing happend).
But then I wanted to use a link instead of a button.
I tried <h:commandLink value="Download" action="#{containerAction.download(file.key)}" /> but it generates some ugly javascript link (not bookmarkable).
Reading the JSF 2 spec, it seems that there is a "Bookmarkability" feature, but it is not realy clear how to use it.
Actually, it seems to work only with views, so I tried to create an empty view and created a h:link :
<h:link outcome="download.xhtml" value="Download">
<f:param name="key" value="#{file.key}"/>
</h:link>
<f:view ...>
<f:metadata>
<f:viewParam name="key" value="#{containerFileDownloadAction.key}"/>
<f:event listener="#{containerFileDownloadAction.download}" type="preRenderComponent" />
</f:metadata>
</f:view>
#Named
#RequestScoped
public class ContainerFileDownloadAction {
private String key;
#Inject #DefaultServiceInstance
private Instance<ContainerService> containerService;
public void download() throws IOException {
final FacesContext facesContext = FacesContext.getCurrentInstance();
// same code as previously
...
facesContext.responseComplete();
}
/// getter / setter for key
...
}
But then, I had a java.lang.IllegalStateException: "getWriter()" has already been called for this response.
Logic as when a view initiates, it uses getWritter to initialize the response.
So I created a Servlet which does the work and created the following h:outputLink :
<h:outputLink value="#{facesContext.externalContext.request.contextPath}/download/">
<h:outputText value="Download"/>
<f:param name="key" value="#{file.key}"/>
</h:outputLink>
But even if that last technique gives me a bookmarkable URL for my file, it is not really "JFS 2" ...
Do you have some advice ?
I agree with BalusC. Typically an application is not purely a JSF application but a Java EE application.
It's not for nothing that other things than JSF views exist for handling http requests in Java EE. In Java EE 6, your named CDI bean can btw also directly be mapped to a path using JAX-RS. This is an alternative to using Servlets. In that case you would use #Produces and #Path (see e.g. Input and Output binary streams using JERSEY?).
On the other hand, one advantage of <f:viewParam> in JSF is that you can easily attach validators to it. Neither Servlets nor JAX-RS resources have support for that at the moment.
<h:link> is also more comfortable to use than writing <h:outputLink value="#{facesContext.externalContext.request.contextPath}/..."> all the time. This can however be mitigated by wrapping this part in a Facelets tag or composite component.
(It would be great I think if a future version of the spec provided a link tag in JSF to link directly to JAX-RS resources (with optional container startup validation to ensure the link is legal)).
JSF is from the beginning on designed to be a MVC framework, not to be some REST file service.
A servlet is perfectly fine for the job. Annotate it with #WebServlet to get a better Java EE 6 feeling.
There is in fact a direct solution to this problem using PrettyFaces URLRewriteFilter -> http://ocpsoft.org/prettyfaces/serving-dynamic-file-content-with-prettyfaces/
This blog explains how to do exactly what you want to do, without having to use an entirely new MVC framework.
Related
I'm passing a parameter p1 to another page page.xhtml:
<ui:include src="page.xhtml">
<ui:param name="p1" value="#{someObject}"/>
</ui:include>
Is this possible to evaluate #{p1} inside #PostConstruct method of the backing bean of page.xhtml? Using the following piece of code, #{p1} cannot resolve:
FacesContext currentInstance = FacesContext.getCurrentInstance();
currentInstance.getApplication().evaluateExpressionGet(currentInstance, "#{p1}", String.class);
Why do I need this?
I'm using an xhtml file (say component.xhtml) as a custom UI component. This file has a backing bean from which I should get component data. Since I'm including this xhtml file twice or more in my main JSF page, I want to pass different objects to each of component.xhtml so that my component work with my custom data each time included.
In Mojarra, you can get it as an attribute of the FaceletContext. You can get it in the #PostConstruct of a managed bean which is guaranteed to be referenced/constructed for the first time in the included page (and thus not in the parent page before the <ui:param> is declared in the component tree).
FaceletContext faceletContext = (FaceletContext) FacesContext.getCurrentInstance().getAttributes().get(FaceletContext.FACELET_CONTEXT_KEY);
Object p1 = faceletContext.getAttribute("p1");
In MyFaces, the whole FaceletContext isn't available in managed beans as it's discarded by end of view build time and this construct would then not work. To be JSF implementation independent, you might want to consider to set it via <c:set scope="request"> instead. It's then available as a request attribute.
As to the concrete functional requirement, consider creating a comoposite component with a backing component. For some examples, see our composite component wiki page and this blog about using multiple input components in a composite component. See also When to use <ui:include>, tag files, composite components and/or custom components?
The param is not available in the #PostConstruct method; you can use the preRenderComponent event to initialize the parameters inside your backing bean; just put it after the ui:composition of the included page, it will be executed before the rendering of the included page itself.
Following the OP example of a passing a p1 parameter to a page.xhtml template
the main page:
<ui:include src="page.xhtml">
<ui:param name="p1" value="#{someObject}"/>
</ui:include>
page.xhtml:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
...>
<ui:composition>
<f:event listener="#{backingBean.init(p1)}" type="preRenderComponent"/>
...
</ui:composition>
</html>
BackingBean.java:
#ViewScoped
public class BackingBean{
private Object p1;
public void init(Object value){
this.p1=p1;
}
...
}
the event is fired before the render of the ui:composition tag, that is before the render of page.xhtml
This works for me:
<ui:include src="page.xhtml">
<ui:param name="p1" value="#{someObject}"/>
</ui:include>
page.xhtml:
<c:set var="data" value="#{p1}" scope="request"/>
Your bean:
#ViewScoped
public class ManagedBean{
private Object someObject;
public Object getSomeObject(){
if(someObject== null){
HttpServletRequest request = (HttpServletRequest)FacesContext.getCurrentInstance().getExternalContext().getRequest();
someObject= request.getAttribute("data");
}
return someObject;
}
public void setSomeObject(Object someObject){
this.someObject = someObject;
}}
So i have seen several questions about similar issues, anyway i cannot fix my problem.
I want to use primefaces' fileUpload functionality to upload multiple files.
So i have this in my xhtml.
I built it based on the instructions here:
How to use PrimeFaces p:fileUpload? Listener method is never invoked or UploadedFile is null
xhtml:
<h:form enctype="multipart/form-data">
<p:fileUpload fileUploadListener="#{controller.upload}" multiple="true"></p:fileUpload>
</h:form>
Note:
upload is within a h:form tag
enctype is set to multipart/form-data
I use "advanced" mode, and the <h:head> tag exists.
Configuration:
I use JSF2.2 and Primefaces 5.2, therefore i did not add additional configuration, as suggested in the post
My Bean:
#ManagedBean
#RequestScoped
public class controller implements Serializable {
public void upload(FileUploadEvent event) throws IOException{
//Do something
}
Note:
I did it as suggested in the post for ajax-uploads.
Troubleshooting:
as i do not use the PrimeFaces file upload filter, suggestion 1 and 2 do not apply
i do not have a nested h:form
Checking traffic in browser: A Post request is being performed and i get Ok 200.
Further, the post states that the Request can only be parsed once (in relation to filter chain ordering). I have 2 (additional) active filter chains, spring security and pretty faces. Can one of those be the problem here?
Update: PrettyFaces seems, indeed, to be the cause of the problem. When disabling it, everything works.
Enviornment:
JSF 2.1.7
SEAM 2.3.0
JBOSS 5.1.2
My application has a string that needs to be localized
"I agree to WorldSite's Privacy Policy and Cookie Notice"
Where the italicized Privacy Policy and Cookie Notice are hyperlinks to other pages.
Originally our facelet was set up like this:
<h:outputText value="#{messages['iAgreeTo']}" />
<h:outputLink target="_blank" value="#{bean.privacyPolicy}">#{messages['privacyPolicy']}</h:outputLink>
<h:outputText value="#{messages['and']}"/>
<h:outputLink target="_blank" value="/jsf/Notice.xhtml">
<h:outputText value="#{messages['cookieNotice']}"/>
<f:param name="content" value="Cookie-Notice"/>
<f:param name="application" value="#{bean.application}"/>
</h:outputLink>
Note: We have URL rewriting in place that takes /jsf/Notice.xhtml and rewrites to
rewrite pattern: /#{application}/Notice/#{content}
result:http://contextRoot/contextPath/myApp/Notice/Cookie-Notice
This allowed for piecemeal translations of the individual keys
iAgreeTo=I agree to WorldSite's
privacyPolicy=Privacy Policy
and= and
cookieNotice=Cookie Notice
But this required a workaround for some languages (in "iAgreeTo" and "and" keys)
iAgreeTo=J'accepte la
privacyPolicy=Politique de la vie privée
and= de WorldSite et les
cookieNotice=Note D'information sur les Cookies
Ideally I would like to be able to have the links be movable within the key. Something like this:
iAgreePhrase=I agree to WorldSite's #{messages['privacyPolicyLink']} and the #{messages['cookieNoticeLink']}
privacyPolicy=Privacy Policy
cookieNotice=Cookie Notice
//The following non-translatable keys held in a separate file
privacyPolicyLink=<h:outputLink target="_blank" value="#{bean.privacyPolicy}">#{messages['privacyPolicy']}</h:outputLink>
cookieNoticeLink=<h:outputLink target="_blank" id="cookieNoticeLink" value="/jsf/Notice.xhtml">\
#{messages['cookieNotice']}\
<f:param name="content" value="Cookie-Notice"/>\
<f:param name="application" value="#{bean.application}"/>\
</h:outputLink>
But the facelet returns the JSF tags (h:outputLink) as strings instead of expanding them to their HTML tags. I can use <a> tags, but then I'm putting rewrite logic in the properties file which is difficult to maintain
iAgreePhrase=I agree to WorldSite's #{messages['privacyPolicyLink']} and the #{messages['cookieNoticeLink']}
privacyPolicy=Privacy Policy
cookieNotice=Cookie Notice
//The following non-translatable keys held in a separate file
privacyPolicyLink=<a target="_blank" href="#{bean.privacyPolicy}">#{messages['privacyPolicy']}</a>
cookieNoticeLink=<a target="_blank" href="#{contextPath}/#{bean.application}/Notice/Terms-and-Conditions">\
#{messages['cookieNotice']}</a>
Is there a way I can achieve my desired effect without having to put rewrite logic in the resource bundle? Some thoughts I have are forcing the application container to process the facelet twice/reorder it, so it inserts the resource bundles first, and then expands the JSF tags.
Alternatively I may be able to construct the rewritten URL in a bean then call that from my resource bundle?
You seem to have got everything round the wrong way and I'm sorry to have to say that I found the question poorly analysed, overlong and confusing to read.
What you should be trying to do is use resource bundles from within JSF tags and not the other way round. Where you need to parameterise a message you use a construct such as this:
messages.properties
nameHeader=My name is {0}
index.xhtml
<h:outputFormat value="#{msgs.nameHeader}">
<f:param value="#{bean.name}"/>
</h:outputFormat>
If you have a scenario where this doesn't work you would have to build the string in a backing bean.
While Oversteer's answer is certainly applicable, this could also prove helpful. You can create URLs from EL expressions like this:
#{facesContext.externalContext.encodeActionURL('/myapp/Notice/Privacy-Policy')}
To include HTML markup in a message, you need to use escape="false" in <h:outputText/>, for example:
<h:outputText escape="false" value="#{messages['iAgreePhrase']}" />
And, in your messages.properties (or localized version):
iAgreePhrase=I agree to WorldSite's Privacy Policy and ...
This has the problem that the URL is calculable only from within an HTTP request, if you for example use the message key from within an asynchronous thread, the URL will not be calculated (there's no facesContext available).
I have a problem with a binding p:commandButton to a property in a backing bean. I've tried to simplify my code to show general idea.
ExampleBean is a backing bean
public class ExampleBean {
public String title;
List<ExampleWrapper> list;
// Getters and setters
}
ExampleWrapper is a POJO
public class Wrapper {
public String name;
public String description;
public CommandButton button;
// Listener which changes button state
// Getters and setters
}
index.xhtml is a main page:
<h:form>
<h:outputText value="Title" />
<p:inpurText value="#{exampleBean.title}"
<ui:include src="list.xhtml">
<ui:param name="bean" value="#{exampleBean}">
</ui:include>
</h:form>
list.xhtml is a fragment I want to be reused in a few places:
<ui:composition ...>
<ui:repeat id="list" var="exampleWrapper" value="#{bean.list}">
<h:outputText value="#{exampleWrapper.name}"/>
<h:outputTextarea value="#{exampleWrapper.description}"/>
<p:commandButton id="button" binding="#{exampleWrapper.button}"
value="Button" />
</ui:composition>
So, I get exception:
javax.el.PropertyNotFoundException: /list.xhtml ... binding="#{exampleWrapper.button}": Target Unreachable, identifier 'exampleWrapper' resolved to null
Without binding attribute everything works and displays fine
Could you explain why and how can I bind button to this POJO property? Any help will be appreciated
I'm using JSF 2.0.2 with Primefaces 3.0.1
The binding (and id) attribute of a JSF UI component is resolved during view build time. The #{exampleWrapper} instance is not available during the view build time. The view build time is that moment when the XHTML file is parsed into a JSF component tree. The #{exampleWrapper} is only available during the view render time. The view render time is the moment when the JSF component tree generates HTML output.
Basically, there's only one <p:commandButton> in the component tree which generates its HTML output multiple times as many as the <ui:repeat> iterates. You need to bind it to the #{bean} instead, or to use JSTL <c:forEach> instead of <ui:repeat>. The JSTL tags runs namely during view build time and the <c:forEach> will thus produce physically multiple JSF UI components. But, more than often, binding components to backing beans is unnecessary in JSF 2.x. Whatever functional requirement you've had in mind for which you thought that this is the solution, it can definitely be solved in a better way.
My problem is that the value of the parameter is null in the bean
In the xhtml I have this code:
<h:commandLink action="#{navigation.editNews}" value="#{new.title}">
<f:param name="newsId" value="#{new.id}" />
</h:commandLink>
In Navigation I redirect to news.xhtml
public String editNews(){
return "editNews";
}
This is the code in faces-config.xml
<navigation-case>
<from-action>#{navigation.editNews}</from-action>
<from-outcome>editNews</from-outcome>
<to-view-id>/news.xhtml</to-view-id>
<redirect />
</navigation-case>
I have a bean where I call method when I push a button in news.xhtml and I try to get param but it is null
FacesContext fc = FacesContext.getCurrentInstance();
Map<String,String> params = fc.getExternalContext().getRequestParameterMap();
String = params.get("newsId");
The <f:param> adds a request parameter. So this parameter has a lifetime of exactly one HTTP request. You've a <redirect/> in your navigation case which basically instructs the webbrowser to send a new request on the given location. This new request does not contain the parameter anymore.
You've basically 2 options:
Get rid of <redirect /> in the navigation case.
Make it a normal GET request instead. If you're on JSF2, use <h:link> instead.
<h:link action="news" value="#{news.title}">
<f:param name="newsId" value="#{news.id}" />
</h:link>
Or if you're still on JSF 1.x (the usage of navigation cases less or more hints this as they are superfluous in JSF 2 thanks to the new implicit navigation feature; or you must be reading outdated tutorials/books/answers targeted on JSF 1.x; also the absence of JSF 2.0 tag on your question is suspicious), then use a normal <a> link instead.
#{news.title}
See also:
Communication in JSF 2.0 - Processing GET request parameters