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:

  1. You have mapped faces servlet to url pattern *.jsf
  2. You have web application located at http://www.yourdomain.com/yourapp/
  3. Your application has the functionality for uploading JPG images and for rendering these images in the other parts of the application
  4. Uploaded images are located in /opt/yourapp/images/ on the server
  5. 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 »

6 Responses to “JSF PhaseListeners in Action - image rendering, back button, simple security”

  1. djo.mos on 30 Apr 2008 at 8:48 pm #

    Marvelous !
    Many thanks for sharing this :)

  2. 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. [...]

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

  4. 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

  5. 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

  6. 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 [...]

Trackback URI | Comments RSS

Leave a Reply

« Resize images with Java - high quality and working solution | The quickest CSS and PNG alpha transparency fix for IE (internet explorer) »

  • Calendar

    April 2008
    M T W T F S S
        May »
     123456
    78910111213
    14151617181920
    21222324252627
    282930