Wednesday, 2 December 2009

Friendly paths to access dynamic JSF content

JSF 1 is HTTP POST based and, as such, makes it hard to generate friendly/RESTful URLs that are meaningful to the user. Try to serve something like a content management system (CMS) using JSF and you immediately run into problems. However if you write a custom ViewHandler, you can translate the virtual url, such as /page/about/contactUs, into a physical page (view), e.g. /dynamicContent.xhtml.

All cases where the ViewHandler references the viewId (which is just the full filename of the page its executing/referencing), need to be mapped correctly so that JSF opens the page that exists, while the user sees the content specified by the URL that maps to a database for example.

Below is the view handler that wraps around the Facelets view handler to provide the described functionality.:
/**
 * View Handler that wraps around first Facelets then the default handler, to translate the virtual address of the content page to the physical xhtml
 * page that will serve it.
 * 
 * @author Chris Watts
 */
public class ParameterisedPathViewHandler extends ViewHandlerWrapper
{
   /** servletmapping for physical page */
   private static final String PAGE_URL = "/dynamicPage.jsf";
   /** physical page file name */
   private static final String PAGE_FILE_NAME = "/dynamicContent.xhtml";
   /** servletmapping prefix for served pages */
   private static final String PAGE_PREFIX = "/page";
   /** richfaces exclusion */
   private static final String A4J_PREFIX = "/page/a4j/";
   private ViewHandler wrappedHandler;

   /**
    * Constructor taking the previous view handler in the chain (called by JSF implementation).
    * @param defaultHandler previous handler in the chain
    */
   public ParameterisedPathViewHandler(ViewHandler defaultHandler)
   {
      //put facelets in the chain.
      //If using richfaces the context param org.ajax4jsf.VIEW_HANDLERS
      //should be set to: "com.sun.facelets.FaceletViewHandler,net.devgrok.jsf.ParameterisedPathViewHandler"
      //and the below code commented out.

      FaceletViewHandler faceletHandler = new FaceletViewHandler(defaultHandler);
      this.wrappedHandler = faceletHandler;
//      this.wrappedHandler = defaultHandler; // to use richfaces chaining
   }

   @Override
   protected ViewHandler getWrapped()
   {
      return wrappedHandler;
   }

   @Override
   public UIViewRoot createView(FacesContext context, String viewId)
   {
      return wrappedHandler.createView(context, resolveUrl(context, viewId));
   }

   private boolean isContentPage(FacesContext context, String viewId)
   {
      String servlet = context.getExternalContext().getRequestServletPath();
      if (servlet == null || servlet.length() == 0)
         return false;
      if (servlet.startsWith(A4J_PREFIX))
         return false;
      return servlet.startsWith(PAGE_PREFIX);
   }

   //Determine what physical page will serve the request.
   private String resolveUrl(FacesContext context, String viewId)
   {
      if (!isContentPage(context, viewId))
         // not content page so use original viewId
         return viewId;

      //store pageId in request map for later retrieval
      Map<String, Object> requestMap = context.getExternalContext().getRequestMap();
      String pageId;
      //pageId is the document to be served
      pageId = viewId.substring(1);
      requestMap.put("pageId", pageId);
      return PAGE_FILE_NAME;
   }

   //If is one of the dynamic content pages then actual url of xhtml file to handle it.
   @Override
   public String getActionURL(FacesContext context, String viewId)
   {
      if (isContentPage(context, viewId))
         return getDynamicPageUrl(context, viewId);
      else
         return wrappedHandler.getActionURL(context, viewId);
   }

   private String getDynamicPageUrl(FacesContext context, String viewId)
   {
      return context.getExternalContext().getRequestContextPath() + PAGE_URL;
   }

   @Override
   public UIViewRoot restoreView(FacesContext context, String viewId)
   {
      return wrappedHandler.restoreView(context, resolveUrl(context, viewId));
   }
}

Resources

Download the source containing the above class and example usage.

Thursday, 19 November 2009

Undeploying Google Guice jar file locked

When trying to undeploy an webapp that uses Google Guice from an webapp server like Tomcat, you may notice that it doesn't undeploy properly. The problem seems to be related to com.google.inject.internal.Finalizer, which is a Thread created to clean up after Guice is finished. However the thread doesn't stop properly.

