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!


3 Comments »

3 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

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