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!
3 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