My solution is to modify this class (by just adding that individual source file to the project as I don't want to rebuild the whole project) adding a stop method that can be call from a ServletContextListener.contextDestroyed().

My modified Finializer:
private boolean stop = false;

/**
 * Loops continuously, pulling references off the queue and cleaning them up.
 */
//  @SuppressWarnings("InfiniteLoopStatement")
@Override
public void run() {
  try {
    while (!stop) {
      try {
        cleanUp(queue.remove());
      } catch (InterruptedException e) { /* ignore */ }
    }
  } catch (ShutDown shutDown) { /* ignore */ }
}

public void stopThread() {
  this.stop = true;
  this.interrupt();
}

Then from listener (in this case I've modified GuiceContextListener from GuiceSF, which seems to have now disappeared but you could write your own from scratch), you need to stop the thread:
public class GuiceContextListener implements ServletContextListener {
  public void contextDestroyed(ServletContextEvent contextEvent)
  {
    ServletContext context = contextEvent.getServletContext();
    //start custom code
    
    //first use the GuiceyFruit Injectors class to notify all objects their being destroyed
    try
    {
      Injector injector = getInjector(context);
      Injectors.close(injector);
    }
    catch (CloseFailedException e)
    {
      LOG.log(Level.SEVERE, "Guicesf finhised", e);
    }
    
    ThreadGroup root = Thread.currentThread().getThreadGroup().getParent();
    while (root.getParent() != null)
    {
      root = root.getParent();
    }
  
    //now find the thread
    Thread thread = getThread(root, Finalizer.class.getName());
    if (thread == null)
    {
      LOG.log(Level.INFO, "finalizer not found");
    }
    else
    {
      LOG.log(Level.INFO, "Stopping finalizer");
      callMethod(thread, "stopThread");
    }
    //end custom code
    context.removeAttribute(Injector.class.getName());
    LOG.log(Level.INFO, "Guicesf finished");
  }

  private static Thread getThread(ThreadGroup group, String name)
  {
    int num = group.activeCount();
    Thread[] threads = new Thread[num * 2];
    num = group.enumerate(threads, true);
    for (int i = 0; i < num; i++)
    {
      Thread thread = threads[i];
      if (thread == null)
      {
      }
      else if (thread.getName().equals(name))
      {
        return thread;
      }
    }
    return null;
  }

  private static void callMethod(Object obj, String methodName)
  {
    try
    {
      Class clazz = obj.getClass();
      Method method = clazz.getMethod(methodName, (Class[]) null);
      method.invoke(obj, (Object[]) null);
    }
    catch (Exception e)
    {
      LOG.log(Level.SEVERE, "callMethod caught exception", e);
    }
  }

Download the modified source: undeploy_guice-src.zip

Other people have noticed the problem, which trace it to problems with FinalizableReferenceQueue. There is an attached patch which resolves the issue of having to manually stop the thread.

Sunday, 25 October 2009

Actions not being called when using 2 view tags

I had a problem where a command button's action was not being called on the managed bean.

When using tiles inside JSF, you may have it set up so that you have a template.jsp and a body.jsp.
The template.jsp, using tiles inserts body.jsp.

template.jsp defines a <f:view> tag as per normal.
Inside body.jsp, whilst you don't technically need a <f:view> tag, you may get some component (namely f:phaseListener) complaining about not having a view root, so you does it asks and defines the <f:view> at the start of the file.

You load up the page, everything renders correctly and you click a submit button. The form validation errors appear as normal, so you fill in some values and click submit again.
This time, NOTHING.
So you try turning on all the logging and still nothing seems wrong.

This happened to me. I wasted quiet a few hours on this one: working out how to log things correctly using eclipse/tomcat and get Java Util Logging working etc. After a ton of breakpoints, it appeared that there were no 'events' cued for the view root to invoke in the Invoke Application Phase. Some more breakpoints showed that the button was being decoded and an event was being added to the queue. However it wasn't in the main view root. Strange.

After some pondering I decided that the problem must be that the included page wasn't sending the events up.

So the solution:
When using tiles, inside the included page use <f:subview id="'body'"> not <f:view>.

However, this means that you can't use a <f:phaseListener> tag in the included page.

I was using mojarra-1.2_12.

Wednesday, 2 September 2009

Calling JSF actions by URL

In JSF 1, there is no provided functionality to invoke an action (by this I mean a java function, not a JSF action invoked by a component). JSF 2 adds functionality to support this, through f:viewParam / f:event and the PreRenderViewEvent.

However in JSF 1, you can bind a phase listener to a specific page using the f:phaseListener tag to call the code before the Render Phase. To make sure that the code is only called when the page is access via the url and not from a postback caused by a component you can use the function added in JSF 1.2: ResponseStateManager.isPostback(FacesContext context).

First off we configure the phase listener in the page (action.xhtml):
<html xmlns="http://www.w3.org/1999/xhtml"
 xmlns:ui="http://java.sun.com/jsf/facelets"
 xmlns:f="http://java.sun.com/jsf/core">
<f:phaselistener binding="#{urlActionBean.listener}" />

Next is the phase listener and action code:
public class UrlActionBean
{
   /** value of request paramer somevalue=value */
   @NamedEL("param.somevalue")
   private String someValue;
   
   /**
    * Listener activated on render phase calling when not postback.
    */
   public PhaseListener getListener()
   {
      return new PhaseListener() {
         public void beforePhase(PhaseEvent event)
         {
            //only ever call from a 'GET' request (ie not action pressed).
            if (!isPostBack())
               processAction();
         }

         public PhaseId getPhaseId()
         {
            return PhaseId.RENDER_RESPONSE;
         }
      
         public void beforePhase(PhaseEvent event)
         {
         }
      };
   }

   public static boolean isPostBack()
   {
      FacesContext context = FacesContext.getCurrentInstance();
      return context.getRenderKit().getResponseStateManager().isPostback(context);
   }
      
   /**
    * Called after values injected, automatically calls
    */
   public void processAction()
   {
      //only do something if value provided
      if (someValue == null || someValue.length() == 0)
      {
         //ignore
         return;
      }

      String forward = null;
      String viewId = WebUtil.viewId();
      // do action based on viewId. This allows the bean to be used by multiple pages.
      // store result of action
      if ("action".equals(viewId))
         forward = doAction();
         
      //forward to page
      if (forward != null)
      {
         redirect(forward);
         return;
      }
   }
   
   /**
    * Causes the navigation handler to switch pages / send redirect.
    * @param outcome page result mapped in faces-config.xml
    */
   public static void redirect(String outcome)
   {
      FacesContext context = FacesContext.getCurrentInstance();
      context.getApplication().getNavigationHandler().handleNavigation(context, null, outcome);
   }   
}

The listener is a render phase listener, that only calls the action handling when it isn't a postback. The action handling code then checks that the required parameter is supplied and then calls an action based on the viewId. This allows for multiple views to use the same bean, but you could have one bean per url and hence not need this check. The field someValue, gets injected the request parameter named 'somevalue' and since it is injected the bean needs to be request scoped as the injection only occurs when the bean is first created.

In this I use the Guice injection of EL expressions described here, to inject request parameters, however they could be looked up using the ELContext if you can't use this method.

Wednesday, 5 August 2009

Eclipse Plugins

After having my elcipse install crashing several times, I thought it a good idea to compile a easily accessible list of plugins I use.

Log4E

Allows loggers to be added to classes, replaces System.out and printStackTrace etc
Website: http://log4e.jayefem.de/content/view/3/2/
Update site: http://log4e.jayefem.de/update

Log4Eclipse

Log4j socket appender host - allows log4j to send log messages directly to eclipse (no need to inspect log files). I have rebuilt it using log4j 1.2.15 see below.
Website: http://www.nitwit.de/log4eclipse/
My build: log4eclipse_0.4.0.jar

Subclipse

Subversion client
Website: http://subclipse.tigris.org/
v1.6 Update site: http://subclipse.tigris.org/update_1.6.x
v1.8 Update site: http://subclipse.tigris.org/update_1.8.x

JBoss Tools

Tools for JBoss server and JSF/Richfaces/Facelets editors. Plus a lot of others (I just use the richfaces plugin).
Website: http://www.jboss.org/tools
Update site: http://download.jboss.org/jbosstools/updates/stable/

TestNG

Plugin to allow execution of testNg (and Junit) tests directly from eclipse.
Website: http://testng.org
Update site: http://beust.com/eclipse

M2Eclipse

Maven integration with eclipse, I mainly use it as a pom file editor.
Website: http://eclipse.org/m2e
Update site: http://download.eclipse.org/technology/m2e/releases

Html Tidy

Based on JTidy, an HTML formatter for eclipse because eclipse's xml/html formatter sucks.
Website: http://eclipsetidy.sourceforge.net/
Download: Zipped update-site

JUtils toString Generator

Website: http://eclipse-jutils.sourceforge.net/
Download: Extract into eclipse directory

FindBugs

A useful static code analysis for finding common coding errors.
Website: http://findbugs.sourceforge.net/
Update site: http://findbugs.cs.umd.edu/eclipse

Jira integration to Mylyn

Wesite atlassian.com
Update site (indigo): http://update.atlassian.com/atlassian-eclipse-plugin/e3.7

Thursday, 30 July 2009

Multiple Log4J instances in a WebApp

Many times I've come across the classic problem of different logging instances in an web app server. Most of them involve classloader conflicts:
  • The webapp has log4j.jar but the webapp has bundled log4j.jar as well and you get errors.
  • OR you have the log4j.jar in the application server's shared library directory and you have 2 webapps but the configuration conflict - or even just a configuration for the application server itself and one for the webapp.

I've finally come across a solution for log4j. It involves using a repository selector based on a context, this is fully described here - Supporting the log4j RepositorySelector in Servlet Containers.

So the solution to stop conflicts and independently configure each application is this.
First put ALL logging jars in the shared library directory including (log4j.jar, slf4j-api.jar, slf4j-log4j.jar, commons-logging.jar, etc). This ensures that if they one of the libraries is loaded in the main classloader it can access the other jars.
Next is a little extension for log4j: log4j-contrib. Put its jar into the shared library as well.

Next you have to configure each individual app. Add the following to the web.xml (at the top so it is the first called).
<!-- Use log4j.properties from the classpath -->
<context-param>
<param-name>log4jConfigLocations</param-name>
<param-value>logj.properties</param-value>
</context-param>
<listener>
<listener-class>com.mathieucarbou.log4j.web.ContextLoggingListener</listener-class>
</listener>


If you want to have Tomcat use log4j (not just the apps), then add the following configuration:

For catalina.bat add somewhere in the file (line 119):
rem JavaUtilLogging to SLF4J required to be on classpath.
set CLASSPATH=%CLASSPATH%;%CATALINA_HOME%\lib\jul-to-slf4j-1.5.6.jar;%CATALINA_HOME%\lib\slf4j-api-1.5.6.jar;%CATALINA_HOME%\lib\slf4j-log4j12-1.5.6.jar;%CATALINA_HOME%\lib\log4j-1.2.15.jar

Or under linux, catalina.sh add somewhere appropriate (line 163):
# JavaUtilLogging to SLF4J required to be on classpath.
CLASSPATH="$CLASSPATH":"$CATALINA_HOME"/lib/jul-to-slf4j-1.5.6.jar:"$CATALINA_HOME"/lib/slf4j-api-1.5.6.jar:"$CATALINA_HOME"/lib/slf4j-log4j12-1.5.6.jar:"$CATALINA_HOME"/lib/log4j-1.2.15.jar

Additionally if you put the log4j.properties in somewhere other than CATALINA_HOME/lib then you'll need to add that directory to the classpath. e.g.
rem if you put it in /conf
set CLASSPATH=%CLASSPATH%;%CATALINA_HOME%\conf

Now alter conf/logging.properties and replace the entire contents with:
handlers = org.slf4j.bridge.SLF4JBridgeHandler
.handlers = org.slf4j.bridge.SLF4JBridgeHandler

And that's it for the command line.

Running it from Eclipse

Eclipse has a guide how to set it up at Enable JULI logging in Eclipse. To set it up, with the configuration files stored in the workspace under Servers for a server named "Tomcat6-Localhost" add the following to the end of the Server's launch configuration arguments:
-Dlog4j.configuration="log4j.properties"
-Djava.util.logging.config.file="${workspace_loc:Servers/Tomcat6-Localhost-config/logging.properties}"
-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
Copy in logging.properties and log4.properties into the workspace configuration directory as per the command line version.

Note: the argument -Dlog4j.configuration, sets the file name to search for on the Tomcat start up classpath. You CANNOT specify the exact file to load, instead you have to add the folder as a class folder (add external folder in eclipse), in order for it to be located.

Under the launch configuration classpath, add the following jars/folders:
  • jdk:lib/tools.jar
  • bin/bootstrap.ja
  • lib/jul-to-slf4j-1.5.6.jar
  • lib/slf4j-api-1.5.6.jar
  • lib/slf4j-log4j12-1.5.6.jar
  • lib/log4j-1.2.15.jar
  • workspace:Servers/Tomcat6-Localhost-config

Monday, 13 July 2009

Access Control in JSF using a PhaseListener

After doing a quick search of the web, I did not find any nice solutions to implementing access control in JSF. Using a servlet filter mapping seemed inadequate, and there wasn't any obvious place to start.

I initially tried using a custom NavigationHandler, however that is only used after an action is performed (e.g. #{someBean.action}), and not for directly accessing a URL (e.g. typing in /test.faces). Some more searching revealed that a PhaseListener was the place to do it. After checking the JSF lifecycles I determined that RESTORE_VIEW was the correct place to do it - ALL pages go through at least the RESTORE_VIEW and RENDER_VIEW phases.
You can check the viewId in the afterPhase (as the view has been loaded in this phase, hence can't check in beforePhase) and redirect using the navigation handler as nesecary.

Below is my implementation of it. I used a flexible inclusion/exclusion filter so I can make the rules as complex as I want. This implementation determines the highest level required for each URL and checks the current security level and redirects accordingly.
Alternatively, you could check each level inidividually - starting with LOGGED_IN, checking if the user is logged in and working up.
package devgrok.jsf;

import static devgrok.jsf.AccessControlPhaseListener.AccessLevel.ADMIN;
import static devgrok.jsf.AccessControlPhaseListener.AccessLevel.LOGGED_IN;
import static devgrok.jsf.AccessControlPhaseListener.AccessLevel.NONE;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;
import javax.servlet.http.HttpSession;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sun.faces.util.MessageFactory;

import devgrok.jsf.SessionForm;
import devgrok.jsf.UrlFilter;

/**
 * Phase Listener that checks the viewId (URL) against a set of filters to determine the required access level. If the
 * correct level is not there then redirect.
 * 
 * See {@link UrlFilter} for details on the url matching.
 * 
 * @author Chris Watts 2009
 * 
 */
public class AccessControlPhaseListener implements PhaseListener
{
  /** Logger for this class */
  private static final Logger log = LoggerFactory.getLogger(AccessControlPhaseListener.class);

  /** */
  private static final long serialVersionUID = 1L;
  private final static String SESSION_BEAN = "sessionBean";
  private final HashMap<AccessLevel, List<UrlFilter>> levelFilters = new HashMap<AccessLevel, List<UrlFilter>>();

  public enum AccessLevel
  {
    NONE, LOGGED_IN, USER_ACTIVE, ADMIN;
  }

  /**
    * 
    */
  public AccessControlPhaseListener()
  {
    initLevels();
    
    requires(LOGGED_IN)
      .include("*")
      .exclude("/index.xhtml")
      .exclude("/login.xhtml")
      .exclude("/user/newUser.xhtml");

    requires(USER_ACTIVE)
      .include("/user/*")
      .exclude("/user/newUser.xhtml");

    requires(ADMIN)
      .include("/admin/*");
  }

  private void initLevels()
  {
    AccessLevel[] levels = AccessLevel.values();
    for (int i = 1; i < levels.length; i++)
    {
      levelFilters.put(levels[i], new ArrayList<UrlFilter>());
    }
  }

  private UrlFilter requires(AccessLevel level)
  {
    //ALL is default
    if (level == NONE)
      return null;

    UrlFilter filter = new UrlFilter();
    List<UrlFilter> list = levelFilters.get(level);
    list.add(filter);
    return filter;
  }

  /*
   * (non-Javadoc)
   * 
   * @see javax.faces.event.PhaseListener#afterPhase(javax.faces.event.PhaseEvent)
   */
  public void afterPhase(PhaseEvent event)
  {
    try
    {
      //check have correct access
      FacesContext context = event.getFacesContext();
      HttpSession session = (HttpSession) context.getExternalContext().getSession(true);
      SessionForm sessionBean = (SessionForm) session.getAttribute(SESSION_BEAN);
      if (sessionBean == null)
      {
        log.error("Could not obtain instance of sessionBean");
        return;
      }

      //can't use this here. only valid at render response phase?
      String viewId = context.getViewRoot().getViewId();
      AccessLevel required = requiredLevel(viewId);
      log.debug("Required level={} for viewId={}", required, viewId);

      //check if page require access:
      switch (required) {
      case NONE:
        break;
      case LOGGED_IN:
        if (!sessionBean.isLoggedIn())
          redirectLogin(event.getFacesContext(), sessionBean);
        break;
      case USER_ACTIVE:
        if (!sessionBean.isActive())
          redirectActive(event.getFacesContext());
        break;
      case ADMIN:
        if (!sessionBean.isAdmin())
          redirectAdmin(event.getFacesContext());
        break;
      default:
        //error
        log.error("huh?");
        throw new IllegalArgumentException("Not a valid access level");
      }
    }
    catch (Exception e)
    {
      // TODO Auto-generated catch block
      log.error("beforePhase caught exception", e);
    }

  }

  /*
   * (non-Javadoc)
   * 
   * @see javax.faces.event.PhaseListener#beforePhase(javax.faces.event.PhaseEvent)
   */
  public void beforePhase(PhaseEvent event)
  {

  }

  private void redirectLogin(FacesContext context, SessionForm sessionForm)
  {
    //trigger login popup to be shown on render.
    sessionForm.logIn();
    addError(context, "access.loginrequired");
    context.getApplication().getNavigationHandler().handleNavigation(context, null, "index");
  }

  private void redirectActive(FacesContext context)
  {
    addError(context, "access.activerequired");
    context.getApplication().getNavigationHandler().handleNavigation(context, null, "userActivate");
  }

  private void redirectAdmin(FacesContext context)
  {
    addError(context, "access.adminrequired");
    context.getApplication().getNavigationHandler().handleNavigation(context, null, "home");
  }

  /**
   * Add keyed error/message.
   * 
   * @param level
   * @param key
   *           message key
   */
  private void addError(FacesContext context, String key)
  {
    FacesMessage fMessage = MessageFactory.getMessage(key);
    if (fMessage != null)
    {
      FacesContext facesContext = FacesContext.getCurrentInstance();
      fMessage.setSeverity(FacesMessage.SEVERITY_ERROR);
      facesContext.addMessage(null, fMessage);
    }
  }

  /**
   * Checks defined filters for view id, checks starting at the highest level down to NONE.
   * 
   * @return the matching level or {@link AccessLevel#NONE} if none matching.
   */
  private AccessLevel requiredLevel(String viewId)
  {
    AccessLevel[] levels = AccessLevel.values();
    for (int i = levels.length - 1; i > 0; i--)
    {
      if (checkLevel(levels[i], viewId))
        return levels[i];
    }

    return AccessLevel.NONE;
  }

  private boolean checkLevel(AccessLevel level, String viewId)
  {
    return matchUri(levelFilters.get(level), viewId);
  }

  private boolean matchUri(List<UrlFilter> list, String uri)
  {
    for (UrlFilter filter : list)
    {
      if (filter.matches(uri))
        return true;
    }
    return false;
  }

  /*
   * (non-Javadoc)
   * 
   * @see javax.faces.event.PhaseListener#getPhaseId()
   */
  public PhaseId getPhaseId()
  {
    //ALL access go through RESTORE_VIEW and RENDER_VIEW (even direct url)
    return PhaseId.RESTORE_VIEW;
  }

}
package devgrok.jsf;

import java.util.ArrayList;
import java.util.regex.Pattern;

/**
 * An inclusion/exclusion filterset, similar to ant's fileset but does not support directories in the same style(**,
 * etc).
 * 
 * For example:
 * <ul>
 * <li>/servlet/* matches all urls starting with "/servlet/" e.g. /servlet/this.html
 * <li>*.do matches all urls that end in ".do" - e.g. mypage.do
 * <li>/servlet/*.do matches all urls starting with "/servlet/" and end in ".do"  - e.g. /servlet/mypage.do
 * </ul>
 * 
 * @author Chris Watts 2009
 * 
 */
public class UrlFilter
{
  private ArrayList<Pattern> include = new ArrayList<Pattern>();
  private ArrayList<Pattern> exclude = new ArrayList<Pattern>();

  public UrlFilter()
  {

  }

  /**
   * Include the wildcard(*) built pattern.
   * 
   * @param pattern
   * @return
   */
  public UrlFilter include(String pattern)
  {
    include.add(generateExpression(pattern));
    return this;
  }

  /**
   * Exclude the wildcard(*) built pattern.
   * 
   * @param pattern
   * @return
   */
  public UrlFilter exclude(String pattern)
  {
    exclude.add(generateExpression(pattern));
    return this;
  }

  /**
   * Checks to see if uri matches at least ONE inclusion filter and doesn't match ANY exclusion filters.
   * 
   * @param uri
   * @return
   */
  public boolean matches(String uri)
  {
    boolean match = false;

    //check inclusions
    for (Pattern pattern : include)
    {
      match = match || pattern.matcher(uri).matches();
    }

    if (!match)
      return false;

    //check exclusions
    for (Pattern pattern : exclude)
    {
      match = match && !pattern.matcher(uri).matches();
    }
    return match;
  }
  
  /** regular expression special character */
  private static char[] specialChars = { '[', '\\', '^', '$', '.', '|', '?', '*', '+', '(', ')' };

  /**
   * 
   * @param input
   * @return
   */
  private static Pattern generateExpression(String input)
  {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < input.length(); i++)
    {
      char letter = input.charAt(i);
      if (letter == '*')
      {
        sb.append(".*");
      }
      else if (contains(specialChars, letter))
      {
        sb.append("\\" + letter);
      }
      else
      {
        sb.append(letter);
      }
    }
    return Pattern.compile(sb.toString());
  }

  private static boolean contains(char[] array, char value)
  {
    if (array == null || array.length == 0)
    {
      return false;
    }

    for (int i = 0; i < array.length; i++)
    {
      char o = array[i];
      if (o == value)
      {
        return true;
      }
    }

    return false;
  }
}

Update 2010/10/21: Here is a zip of the source

JSF - Injecting Managed Beans into Managed Beans

Using Google Guice and JSF with something like GuiceSF to inject the JSF managed beans (backing beans) gives you a lot of power over the injection. However, by doing so JSF no longer resolves the EL expressions from faces-config.xml and injects them into the managed beans. An easy way around this, and somewhat cleaner, is to create an annotation that is 'bound' to an EL expression. To do so requires the use of GuiceyFruit's extensions to Guice.

First create the annotation:
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import com.google.inject.BindingAnnotation;

/** 
 * Injection by expression language.
 * @see com.google.inject.name.Named
 */
@BindingAnnotation
@Target( { FIELD, PARAMETER, METHOD })
@Retention(RUNTIME)
public @interface NamedEL
{
   /** expression language (without the #{}). */
   String value();
}

Next you configure the annotation and the provider inside your module:
public final class GuiceModule extends GuiceyFruitModule
{
   @Override
   protected void configure()
   {
      bindAnnotationInjector(NamedEL.class, ELProvider.class);
   }
}

And finally the provider:
public class ELProvider extends AnnotationMemberProviderSupport<NamedEL>
{
   /** Logger for this class */
   private static final Logger log = LoggerFactory.getLogger(ELProvider.class);

   private Injector injector;
   private FacesContext context;

   @Inject
   public ELProvider(Injector injector)
   {
      this.injector = injector;
   }

   public FacesContext getContext()
   {
      return context;
   }

   @Inject(optional = true)
   public void setContext(FacesContext context)
   {
      this.context = context;
   }

   public boolean isNullParameterAllowed(NamedEL annotation, Method method, Class<?> parameterType, int parameterIndex)
   {
      return false;
   }

   protected Object provide(NamedEL resource, Member member, TypeLiteral<?> requiredType, Class<?> memberType, Annotation[] annotations)
   {
      String name = getName(resource, member);

      Binding<?> binding = Injectors.getBinding(injector, Key.get(requiredType, Names.named(name)));
      if (binding != null)
      {
         return binding.getProvider().get();
      }

      try
      {
         if (context == null)
         {
            context = FacesContext.getCurrentInstance();
         }
         return getBean(name, requiredType.getRawType());
      }
      catch (ProvisionException e)
      {
         throw e;
      }
      catch (Exception e)
      {
         throw new ProvisionException("Failed to find name '" + name + "' ExpressionResolver. Cause: " + e, e);
      }
   }

   String getName(NamedEL resource, Member member)
   {
      String answer = resource.value();
      if (answer == null || answer.length() == 0)
      {
         answer = member.getName();
      }
      if (answer == null || answer.length() == 0)
      {
         throw new IllegalArgumentException("No name defined");
      }
      return answer;
   }
   
   private <T> T getBean(String expr, Class<T> expected)
   {
      try
      {
         Application app = context.getApplication();
         ValueExpression ve = app.getExpressionFactory().createValueExpression(context.getELContext(), "#{" + expr + "}", expected);
         return (T) ve.getValue(context.getELContext());
      }
      catch (RuntimeException e)
      {
         log.error("getBean caught RuntimeException", e);
         throw e;
      }
   }
}
It uses a custom FacesContext provider bound to ServletScopes.REQUEST but because its optional it defaults to looking it up the standard JSF way. This allows startup time initialisation of singletons, which occurs outside the request scope.

Usage

To use it is pretty simple. Say you had a bean called mybean, then the following would inject it during the normal creation time (e.g. when first referencing a session scoped bean).
/** managed bean 'mybean' */
@NamedEL("mybean")
private String mybean;

/** request parameter 'key' */
@NamedEL("param.key")
private String paramKey;
The second reference is a nifty trick which injects the value of the request parameter 'key'.

Resources

Guicesf seems to be down so here are my copies of them (slightly modified). binary source
Source for this example: download source

Wednesday, 8 July 2009

EJB3 Stateful Session Beans with ejbCreate

I had trouble obtaining a stateful session bean using the EJB3 pattern when trying to pass initial data to it. You can use injection to create instances of stateful beans by simply declaring an instance variable in a bean, for example: @EJB MyStatefulLocal mylocal;. But this doesn't allow you to pass any data on creation. The answer is to use a EJBLocalHome interface like in EJB2 but it's a bit complicated with all the annotations. The ejbCreate method from EJB2.1 is called create in the local home interface and called whatever you want in the bean itself (as long as it is annotated with @Init Below is an example. You can download the source here: download source.
/** Local Interface. Dummy interface to enable us to return the Business interface. */
public interface MyStatefulLocalEjb2 extends EJBLocalObject, MyStatefulLocal 
{}

/** Local Business Interface. Put business method definitions here */
public interface MyStatefulLocal
{}

/** Define method(s) which create the EJB. Must be of name create([optional parameters]),  can be anything e.g. createMyBean(). */
public interface MyStatefulLocalHome extends EJBLocalHome
{
   public MyStatefulLocalEjb2 create(MyEntity initialData);
}

/** This is the EJB itself, note that it implements the "Local Business Interface" not the "Local Interface". */
@Stateful
@LocalHome(MyStatefulLocalHome.class)
public class MyStatefulBean implements MyStatefulLocal
{
   /** matches method in {@link MyStatefulLocalHome#create(MyEntity)} */
   @Init
   public void init(MyEntity initialData)
   {
   }
}

/** This shows using it from another session bean or message bean. */
public class SomeBean
{
   @EJB
   private MyStatefulLocalHome home;

   public void obtainStateful()
   {
      MyEntity initialData = new MyEntity();
      MyStatefulLocal myStateful = home.create(initialData);
   }
}

Thursday, 2 July 2009

Reacquiring a Stateful Session Bean on NoSuchEJBException

In the current project I'm working on I'm using a Tomcat server connecting to a remote JBoss instance. We're using a Stateful Session Bean (SFSB) to hold the session information for the current user for authentication/access control purposes. The bean is stored in tomcat within the session (within a session-scoped managed bean actually) and generally it works fine.
However, if the bean is destroyed on the server then, as per the EJB specification, an NoSuchEJBException is thrown. This could be handled individually by catching the exception, printing a user friendly message and getting a new instance of the session bean. However, it you've got several method calls across multiple classes, having this try/catch code throughout all the classes bloats the code and just plain looks ugly.

I wanted a solution where I didn't need to touch the module variable that stored the EJB given to me by jboss - I wanted it to obtain a new instance itself. The reason being, if I have this instance shared across multiple JSF managed beans (some stored in session-scope, some in request), the other classes shouldn't have to know that a new instance of the EJB was obtained.

The solution is to use a nifty feature of Java reflection API - a Proxy that passes calls to a InvocationHandler instance that you write. Actually the EJB instance JBoss's context factory provides you is a Proxy (and the same is true for other app servers), which makes wrapping it even easier. Observe:
First create a class that implements InvocationHandler and a custom interface we'll use to directly access the handler called StatefulProxy:
package devgrok.proxy;

import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import javax.ejb.NoSuchEJBException;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Utility wrapper, automatically obtains a new instance of the session bean when the existing instance is lost. Uses
* jndi to obtain new instance.
* @author Chris Watts 2009
*/
public class JndiRemoteProxyInvocationHandler implements InvocationHandler, StatefulProxy, Serializable
{
/** Logger for this class */
private static final Logger log = LoggerFactory.getLogger(JndiRemoteProxyInvocationHandler.class);

private Proxy remoteBean = null;
private final String jndiName;

/*
* (non-Javadoc)
* 
* @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[])
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
if (method.getDeclaringClass().equals(StatefulProxy.class))
return method.invoke(this, args);

if (remoteBean == null)
getNewInstance();

try
{
//relay it on to JBoss's handler
InvocationHandler handler = Proxy.getInvocationHandler(remoteBean);
return handler.invoke(remoteBean, method, args);
}
catch (NoSuchEJBException e)
{
log.warn("caught exception {}", e.toString());
remoteBean = null;
throw e;
}
}

/**
* Constructs the proxy, obtaining the instance from the given jndi name.
* @param jndiName
*/
public JndiRemoteProxyInvocationHandler(String jndiName)
{
this.jndiName = jndiName;
getNewInstance();
}

/**
* Obtains a new instance of the bean from the context.
* @return
*/
public void getNewInstance()
{
try
{
Context context = new InitialContext();
this.remoteBean = (Proxy) context.lookup("java:/comp/env/" + jndiName);
}
catch (NamingException e)
{
log.warn("caught exception {}", e.toString());
throw new RuntimeException(e);
}
}

public Object getRemoteBean()
{
return remoteBean;
}

public void clearInstance()
{
remoteBean = null;
}
}
Next the interface to provide access to the utility methods:
package devgrok.proxy;

/**
* Interface to allow calls to the InvocationHandler for the stateful bean proxy.
* @author Chris Watts 2009
*/
public interface StatefulProxy<T>
{
/**
* Get a new instance of the underlying class.
*/
public void getNewInstance();

/**
* Returns the remote bean.
* 
* @return
*/
public T getRemoteBean();

/**
* Clears the stored instance of the bean (useful for logout/cleanup).
*/
public void clearInstance();
}
Now all that is left is obtaining an instance. Since I'm using Google Guice I wrote a provider (aka a factory) but you could easily adapt it:
package devgrok.proxy;

import java.lang.reflect.Proxy;

import com.google.inject.Provider;

/**
* A guice provider, providing a proxied EJB looked up by JNDI.
* @author Chris Watts 2009
*/
public class ProxyProvider<T> implements Provider<T>
{
private final String jndiName;
private final Class<T> clazz;

/**
* @param jndiName
* @param clazz
*/
public ProxyProvider(String jndiName, Class<T> clazz)
{
this.jndiName = jndiName;
this.clazz = clazz;
}

/*
* (non-Javadoc)
* 
* @see com.google.inject.Provider#get()
*/
public T get()
{
return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[] { clazz, StatefulProxy.class },
new JndiRemoteProxyInvocationHandler(jndiName));
}

}
To bind it:
bind(MySessionRemote.class).toProvider(new ProxyProvider<MySessionRemote>("ejb/myEjb", MySessionRemote.class)).in(ServletScopes.SESSION);

Sunday, 21 June 2009

Tomcat - load & bind an external naming context

In my previous post Injecting JBoss EJB3 beans into Tomcat6, I used Tomcat's ability to bind EJB's to the local JNDI context by specifying JBoss' NamingContextFactory and the URL to the EJB. However in the process of using it, I discovered a problem with Stateful Session Beans.

I was getting NoSuchEJBException - either after a redeploy to JBoss, or calling the @Remove annotated method on the session bean. A lot of debugging later, I discovered that the cause was that Tomcat was initially storing a ejb ref (org.apache.EjbRef) in the context and when first accessed it looks up the EJB using the factory and then replaces the reference in the context with the instance of the EJB. Every subsequent lookup in the context returns the same instance.

This however breaks the JEE5 contract:
FROM JSR 220: NOTE: When a stateful session bean is looked up or otherwise
obtained through the explicit JNDI lookup mechanisms, the container must
provide a new stateful session bean instance, as required by the Java EE
specification (Section “Java Naming and Directory Interface (JNDI) Naming
Context” [12]).
I initially tried a few hacks to stop Tomcat 6 storing the instance and only keeping the ref, but I was worried about the lag every I required a new instance. Each time it creates a context factory, EJB factory etc to look up the EJB. If you were manually doing the lookup using a custom service locator, you would create a singleton service locator and stores a initial context with the remote app server's details pumped in. Thus everytime you wanted an instance a single call to the existing context would be all that was required.

My solution I felt was to bind an external context naming tree to the local naming tree. That way you could reference any external object easily on the external context with just a @EJB ref. The resulting patch is located here, it is against Tomcat 6.0.20. You can also download my precompiled binary:
catalina.jar
And the source:
apache-tomcat-6.0.20-src

To use it create a context.xml:
<Context>
   <!-- Binds the remote context tree starting at myear to local name java:/comp/env/ejb. -->
   <Resource name="ejb" type="javax.naming.Context" factory="org.jnp.interfaces.NamingContextFactory" URL="jnp://apphost:1099/myear"/>
</Context>
Next if you want to reference a bean which has a @Stateful(mappedName="myear/myBean"), using the following in your JSF/contains managed bean:
public class TomcatManagedBean
{
   @EJB(name="ejb/myBean")
   private MyBeanRemote session;

Thursday, 18 June 2009

Injecting JBoss EJB3 beans into Tomcat6

Injecting EJB's into a managed bean (a bean spawned by appserver, of a JSF managed bean) inside an application server is simple:
public class AppManagedBean
{
   @EJB(mappedName="ejb/myBean")
   private MyBeanRemote session;
The container looks up the mapped bean and injects it. Easy.

However in tomcat it gets a little messy. Tomcat doesn't document how to inject them. It assumes something annotated with @EJB is in the context somewhere and tries to look it up.
It has documented how to lookup ejb's by defining entries in your context.xml or server.xml. But there isn't anything documented for defining EJB's. Looking at the soure it has support for <Ejb/> references but no real clue on how to configure it. So after stepping through the code I came up with the following soloution:

My environment is JBoss 5 running remotely (on localhost). A seperate instance of Tomcat 6. In it I have defined a session bean:
@Stateful(mappedName="userSession")
public class UserSessionBean implements UserSessionRemote 
This bean is used by a JSF backing bean (i.e. a managed bean):
public class TomcatManagedBean
{
   @EJB(name="ejb/myBean")
   private MyBeanRemote session;
In order for this bean to bound to the context, you need to configure Tomcat. There are 2 ways: in the server.xml or in the context.xml
I find that putting it in META-INF/context.xml is the easiest, as it gets reloaded nicely in Eclipse. The name="ejb/myBean" corresponds to the name attribute in the <Ejb/> below. In the URL is the appserver name, port, then the mapped name (it could be myProject/ejb/bean or whatever you feel like) - as long as it matched the mappedName on the EJB.
<Context>
   <!-- Makes Tomcat manage the ejb for you. -->
   <Ejb name="ejb/myBean" type="Session" factory="org.jnp.interfaces.NamingContextFactory" URL="jnp://localhost:1099/userSession" />
</Context>
Update 2010/02/02: Correct the mappedName in AppManagedBean

Sunday, 14 June 2009

Change user name in eclipse

The code comment templates are handy for inserting the author of a class, which is handy in rather large projects. For example, here is my template for types(classes):
/**
* @author ${user}
*
* ${tags}
*/

However under windows (and probably *nix as well) it just inserts the current user name. But I want it always to display my real name - which is more meaningful when employees come and go.

You can change it by passing this argument on the comment line:
You need to put it at the end of your command line.
eclipse.exe -vmargs -Duser.name="You real name"

Friday, 12 June 2009

A few good Guice Tutorials

I came across these rather good Guice tutorials in trying to find a way to integrate Google Guice in an application server (EJB3).

Part 1: a quick intro.
Part 2: method interceptors.

This was the tricky one, how to inject EJB's which are created by the container without having to have every class extend a base class with a @PostConstruct annotated method to do the injecting. The solution was to use the new interceptors introduced into JEE 5.
Part 3 – Integrating Guice with EJB
If you want a little background, see EJB Interceptors.

Saturday, 6 June 2009

CommandButton not calling action when using immediate and rendered

I came across a problem with using a custom command component, that when using immediate it wasn't calling the action I had bound to it. I started resolving the issue and discovered my decode code wasn't being called. I had create a custom button, cannibalising and extending the original CommandButton component. Due to my lack of understanding of setting up renderkits, I gave up and hacked in a renderer:
public class ImageButton extends HtmlCommandButton
{
   private ImageButtonRenderer render = new ImageButtonRenderer();
   
   @Override
   public void decode(FacesContext context)
   {
      render.decode(context, this);
   }
   
   @Override
   public void encodeBegin(FacesContext context) throws IOException
   {
      render.encodeBegin(context, this);
   }
This resulted in decode not always working. I came across this nice summary of how to write a custom component and altered my code accordingly. Decode worked but the action was still not called. Thought the cause was just with immediate="true". So I swtiched to using a standard commandButton for testing.
<h:inputhidden value="#{testBean.add}" immediate="true" />
<h:commandbutton value="Delete" immediate="true" rendered="#{!testBean.add}" />
However the command button was still not being decoded, as I thought immediate on the attribute should work but for the 2nd time I've misunderstood how it works for input components. Immediate on input components only changes priority of validation, it doesn't cause them to update the model any earlier.

UIComponentBase.processDecodes:1018 calls isRendered() - which returns false as the Update Model Values phase hasn't been applied. Which skips the decode of the the component. Which clearly isn't right. Whether it was rendered or not should be remembered from the Render phase during the decode(Apply Request Values) phase. In the following phases it should be calculated from the EL binding. This to me seems a bug in the specification or the implementation.

So how to use a model value for the rendered attribute for a button with immediate on?

The answer use Tomahawk's saveState tag. The state is maintained on the server side, and as long as it is put before the other tags that need it, it will restore the model/attribute to the same value it was after the Render phase during the Apply Request Values phase. Thus when it goes to decode the button, the same values that were used to calculate the rendered during the previous rendering to create this page are used.
<t:savestate value="#{testBean.add}" />
<h:commandbutton value="Delete" immediate="true" rendered="#{!testBean.add}" />

ClassCastException with PortableRemoteObject.narrow() EJB2/EJB3

After switching to JBoss 5 and recreating my EAR project inside eclipse, I started getting ClassCastException:
java.lang.ClassCastException
at com.sun.corba.se.impl.javax.rmi.PortableRemoteObject.narrow(PortableRemoteObject.java:229)
at javax.rmi.PortableRemoteObject.narrow(PortableRemoteObject.java:137)
Object obj = initialContext.lookup(ejbReferenceComponent);
MyEJBRemoteHome home = javax.rmi.PortableRemoteObject.narrow(obj, MyEJBRemoteHome.class);

After looking around, the cause of it is how the app server determines whether to give you a EJBHome instance which you have to use .narrow() then .create() to get a reference to the bean for EJB2 vs returning a proxy to the bean itself for EJB3. JBoss has some information on referencing EJB3 from EJB2 environments and vice-versa.

The summary generally is if the application.xml has the EJB2 header then it gives you an EJBHome interface.
If the ear's ejb-jar has the EJB3 header then it gives you a proxy to the bean itself (no need for narrow/create).

The lack of a more intelligent error from PortableRemoteObject is a bit annoying. However here is a generic solution for cases when your switching between environments:
public <T, S extends EJBHome> T lookupEJB(String ejbReferenceComponent, Class<S> homeClass, Class<T> ejbClass)
throws NamingException, IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException,
NoSuchMethodException
{
log.debug("{}", initialContext.getEnvironment());
java.lang.Object remote = initialContext.lookup(ejbReferenceComponent);

if (ejbClass.isInstance(remote))
return (T) remote;

S home = (S) PortableRemoteObject.narrow(remote, homeClass);

return (T) home.getClass().getMethod("create", (Class[]) null).invoke(home, (Object[]) null);
}
Of course the simplest approach is to convert it all to EJB3 and you won't have to worry :)

Tuesday, 24 March 2009

Simple transactional unit testing database

Last week I wrote a post about using Google Guice to perform transaction handling using interceptors.
When it came time to write some database level unit tests, I wanted to have the same kind of simple transaction handling so that every unit test is rolled back on completion. Rolling back the changes each test makes is necessary for the tests to be repeatable and consistent.

TestNG provides convenient way to do processing before and after each method is called using @BeforeMethod and @AfterMethod. All you need to do is annotate methods that you want automatically rolled back with @Transactional then check for it and act accordingly. The same could be done with JUnit using a RunListener.

Below is my implementation. I use just one connection per unit test, it can easily be adapted across multiple connections.
public abstract class BaseUtilTest
{
private static final Logger log = Logger.getLogger(BaseUtilTest.class);
protected Connection con;

@BeforeClass
public void init()
{
Injector myInjector = Guice.createInjector(new TestModule());
myInjector.injectMembers(this);
}

@BeforeMethod
public void before(Method method) throws Exception
{
con = ConnectionUtils.getConnection();
if (method.isAnnotationPresent(Transactional.class))
{
//do transactional
}
}

@AfterMethod
public void after(Method method, ITestContext context) throws Exception
{
if (method.isAnnotationPresent(Transactional.class))
{
//rollback any changes
rollback();
}

ITestResult result = Reporter.getCurrentTestResult();
if (!result.isSuccess() && result.getThrowable() != null)
{
log.error(method.toGenericString(), result.getThrowable());
}
}

void rollback()
{
if (con == null)
{
log.warn("No bound connection to rollback");
return;
}
log.info("Rolling back transaction");
try
{
con.rollback();
}
catch (SQLException e)
{
log.warn(e);
}
}
}

Friday, 20 March 2009

Lightweight transaction handling in Tomcat

With Enterprise Java Beans (EJB) used with Container-Managed Transactions (CMT), transactions are transparently handled by the application server. When a method is marked with @TransactionAttribute(REQUIRED) then it starts a transaction (if one is not already started) and on competition of the method commits it (if it created the transaction). If an exception occurs, then the container rolls back the transaction. The beauty of it, is that it requires no extra code, just a single annotation.

However, if running outside an application server, e.g. in a Tomcat only environment, then container managed transactions cannot be used. The common approach would be to use Spring.
Spring provides the same functionality as EJB's but with a few drawbacks. It requires a large number of libraries to be included in your classpath plus a cumbersome to maintain configuration file just to set up some basic transaction handling. Spring does have its advantages but is overkill in a lot of cases.

Enter Google Guice. Google's dependancy injection 'framework'.

Guice and be configured as simply or as complex as you want.
In the most simple case, guice requires very little configuration. Unless specified, all beans have no scope. That is, they are single use - new instances are created for each injection. POJO services that don't implement an interface don't even need to be named. If you so chose, you could create an empty configuration Module and let everything auto-wire.

Onto transaction handling.
Guice 1 doesn't provide any mechanism to perform transaction handling. However it turns out that doesn't matter as it takes very little effort to implement a simple transaction handler.
The trick is to define an annotation, then bind an interceptor to all methods that are annotated. The interception is done at load time, so all the complex matching is done once with Guice's internal CGLib proxies created.

First create the annotation to use (you could use JEE ones if you wanted but would need to put them in the class path).
@Retention(RetentionPolicy.RUNTIME)
@Target( { ElementType.METHOD, ElementType.TYPE })
public @interface Transactional
{
}


Next define your Module. Modules are Guice's configuration.
import static com.google.inject.matcher.Matchers.annotatedWith;
import static com.google.inject.matcher.Matchers.subclassesOf;

import org.owatta.service.AbstractService;
import org.owatta.annotation.Transactional;

import com.google.inject.AbstractModule;

public class ServiceModule extends AbstractModule
   {
   @Override
   protected void configure()
   {
      //this line tells guice to bind TransactionInterceptor to any class
      //that is a subclass of AbstractService and is annotated with @Transactional
      //the subclassesOf() could be replaced with Matchers.any()
      bindInterceptor(subclassesOf(AbstractService.class), annotatedWith(Transactional.class), new TransactionInterceptor());
   }
}

Next create your interceptor. Since I only need to handle transaction directly on a connection my transaction interceptor requires little work.
/**
*
*/
package org.owatta.modules;

import java.sql.Connection;
import java.sql.SQLException;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.log4j.Logger;
import org.owatta.web.FacesFilter;

/**
* Simple transaction interceptor. Commits on success, rolls back on error.
* @author Chris Watts
*/
public class TransactionInterceptor implements MethodInterceptor
{
   private final static Logger log = Logger.getLogger(TransactionInterceptor.class);

   public Object invoke(MethodInvocation arg0) throws Throwable
   {
      Object returnValue = null;
      try
      {
         returnValue = arg0.proceed();
         Connection con = FacesFilter.getConnection();
         if (con == null)
         {
            log.warn("No bound connection to rollback");
         }
         else
         {
            try
            {
               con.commit();
            }
            catch (SQLException e)
            {
               log.error("Error commiting transaction, rolling back", e);
               //make sure the caller knows the commit failed
               throw new RuntimeException(e);
            }
         }
         return returnValue;
      }
      catch (Throwable t)
      {
         rollback();
         throw t;
      }
   }

   /**
    * perform rollback
    */
   void rollback()
   {
      Connection con = FacesFilter.getConnection();
      if (con == null)
      {
         log.warn("No bound connection to rollback");
         return;
      }
      log.info("Rolling back transaction");
      try
      {
         con.rollback();
      }
      catch (SQLException e)
      {
         log.warn(e);
      }
   }
}

Since I'm using JSF, I'm using GuiceSF as a variable resolver in order to inject the managed beans. I put the following in my web.xml:
<!-- guice / jsf -->
<context-param>
   <param-name>com.guicesf.Modules</param-name>
   <param-value>com.google.inject.servlet.ServletModule,org.owatta.modules.ServiceModule</param-value>
</context-param>

<!-- Entity Manager Filter declaration -->
<filter>
   <filter-name>GuiceFilter</filter-name>
   <filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
</filter>

<filter-mapping>
   <filter-name>GuiceFilter</filter-name>
   <url-pattern>*.faces</url-pattern>
</filter-mapping>

This is actually 2 parts. The first part configures GuiceSF, the 2nd configures Guice so it can use request / session scoped beans. Depending on your needs this may not be necessary.
The final JSF step is to configure the variable resolver, add the following to faces-config.xml:
<application>
<!-- injector for managed beans -->
<el-resolver>com.guicesf.GuiceResolver</el-resolver>
</application>
The final step is to put the required jars on the class path:
  • guice-1.0.jar
  • aopalliance.jar
  • guice-servlet-1.0.jar (only required if you want servlet scope handling)
  • guicesf-0.1.jar (for JSF integration)
The elegance of this solution is that it is VERY lightweight, very little lock-in and complete control.

Saturday, 31 January 2009

Logging Tomcat 6 startup via log4j

For a recent task I had the need to log the tomcat startup process via log4j (so I could view it in eclipse using the log4eclipse plugin).
The simple copying log4j.properties and log4j.jar into TOMCAT/lib didn't work as it had for previous versions.
After some quick digging around I finally read the right section of the documentation. The steps are pretty easy to follow.

The jar of juli (their logger) that comes with the standard build doesn't allow for commons-logging.
To get it to work you need to download the source and compile the extras commons-logging.
Which is a simple:
ant download
ant build-only
ant -f extras.xml commons-logging
Next:
  1. Replace $CATALINA_HOME/bin/tomcat-juli.jar with output/extras/tomcat-juli.jar.
  2. Place output/extras/tomcat-juli-adapters.jar in $CATALINA_HOME/lib.

For apache-tomcat-6.0.14-src you'll need to change build.properties.default line 45:
jdt.loc=http://archive.eclipse.org/eclipse/downloads/drops/R-3.2.2-200702121330/eclipse-JDT-3.2.2.zip

Additionally as of apache-tomcat-6.0.18-src, there are errors compiling the target build-tomcat-dbcp using JDK 1.6; so use JDK 1.5 to do the 'ant download' step (or all).