April 30th 2008 07:34 pm
JSF PhaseListeners in Action - image rendering, back button, simple security
I am going to show you how to use Java Server Faces phase listeners to do three very common things:
- Rendering images located on the filesystem
- Solving the back button problem
- Simple application security
Rendering images
For the sake of the example, let’s assume a few things first:
- You have mapped faces servlet to url pattern *.jsf
- You have web application located at http://www.yourdomain.com/yourapp/
- Your application has the functionality for uploading JPG images and for rendering these images in the other parts of the application
- Uploaded images are located in /opt/yourapp/images/ on the server
- You want to be able to access/render those images via http://www.yourdomain.com/yourapp/image/imageNameWithoutExtension.jsf
First, let’s write the ImagePhaseListener:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | package com.yourcompany.yourapp.custom; import javax.faces.event.PhaseEvent; import javax.faces.event.PhaseListener; import javax.faces.event.PhaseId; import javax.faces.context.FacesContext; import javax.servlet.http.HttpServletResponse; import javax.imageio.ImageIO; import java.io.File; public class ImagePhaseListener implements PhaseListener { public final static String IMAGE_VIEW_ID = "image"; public void afterPhase(PhaseEvent event) { FacesContext context = event.getFacesContext(); String viewId = context.getViewRoot().getViewId(); if (viewId.startsWith("/" + IMAGE_VIEW_ID + "/")) { String imageFileName = viewId.substring(viewId.lastIndexOf("/") + 1, viewId.lastIndexOf(".")) + ".jpg"; String imagesRoot = context.getExternalContext().getInitParameter("imagesRoot"); handleImageRequest(context, new File(imagesRoot + imageFileName)); } } public void beforePhase(PhaseEvent event) { // Do nothing here… } public PhaseId getPhaseId() { return PhaseId.RESTORE_VIEW; } private void handleImageRequest(FacesContext context, File imageFile) { HttpServletResponse response = (HttpServletResponse) context.getExternalContext().getResponse(); response.setContentType("image/jpeg"); try { ImageIO.write(ImageIO.read(imageFile), "jpeg", response.getOutputStream()); } catch (Exception exception) { // log this } context.responseComplete(); } } |
Relevant part of the web.xml:
<context-param> <param-name>imagesRoot</param-name> <param-value>/opt/yourapp/images/</param-value> </context-param>
Relevant part of the faces-config.xml:
<lifecycle> <phase-listener>com.yourcompany.yourapp.custom.ImagePhaseListener</phaselistener> </lifecycle>
Now, if you have the image /opt/yourapp/images/img1.jpg you can render it via http://www.yourdomain.com/yourapp/image/img1.jsf
If you want to access the image from the page, you can use something like this:
<img src="#{facesContext.externalContext.request.contextPath}/image/img1.jsf" alt="" />Back button problem
Let’s write the CacheControlPhaseListener:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | package com.yourcompany.yourapp.custom; import javax.faces.context.FacesContext; import javax.faces.event.PhaseEvent; import javax.faces.event.PhaseId; import javax.faces.event.PhaseListener; import javax.servlet.http.HttpServletResponse; public class CacheControlPhaseListener implements PhaseListener { public PhaseId getPhaseId() { return PhaseId.RENDER_RESPONSE; } public void afterPhase(PhaseEvent event) {} public void beforePhase(PhaseEvent event) { FacesContext facesContext = event.getFacesContext(); HttpServletResponse response = (HttpServletResponse) facesContext.getExternalContext().getResponse(); response.addHeader("Pragma", "no-cache"); response.addHeader("Cache-Control", "no-cache"); response.setHeader("Cache-Control", "no-store"); response.addHeader("Cache-Control", "must-revalidate"); } } |
Relevant part of the faces-config.xml:
<lifecycle> <phase-listener>com.yourcompany.yourapp.custom.CacheControlPhaseListener</phaselistener> </lifecycle>
Simple application security
Let’s write the AdminPhaseListener:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | package com.yourcompany.yourapp.custom; import javax.faces.event.PhaseListener; import javax.faces.event.PhaseEvent; import javax.faces.event.PhaseId; import javax.faces.context.FacesContext; public class AdminPhaseListener implements PhaseListener { public void afterPhase(PhaseEvent event) { FacesContext context = event.getFacesContext(); String viewId = context.getViewRoot().getViewId(); // Protect every page under the admin folder boolean onProtectedArea = viewId.startsWith("/admin/"); // If not on the protected area - return if (!onProtectedArea) { return; } // login page is login.jsf under the admin folder boolean onLoginPage = viewId.startsWith("/admin/login."); /* Admin is logged in if there is an object in the session under the key "admin_user" Don't forget to put that object into the session after successful login! */ boolean isLoggedIn = context.getExternalContext().getSessionMap().get("admin_user") != null; if (!isLoggedIn && !onLoginPage) { // Send user to the login page ("login" is from-outcome in the face-config.xml) goTo(context, "login"); } // Forvard admin to the main admin page if (isLoggedIn && onLoginPage) { // ("admin" is from-outcome in the face-config.xml) goTo(context, "admin"); } } private void goTo(FacesContext ctx, String where) { ctx.getApplication().getNavigationHandler().handleNavigation(ctx, null, where); } public void beforePhase(PhaseEvent event) { //Do nothing here… } public PhaseId getPhaseId() { return PhaseId.RESTORE_VIEW; } } |
Relevant part of the faces-config.xml:
<lifecycle> <phase-listener>com.yourcompany.yourapp.custom.AdminPhaseListener</phaselistener> </lifecycle>
Simple as that.
If you enjoyed this post, make sure you subscribe to my RSS feed!
6 Comments »
djo.mos on 30 Apr 2008 at 8:48 pm #
Marvelous !
Many thanks for sharing this
GAME OVER - Java Server Faces | ComeSolveGo on 03 May 2008 at 5:08 pm #
[...] Back button problem. You have to fix that on your own. I have wrote one solution. [...]
Darryl on 13 May 2008 at 4:23 pm #
Hello there,
Thanks for that one.. I’m using a Jboss Seam framework and I was having a huge problem with lists which I had outjected.. and that little CacheControlPhaseListener made everything work just fine.. now the back-button sends every page to the Home page..
Thanks!!
Darryl
techie on 13 Jun 2008 at 10:57 pm #
Thanks for sharing, no more back button problem.
“com.yourcompany.yourapp.custom.AdminPhaseListener”
the is to be
rkaw on 20 Jun 2008 at 11:42 am #
Hey Guys,
I can see from comments above that there are some people who used that code and confirm that it works.
Unfortunately CacheControlPhaseListener does not work on my application for both FF 3.0 and 2.0.x.
I use JSF RI 1.2_07, apache-tomcat-6.0.16 and Eclipse Europa
I put server in debug mode, set breakpoint at the top of beforePhase method and can confirm that headers are being added to response instance.
I also installed liveHTTPHeaders plugin in FF and can see that headers are presenet,
but still can click back button and previously displayed page gets loaded.
Am I missing anything ?
Thanks in advance.
Cheers
Rafal
Refresh current JSF page « The guy who does not speak… on 14 Jul 2008 at 7:30 am #
[...] your page doesn’t get refreshed due to browser caching, you can set appropriate headers to response in PhaseListener (check [...]