Note that there are some explanatory texts on larger screens.

plurals
  1. POServing files with JSF 2 / CDI, using bookmarkable URLs
    text
    copied!<p>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 ?</p> <p>I've read the <a href="http://download.oracle.com/otndocs/jcp/jsf-2_1-mrel2-eval-oth-JSpec/" rel="nofollow noreferrer">JSF 2 spec (JSR 314)</a> 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 ...</p> <p>Actually, I was used to Seam's 2 way to do that : extending the <a href="http://docs.jboss.org/seam/2.2.0.GA/api/org/jboss/seam/web/AbstractResource.html" rel="nofollow noreferrer"><code>AbstractResource</code></a> class with <code>getResource(HttpServletRequest, HttpServletResponse)</code> method and <code>getResourcePath()</code> to declare which path to serve after <code>&lt;webapp&gt;/seam/resource/</code> URL prefix and declaring the <code>SeamResourceServlet</code> in the <code>web.xml</code> file.</p> <p>Here is what I did.</p> <p>I've first saw <a href="https://stackoverflow.com/questions/5498391/how-to-download-a-file-stored-in-a-database-with-jsf-2-0">How to download a file stored in a database with JSF 2.0</a> and tried to implement it.</p> <pre class="lang-html prettyprint-override"><code>&lt;f:view ... &lt;f:metadata&gt; &lt;f:viewParam name="key" value="#{containerAction.key}"/&gt; &lt;f:event listener="#{containerAction.preRenderView}" type="preRenderComponent" /&gt; &lt;/f:metadata&gt; ... &lt;rich:dataGrid columns="1" value="#{containerAction.container.files}" var="file"&gt; &lt;rich:panel&gt; &lt;h:panelGrid columns="2"&gt; &lt;h:outputText value="File Name:" /&gt; &lt;h:outputText value="#{file.name}" /&gt; &lt;/h:panelGrid&gt; &lt;h:form&gt; &lt;h:commandButton value="Download" action="#{containerAction.download(file.key)}" /&gt; &lt;/h:form&gt; &lt;/rich:panel&gt; &lt;/rich:dataGrid&gt; </code></pre> <p>And here is the beans :</p> <pre class="lang-java prettyprint-override"><code>@Named @SessionScoped public class ContainerAction { private Container container; /// Injections @Inject @DefaultServiceInstance private Instance&lt;ContainerService&gt; 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; } } </code></pre> <p>Here I had to <a href="http://seamframework.org/Community/Weld111AndTomcat7Problems" rel="nofollow noreferrer">switch to Tomcat 7</a> (I was using 6) in order to interpret correctly that EL expression. With <code>@SessionScoped</code> it worked, but not with <code>@RequestScoped</code> (when I clicked the button, nothing happend).</p> <p>But then I wanted to use a link instead of a button.</p> <p>I tried <code>&lt;h:commandLink value="Download" action="#{containerAction.download(file.key)}" /&gt;</code> but it generates some ugly javascript link (not bookmarkable).</p> <p>Reading the JSF 2 spec, it seems that there is a "Bookmarkability" feature, but it is not realy clear how to use it.</p> <p>Actually, it seems to work only with views, so I tried to create an empty view and created a <code>h:link</code> :</p> <pre class="lang-html prettyprint-override"><code>&lt;h:link outcome="download.xhtml" value="Download"&gt; &lt;f:param name="key" value="#{file.key}"/&gt; &lt;/h:link&gt; </code></pre> <pre class="lang-html prettyprint-override"><code>&lt;f:view ...&gt; &lt;f:metadata&gt; &lt;f:viewParam name="key" value="#{containerFileDownloadAction.key}"/&gt; &lt;f:event listener="#{containerFileDownloadAction.download}" type="preRenderComponent" /&gt; &lt;/f:metadata&gt; &lt;/f:view&gt; </code></pre> <pre class="lang-java prettyprint-override"><code>@Named @RequestScoped public class ContainerFileDownloadAction { private String key; @Inject @DefaultServiceInstance private Instance&lt;ContainerService&gt; containerService; public void download() throws IOException { final FacesContext facesContext = FacesContext.getCurrentInstance(); // same code as previously ... facesContext.responseComplete(); } /// getter / setter for key ... } </code></pre> <p>But then, I had a <code>java.lang.IllegalStateException: "getWriter()" has already been called for this response</code>.</p> <p>Logic as when a view initiates, it uses getWritter to initialize the response.</p> <p>So I created a Servlet which does the work and created the following <code>h:outputLink</code> :</p> <pre class="lang-html prettyprint-override"><code>&lt;h:outputLink value="#{facesContext.externalContext.request.contextPath}/download/"&gt; &lt;h:outputText value="Download"/&gt; &lt;f:param name="key" value="#{file.key}"/&gt; &lt;/h:outputLink&gt; </code></pre> <p>But even if that last technique gives me a bookmarkable URL for my file, it is not really "JFS 2" ...</p> <p>Do you have some advice ?</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload