Wednesday, December 16, 2009

How ICEFaces Loads Its CSS GIF files

How ICEFaces loads its CSS gif Files

ICEFaces has the dataPaginator tag. It uses icons(gif files) which the user can click on to go back or forward to another page.
The following is an example:
<ice:dataPaginator id="dataScroll_1" for="data" fastStep="3"
 pageCountVar="pageCount" pageIndexVar="pageIndex" paginator="true"
 paginatorMaxPages="20" styleClass="formBorderHighlight">
 <f:facet name="first">
  <ice:graphicImage id="firstpage_1"
   url="./xmlhttp/css/xp/css-images/arrow-first.gif"
   style="border:none;" title="first page" />
 </f:facet>
 ......
</ice:dataPaginator>

Notice that the graphicImage tag uses the url attribute to load the gif file. Where is this gif file ?

The answer is that it is in the icefaces.jar file. More specifically, it is at the location
com/icesoft/faces/resources/css/xp/css-images/arrow-first.gif in the jar.

Now the next question is: how does the servlet find it?

The url starts with xmlhttp. From servlet mapping in web.xml, we know this url will be handled by the same icefaces servlet com.icesoft.faces.webapp.xmlhttp.PersistentFacesServlet that handles other icefaces jsp files. But how does it work exactly?

Debugging the application, I found that the following class is used when the PersistentFacesServlet handles the url:

public class ResourceServer implements Server
{
  public ResourceServer(Configuration configuration, MimeTypeMatcher mimeTypeMatcher, FileLocator fileLocator)
  {
    PathDispatcherServer pathDispatcher = new PathDispatcherServer();
    pathDispatcher.dispatchOn(".*xmlhttp\\/javascript-blocked$", new RedirectOnJSBlocked(configuration));
    pathDispatcher.dispatchOn(".*xmlhttp\\/.*\\/.*\\.js$", new CacheControlledServer(new ServeJSCode()));
    pathDispatcher.dispatchOn(".*xmlhttp\\/css\\/.*", new CacheControlledServer(new ServeCSSResource(mimeTypeMatcher)));
    pathDispatcher.dispatchOn(".*xmlhttp\\/blank$", new ServeBlankPage());
    pathDispatcher.dispatchOn(".*", new FileServer(fileLocator, mimeTypeMatcher));
    if(configuration.getAttributeAsBoolean("compressResources", true))
       dispatcher = new CompressingServer(pathDispatcher);
    else
       dispatcher = pathDispatcher;
  }
  
  public void service(Request request) throws Exception
  {
     dispatcher.service(request);  
  }

  public void shutdown()
  {
    dispatcher.shutdown();
  }
  
  private Server dispatcher;

Note that for the url pattern .*xmlhttp/css/*, it uses ServeCSSResource, which is defined as follows:
public class ServeCSSResource
    implements Server
{
    public ServeCSSResource(MimeTypeMatcher mimeTypeMatcher)
    {
        loader = getClass().getClassLoader();
        matcher = mimeTypeMatcher;
    }
    public void service(Request request)
        throws Exception
    {
        final String path = request.getURI().getPath();
        String file = path.substring(path.lastIndexOf("css/") + 4, path.length());
        final InputStream in = loader.getResourceAsStream("com/icesoft/faces/resources/css/" + file);
        if(in == null)
            request.respondWith(NotFoundHandler.HANDLER);
        else
            request.respondWith(new ResponseHandler() {
                public void respond(Response response)
                    throws Exception
                {
                    response.setHeader("Content-Type", matcher.mimeTypeFor(path));
                    response.writeBodyFrom(in);
                }
                throws IOException
            {
                super();
            }
            }
);
    }
    public void shutdown()
    {
    }
    private static final String Package = "com/icesoft/faces/resources/css/";
    private ClassLoader loader;
    private MimeTypeMatcher matcher;
}
The constant string "Package" defined in this class reveals the secret behind the scene. The "Package" variable defines exactly the directory path to find the CSS gif files.

Friday, October 30, 2009

Notes on Bidirectional Associations in Hibernate Collection Mapping

The Hibernate collection mapping

Bidirectional associations

The example uses Customer and Address. There is a one to many relationship from customer to address. My tests results are the follows.
I made a new Customer object and a new Address object. A customer java object has a set of Address object. I add the new Address object to the set. Notice that no id value is specified for Customer and Address. Hibernate will generate them when the objects are saved.
  1. The inverse="true" can only be declared on the many-valued end. ( Customer in this case). If you declare it in the Address mapping file, error occurs.
  2. If you do not declare inverse="true", you’ll get error when saving the Customer object. The error will say that the object is transient. ( It is the Address object in this case.)
  3. If you use inverse="true" but cascade="none", you can save the Customer, but the address is not saved to the address table
  4. If you use inverse="true" and cascade="save-update", the both the customer and address will be saved to the tables successfully.
Note that #4 is consistent with the Hibernate documentation about the many-to-many association. In the many-to-many association, Changes made only to the inverse end of the association are not persisted. In our one-to-many association, the inverse end is the many-valued end, which is Address.

Some other observations.
In cascade="all", "all" does not include everything. "delete-orphan" is separate. So you can see cases like cascade="all,delete-orphan" or "all-delete-orphan", ( I think they are the same).

How JSF and ICEfaces Are Initialized in Application Startup

A Case for Study

We use a sample application called fooApp in the following analysis. The application is deployed to the server fooAdminServer in the domain fooDomain.

The log file when the application is started has the following log messages in that order:
2009-06-16 13:44:23,403 DEBUG [ConfigureListener]: parse(zip:C:/bea921/user_projects/domains/fooDomain/servers/fooAdminServer/tmp/_WL_user/fooApp/3pj2tv/APP-INF/lib/jsf-impl-1.1.jar!/com/sun/faces/jsf-ri-runtime.xml)
2009-06-16 13:44:23,512 DEBUG [ConfigureListener]: parse(zip:C:/bea921/user_projects/domains/fooDomain/servers/fooAdminServer/tmp/_WL_user/fooApp/qw1mk0/war/WEB-INF/lib/icefaces-comps-1.6.2.jar!/META-INF/faces-config.xml)
2009-06-16 13:44:23,544 DEBUG [ConfigureListener]: parse(zip:C:/bea921/user_projects/domains/fooDomain/servers/fooAdminServer/tmp/_WL_user/fooApp/3pj2tv/APP-INF/lib/icefaces-comps-1.6.2.jar!/META-INF/faces-config.xml)
2009-06-16 13:44:23,591 DEBUG [ConfigureListener]: parse(zip:C:/bea921/user_projects/domains/fooDomain/servers/fooAdminServer/tmp/_WL_user/fooApp/3pj2tv/APP-INF/lib/icefaces-1.6.2.jar!/META-INF/faces-config.xml)
2009-06-16 13:44:23,606 DEBUG [ConfigureListener]: parse(file:/C:/bea921/user_projects/domains/fooDomain/servers/fooAdminServer/upload/fooApp/app/fooApp/fooApp/WEB-INF/faces-config.xml)
Note that each log message shows an xml file is parsed. There are other log messages in between these log messages. Those log messages basically shows the content in the xml files are loaded item by item.

These log messages are generated by the class com.sun.faces.config.ConfigureListener.

The method is
public void contextInitialized(ServletContextEvent sce)

The following is the copy of the function stack from Eclipse. I set a breakpoint on the constructor of D2DViewHandler.java.
(Note:image omitted here.)
The method basically is the following (decompiled from jar. May not be accurate):
public void contextInitialized(ServletContextEvent sce)
  {
    Digester digester = null;
    FacesConfigBean fcb = new FacesConfigBean();
    ServletContext context = sce.getServletContext();
    tlsExternalContext.set(new ServletContextAdapter(context));
    try
    {
      if (RIConstants.IS_UNIT_TEST_MODE) context
          .setAttribute(FACES_CONFIG_BEAN_KEY, fcb);
    }
    catch (Exception e)
    {
      if (log.isDebugEnabled()) log.debug("Can't query for test environment");
    }
    RIConstants.HTML_TLV_ACTIVE = isFeatureEnabled(context,
        "com.sun.faces.enableHtmlTagLibValidator");
    URL url = null;
    if (log.isDebugEnabled()) log.debug("contextInitialized("
        + context.getServletContextName() + ")");
    if (initialized()) return;
    digester = digester(isFeatureEnabled(context, "com.sun.faces.validateXml"));
    url = Util.getCurrentLoader(this)
        .getResource("com/sun/faces/jsf-ri-runtime.xml");
    parse(digester, url, fcb);
    Iterator resources;
    try
    {
      List list = new LinkedList();
      for (Enumeration items = Util.getCurrentLoader(this)
          .getResources("META-INF/faces-config.xml"); items.hasMoreElements(); list
          .add(0, items.nextElement()));
      resources = list.iterator();
    }
    catch (IOException e)
    {
      // omitted
    }
    for (; resources.hasNext(); parse(digester, url, fcb))
      url = (URL) resources.next();

    String paths = context.getInitParameter("javax.faces.CONFIG_FILES");
    if (paths != null)
    {
      for (StringTokenizer t = new StringTokenizer(paths.trim(), ","); t
          .hasMoreTokens();)
      {
        url = getContextURLForPath(context, t.nextToken().trim());
        if (url != null) parse(digester, url, fcb);
      }

    }
    url = getContextURLForPath(context, "/WEB-INF/faces-config.xml");
    if (url != null) parse(digester, url, fcb);
    try
    {
      configure(context, fcb);
    }
    catch (FacesException e)
    {
      e.printStackTrace();
      throw e;
    }
    catch (Exception e)
    {
      e.printStackTrace();
      throw new FacesException(e);
    }
    verifyFactories();
    if (isFeatureEnabled(context, "com.sun.faces.verifyObjects")) verifyObjects(context,
        fcb);
    tlsExternalContext.set(null);
  }
So it first reads the file com/sun/faces/jsf-ri-runtime.xml. Then it reads all the files META-INF/faces-config.xml on the classpath. Notice that there may be more than one META-INF/faces-config.xml file under different folders in the classpath. This is exactly how the ICEFaces configuration is loaded into the applicaiton. The application has the icefaces-1.6.2.jar on its classpath APP-INF/lib. And the jar contains a file META-INF/faces-config.xml. Also notice that at the beginning of the method, it creates a brand new object

FacesConfigBean fcb = new FacesConfigBean();

When it reads the xml files, it creates objects configured in those xml files and put them into this object.

The method calls configure(context, fcb) near the end. This method is the following( from decompiling):
protected void configure(ServletContext context, FacesConfigBean config)
      throws Exception
  {
    configure(config.getFactory());
    configure(config.getLifecycle());
    configure(config.getApplication());
    configure(config.getComponents());
    configure(config.getConvertersByClass());
    configure(config.getConvertersById());
    configure(config.getManagedBeans());
    configure(config.getNavigationRules());
    configure(config.getRenderKits());
    configure(config.getValidators());
  }
One thing to concern is that how multiple viewhandlers are setup. We have the following view handlers from various xml files in order:
  • com.sun.faces.application.ViewHandlerImpl in isf-ri-runtime.xml
  • com.icesoft.faces.application.D2DViewHandler in icefaces-1.6.2.jar faces-config.xml
  • com.myCompany.CustomViewHandler in /WEB-INF/faces-config.xml.
Do they all work at runtime, or one will override all the others? The method configure(config.getApplication()) is the following:

private void configure(ApplicationBean config) throws Exception
  {
    if (config == null) return;
    Application application = application();
    configure(config.getLocaleConfig());

    // omitted many lines here

    values = config.getViewHandlers();
    if (values != null && values.length > 0)
    {
      for (int i = 0; i < values.length; i++)
      {
        if (log.isTraceEnabled()) log
            .trace("setViewHandler(" + values[i] + ')');
        Object instance = Util.createInstance(values[i],
            javax.faces.application.ViewHandler.class,
            application.getViewHandler());
        if (instance != null) application
            .setViewHandler((ViewHandler) instance);
      }
    }
  }
Here the Application class is javax.faces.application.Application . The instance application is created by the method application():
private Application application()
  {
    ApplicationFactory afactory = (ApplicationFactory) FactoryFinder
        .getFactory("javax.faces.application.ApplicationFactory");
    return afactory.getApplication();
  }
From the log message, the application seems to be com.sun.faces.application.ApplicationImpl. And the following is its setViewHandler method:
public void setViewHandler(ViewHandler handler)
  {
    if (handler == null)
    {
      String message = Util
          .getExceptionMessageString("com.sun.faces.NULL_PARAMETERS_ERROR");
      message = message + " ViewHandler " + handler;
      throw new NullPointerException(message);
    }
    synchronized (this)
    {
      if (associate.isResponseRendered())
      {
        if (log.isErrorEnabled()) log
            .error("Response for this request has been rendered already ");
        throw new IllegalStateException(Util
            .getExceptionMessageString("com.sun.faces.ILLEGAL_ATTEMPT_SETTING_VIEWHANDLER"));
      }
      viewHandler = handler;
      if (log.isDebugEnabled()) log.debug("set ViewHandler Instance to "
          + viewHandler);
    }
  }
So from this method, the viewHandler set later will override the viewHandlers set previously. On the other hand, the variable config in the method configure(ApplicationBean config) is an instance of the class com.sun.faces.config.beans.ApplicationBean which uses an ArrayList for viewHandlers. So it can hold multiple view handlers. Looking into the method call
Util.createInstance(values[i],javax.faces.application.ViewHandler.class, application.getViewHandler())
We can find some tricky things here. The third argument application.getViewHandler() in the above call is important. It acts like a delegate. It returns the ViewHandler set previously. And then it is used as a delegate in the new ViewHandler. So the new ViewHandler does not completely override the previous one. For this to work, the new ViewHandler class needs to have a constructor like Foo(ViewHandler delegate), where the argument delegate is used to hold the old ViewHandler.

A Sample Customized ViewHandler

The following is a sample customerized ViewHandler that incoorparates the default view handler with ICEfaces Facelet viewhandler. It assumes that the facelet files use "xhtml" as the file extension. It also adds the function to customize the URL. This view handler need to be put in the user-deined file faces-config.xml .
public class MyViewHandler extends ViewHandler
  {
    ViewHandler defaultHandler;
    ViewHandler faceletViewHandler;

    public MyViewHandler(ViewHandler defaultHandler)
    {
      this.defaultHandler = defaultHandler;
      this.faceletViewHandler = new D2DFaceletViewHandler(defaultHandler);
    }

    private ViewHandler getViewHandler(String viewId)
    {
      if (viewId.endsWith(".xhtml"))
      {
        return faceletViewHandler;
      }
      return defaultHandler;
    }

    public UIViewRoot createView(FacesContext context, String viewId)
    {
      ViewHandler viewHandler = getViewHandler(viewId);
      return viewHandler.createView(context, viewId);
    }

    public String getActionURL(FacesContext context, String viewId)
  {    
  String actionURL = defaultHandler.getActionURL(context, viewId);
  String customerPrefix = context.getExternalContext()
  .getInitParameter("USER_DEIFINED_PREFIX_NAME");    
  return (customerPrefix == null) ? actionURL, customerPrefix + actionURL;
  }

    public String getResourceURL(FacesContext context, String path)
  {  
  String resourceUrl = defaultHandler.getResourceURL(context, path);
  return (customerPrefix == null) ? resourceUrl, customerPrefix + resourceUrl;
  }

    public void renderView(FacesContext context, UIViewRoot viewToRender)
        throws IOException, FacesException
    {
      ViewHandler viewHandler = getViewHandler(viewToRender.getViewId());
      viewHandler.renderView(context, viewToRender);
    }

    public UIViewRoot restoreView(FacesContext context, String viewId)
    {
      ViewHandler viewHandler = getViewHandler(viewId);
      return viewHandler.restoreView(context, viewId);
    }
  }

Appendix:

The following is more log text following the xml parsing logs:
2009-06-16 13:44:23,606 DEBUG [ConfigureListener]: parse(file:/C:/bea921/user_projects/domains/fooDomain/servers/fooAdminServer/upload/fooApp/app/fooApp/fooApp/WEB-INF/faces-config.xml)
2009-06-16 13:44:23,622 DEBUG [FacesConfigBean]: addConverterById(myConverter1)
......
2009-06-16 13:44:23,622 DEBUG [FacesConfigBean]: addConverterById(myConverter2)
2009-06-16 13:44:23,653 DEBUG [FacesConfigBean]: addManagedBean(myBean)
2009-06-16 13:44:23,653 DEBUG [ConfigureListener]: setApplicationFactory(com.sun.faces.application.ApplicationFactoryImpl)
2009-06-16 13:44:23,669 DEBUG [ConfigureListener]: setFacesContextFactory(com.sun.faces.context.FacesContextFactoryImpl)
2009-06-16 13:44:23,669 DEBUG [ConfigureListener]: setFacesContextFactory(com.icesoft.faces.context.FacesContextFactoryImpl)
2009-06-16 13:44:23,669 DEBUG [ConfigureListener]: setLifecycleFactory(com.sun.faces.lifecycle.LifecycleFactoryImpl)
2009-06-16 13:44:23,669 DEBUG [ConfigureListener]: setRenderKitFactory(com.sun.faces.renderkit.RenderKitFactoryImpl)
2009-06-16 13:44:23,778 DEBUG [ApplicationFactoryImpl]: Created ApplicationFactory 
2009-06-16 13:44:23,809 DEBUG [ApplicationImpl]: Created Application instance 
2009-06-16 13:44:23,809 DEBUG [ApplicationFactoryImpl]: Created Application instance com.sun.faces.application.ApplicationImpl@4f0638
2009-06-16 13:44:23,841 DEBUG [LifecycleFactoryImpl]: Created Default Lifecycle
2009-06-16 13:44:23,841 DEBUG [LifecycleFactoryImpl]: getLifecycle: DEFAULT com.sun.faces.lifecycle.LifecycleImpl@1944f2c
2009-06-16 13:44:23,841 DEBUG [ConfigureListener]: addPhaseListener(org.springframework.webflow.executor.jsf.FlowPhaseListener)
2009-06-16 13:44:23,887 DEBUG [LifecycleImpl]: addPhaseListener(ANY 0,org.springframework.webflow.executor.jsf.FlowPhaseListener@11bfb7e
2009-06-16 13:44:23,887 DEBUG [ConfigureListener]: addPhaseListener(myCustomPhaseListener)
2009-06-16 13:44:23,887 DEBUG [LifecycleImpl]: addPhaseListener(INVOKE_APPLICATION 5,myCustomPhaseListener@afad79
2009-06-16 13:44:23,887 DEBUG [ConfigureListener]: setActionListener(com.sun.faces.application.ActionListenerImpl)
2009-06-16 13:44:23,887 DEBUG [ApplicationImpl]: set ActionListener Instance to com.sun.faces.application.ActionListenerImpl@ad342a
2009-06-16 13:44:23,903 DEBUG [ConfigureListener]: setNavigationHandler(com.sun.faces.application.NavigationHandlerImpl)
2009-06-16 13:44:23,903 DEBUG [NavigationHandlerImpl]: Created NavigationHandler instance 
2009-06-16 13:44:23,903 DEBUG [NavigationHandlerImpl]: Created NavigationHandler instance 
2009-06-16 13:44:23,903 DEBUG [ApplicationImpl]: set NavigationHandler Instance to com.sun.faces.application.NavigationHandlerImpl@16c0caa
2009-06-16 13:44:23,903 DEBUG [ConfigureListener]: setNavigationHandler(org.springframework.webflow.executor.jsf.FlowNavigationHandler)
2009-06-16 13:44:24,012 DEBUG [ApplicationImpl]: set NavigationHandler Instance to org.springframework.webflow.executor.jsf.FlowNavigationHandler@305d34
2009-06-16 13:44:24,012 DEBUG [ConfigureListener]: setPropertyResolver(com.sun.faces.el.PropertyResolverImpl)
2009-06-16 13:44:24,012 DEBUG [ApplicationImpl]: set PropertyResolver Instance to com.sun.faces.el.PropertyResolverImpl@18850b0
2009-06-16 13:44:24,012 DEBUG [ConfigureListener]: setStateManager(com.sun.faces.application.StateManagerImpl)
2009-06-16 13:44:24,028 DEBUG [ApplicationImpl]: set StateManager Instance to com.sun.faces.application.StateManagerImpl@5aa65e
2009-06-16 13:44:24,028 DEBUG [ConfigureListener]: setVariableResolver(com.sun.faces.el.VariableResolverImpl)
2009-06-16 13:44:24,028 DEBUG [ApplicationImpl]: set VariableResolver Instance to com.sun.faces.el.VariableResolverImpl@89b9bf
2009-06-16 13:44:24,028 DEBUG [ConfigureListener]: setVariableResolver(org.springframework.webflow.executor.jsf.DelegatingFlowVariableResolver)
2009-06-16 13:44:24,028 DEBUG [ApplicationImpl]: set VariableResolver Instance to org.springframework.webflow.executor.jsf.DelegatingFlowVariableResolver@febedf
2009-06-16 13:44:24,028 DEBUG [ConfigureListener]: setViewHandler(com.sun.faces.application.ViewHandlerImpl)
2009-06-16 13:44:24,044 DEBUG [ViewHandlerImpl]: Created ViewHandler instance 
2009-06-16 13:44:24,044 DEBUG [ApplicationImpl]: set ViewHandler Instance to com.sun.faces.application.ViewHandlerImpl@6e1156
2009-06-16 13:44:24,044 DEBUG [ConfigureListener]: setViewHandler(com.icesoft.faces.application.D2DViewHandler)
2009-06-16 13:44:24,075 INFO  [D2DViewHandler]: 
ICEsoft Technologies, Inc.
ICEfaces 1.6.2 
Build number: 22
Revision: 15177

Friday, October 23, 2009

How to Create Script for Weblogic Server Configuration

One way to create the WLST script is to use the Weblogic configuration wizard to configure the domain and server. After it is done, use a WLST command to generate the script from the configured server.

The following describes the steps.
  1. Use weblogic configuration wizard to create the domain. You will give the domain a name. Here suppose we use "myDomain".
  2. Execute C:\bea10\wlserver_10.3\common\bin\wlst.cmd. This will set the environment and give the prompt wls:/offline>
  3. Run WLST command configToScript to generate scripts. Example:
    configToScript('c:/bea10/user_projects/domains/myDomain','c:/myscripts')
    This will generate four files under the directory c:/myscripts: config.py, config.py.properties, c2sConfigmyDomain, and c2sSecretmyDomain.
  4. We won't use c2sConfigmyDomain or c2sSecretmyDomain. We need to modify config.py and config.py.properties files.
    In the config.py file, a critical change is to change setEncrypted("Password", .....) to set("Password","..."), i.e, to use the plain text password. Remove any string values that use those c2s values. Also delete the call in the script that set credentials.
    In the config.py.properties file, a critical change is to modify the domain directory to your desired domain name, say 'myNewDomain':
    domainDir=C:/bea10/user_projects/domains/myNewDomain
Now you can run the script to create the domain. First run the file at %WL10_HOME%\wlserver_10.3\common\bin\wlst.cmd. Then at the WLST command promt, execute the following
execfile('C:/myscripts/config.py')
After execution, your new domain will be created at C:/bea10/user_projects/domains/myNewDomain.
There are still some little things you need to modify. The following are the steps.
  1. The file %WL10_HOME%\wlserver_10.3\common\bin\commonEnv.cmd sets the patch.jar in the classpath. But the default download of webogic 10gR3 does not seem to include the patch.jar. And this may cause error when the weblogic server is started. So modify commonEnv.cmd to exclude patch jar files.
  2. The generated script will use jrocket even though the domain was created by wizard to use jdk.

    To use jdk, in the file myNewDomain\bin\setDomainEnv.cmd, change the following

    if "%JAVA_VENDOR%"=="BEA" (
    set JAVA_HOME=%BEA_JAVA_HOME%
    ) else (
    if "%JAVA_VENDOR%"=="Sun" (
    set JAVA_HOME=%SUN_JAVA_HOME%
    ) else (
    set JAVA_VENDOR=BEA
    set JAVA_HOME=C:\bea10\jrockit_160_05
    )
    )

    To

    if "%JAVA_VENDOR%"=="BEA" (
    set JAVA_HOME=%BEA_JAVA_HOME%
    ) else (
    if "%JAVA_VENDOR%"=="Sun" (
    set JAVA_HOME=%SUN_JAVA_HOME%
    ) else (
    set JAVA_VENDOR=Sun
    set JAVA_HOME=C:\bea10\jdk160_05
    )
    )

Notes on creating server cluster with the WLST script.

Assume the following scenario. You have one admin server myAdminServer and one cluster myCluster. The cluster has two servers: myAppServer1 and myAppServer2. The two servers will be located on the two linux machines machine1 and machine2 respectively. We'll also assume that the admin server will be on machine1. The admin server and the cluster will use different port numbers. Assume that myAdminServer will use port number 7001 and the cluster will use 7002. These port numbers can be specified in the property file for the WLST script.
First on machine1, make sure no server is running at the port 7001. Now run the WLST script on machine1 to create the domain. Then simply copy the whole domain to the same location in the directory structure on machine2. No other configuration is needed on machine2.

Thursday, October 22, 2009

JMS and Transaction

When we talk about JTA, we are thinking about all the actions in a business activity as a single unit that will either succeed or fail. When it comes to JMS, one may think of the following scenario. You do some business which involves some database action. You also send messages. And the messages are received by the clients. You may think that this whole thing will be a transaction unit. The special thing here is that it involves both the sending and receiving of the messages in this unit. You may even think that this has to do with the so-called synchronous and asynchronous messaging. But all these may be some wrong impression about JMS.

JMS and JTA

Firstly, the JMS JTA is not about to put both the sending and receiving of messages in an atomic unit. In reality, you may just want to make sure that only one of the sending and receiving actions will be part of the transaction.

The following are the behaviors regarding to the message production and consumption.

For message production, when an application commits a transaction, all the messages it sent within the transaction are actually delivered. If the application rolls back the transaction, the messages it sent are discarded.

For message consumption, when an application commits a transaction, all the messages it received within the transaction are removed from the messaging system.
When a topic subscriber rolls back a received message, the message is redelivered to that subscriber.
When a queue receiver rolls back a received message, the message is redelivered to the queue, not the consumer, so that another consumer on that queue may receive the message.

Note: In weblogic console, you can set a Redelivery limit for both the topic and the queue. It is the number of redelivery tries a message can have before it is moved to the error destination. This setting overrides any redelivery limit set by the message sender. If the redelivery limit is configured, but no error destination is configured, then persistent and non-persistent messages are simply dropped (deleted) when they reach their redelivery limit.

Secondly, even though sending and receiving messages can be in two separate JTA transactions, the effect will still be like that they are in one unit. This will be mostly the responsibility of the message provider, or more technically the Message-Oriented Middleware (MOM), that implements the JMS service. After the message is produced, the message provider will do the redelivery if the message receiving is rolled back.

So what if you do want to have both the sending and receiving of the messages in the same atomic unit?
The answer: It is not possible! References [4] and [5] have very good explanations on this. Your application cannot both send a JMS message and receive a reply to it within the same transaction. This is true no matter if you use local transaction in JMS or global transaction in a J2EE environment. Because a message sent during a transaction is not actually sent until the transaction is committed, the transaction cannot contain any receives that depend on that message's having been sent. You can, however, receive a message and send a message in one transaction. Note here that the order of send/receive matters. Also I think that if you send a message and receive an unrelated message, then it is also OK to put them in one transaction. The important thing about this scenario is that the received message must be unrelated to the message sent so it does not depend on that message being sent successfully. This scenario may not make sense in real life. After all, why do you need to put two unrelated actions into one transaction?

By the way, in a non-transactional session, the action that is similar to a commit in transactional session may be the message acknowledgement. When the Session.CLIENT_ACKNOWLEDGE mode is used in a non-transactional session, the following holds according to Reference[4]:

  1. In this mode, acknowledgement takes place on the session level. So acknowledging a consumed message automatically acknowledge the receipt of all the messages that have been consumed by the session. So for example, if a consumer consumes 10 messages and then acknowledge the 5th message, all 10 messages are acknowledged.
  2. In the above statement, we can see that even though the 5th message is not the last message among the 10 messages, the effect is still that all the 10 messages are now acknowledged. But also notice that these 10 messages have been received at the time the 5th message sends its acknowledgement. So for the messages the consumer will receive in the future, new acknowledgement will need to be sent.

Synchronous and Asynchronous Messaging

Synchronous and asynchronous messaging is not about that a message will be sent and received in one atomic action unit. It is about if the call to receive the message will be blocked.

A JMS client can consume messages either synchronously or asynchronously.
  1. Synchronous: In this mode, a client receives a message by invoking the receive() method of the MessageConsumer object. The application thread blocks until the method returns, and this has the consequence that if a message is not available, it blocks until a message becomes available or the receive() method times out. Also, note that in this model the client can consume one message at a time.
    A long parameter can be passed to the receive() method to specify a time-out (for example, 3000L for 3 seconds).
  2. Asynchronous: In this mode, the client registers a MessageListener object with a message consumer. This is like a call-back where the client consumes a message when the session invokes the onMessage() method. In other words, the application's thread doesn't block.

Persistent and Non-persistent messages

JMS defines two delivery modes:
  1. Persistent messages: Guaranteed to be successfully consumed once and only once. Messages are not lost.
  2. Non-persistent messages: Guaranteed to be delivered at most once. Message loss is not a concern.
This, however, is all about performance trade-offs. The more reliable the delivery of messages, the more bandwidth and overhead required to achieve that reliability. Performance can be maximized by producing non-persistent messages, or you can maximize the reliability by producing persistent messages.

The following is from javadoc of javax.jms.DeliveryMode.

public interface DeliveryMode The delivery modes supported by the JMS API are PERSISTENT and NON_PERSISTENT.

A client marks a message as persistent if it feels that the application will have problems if the message is lost in transit. A client marks a message as non-persistent if an occasional lost message is tolerable. Clients use delivery mode to tell a JMS provider how to balance message transport reliability with throughput.

Delivery mode covers only the transport of the message to its destination. Retention of a message at the destination until its receipt is acknowledged is not guaranteed by a PERSISTENT delivery mode. Clients should assume that message retention policies are set administratively. Message retention policy governs the reliability of message delivery from destination to message consumer. For example, if a client's message storage space is exhausted, some messages may be dropped in accordance with a site-specific message retention policy.

A message is guaranteed to be delivered once and only once by a JMS provider if the delivery mode of the message is PERSISTENT and if the destination has a sufficient message retention policy.

The delivery mode is configured on the connection factory. In weblogic console, you can set a value for the "Default Delivery Mode" property of a connection factory. The value is either Persistent or Non-Persistent. This is the default delivery mode used for messages when a delivery mode is not explicitly defined. All messages with a DefaultDeliveryMode of null that are produced on a connection created with this factory will receive this value. Message producers can get the delivery mode explicitly by calling the javax.jms.MessageProducer.getDeliveryMode() method. This attribute is dynamic. It can be changed at any time. However, changing the value does not affect existing connections. It only affects new connections made with this connection factory.

References

1. http://java.sun.com/developer/technicalArticles/Ecommerce/jms/index.html
2. http://download.oracle.com/docs/cd/E12840_01/wls/docs103/jms/trans.html
3. http://openmessaging.blogspot.com/2009/04/durable-messages-and-persistent.html
4. http://docs.oracle.com/javaee/1.4/tutorial/doc/JMS6.html
5. http://docs.oracle.com/javaee/1.4/tutorial/doc/JMS7.html

Tuesday, October 6, 2009

How to Configure JMS in Weblogic

The following are the steps to configure a sample JMS server that supports JTA. Suppose a weblogic server or cluster has already been created. Let's call it SampleServer.
  1. Create a Non-XA DataSouce
  2. Create a persistent JDBCStore. Use the non-XA DataSource created above. Don't forget to assign the store a prefix name, say 'Example'. A table ExampleWLStore should be created in the database if the configuration will be successful. Target the JDBCStore to SampleServer.
  3. Create a JMS server. Use the JDBCStore created above. Target it to SampleServer
  4. Create a JMS module. Target it to SampleServer
  5. In the JMS module, create subdeployment. Target the subdeployment to the JMS server.
  6. Create a Queue and a Queue Connection Factory in the JMS module and associate them with the subdeployment. Assign a JNDI name to the queue and the factory. Notice that when creating the queue connection factory, the xa-connection-factory-enabled flag should be set to 'true'.

Notes. It may be necessary to create the queue and the subdeployment and to target it to a JMS server. But you do not have to do this for connection factory. In the webblogic10 sample application medrec, you can see from the admin console that three queues are configured. But there is no connection factory. But JMS won't work without a factory. So where is the factory? The medrec source code reveals the secret. It turns out that weblogic has two predefined factories that can be used. The JmsClientImpl.java in medrec defines the following:

private String connectionFactoryName = "weblogic.jms.XAConnectionFactory";

Then the code uses this name as the JNDI name to look up the connection factory. For more information, look at the weblogic document on it.

Thursday, October 1, 2009

How to Use Message-Driven Bean in the Web Application

A sample that uses EJB 3.0 message-driven bean is described here. It is from the ejb3 example in weblogic 10.

Create MDB and Session Bean

To use MDB, two resources are needed: (1) Message Connection Factory (2) Message Destination( a queue or a topic ). These are configured in the admin server. They will have a JNDI name.

In this example, a message queue is created in Admin with the JNDI name "weblogic.examples.ejb30.ExampleQueue". A queue connection factory is created in Admin with the JNDI name "weblogic.examples.ejb30.QueueConnectionFactory".

For the MDB to work, there needs to be a sender to send the message. And the MDB will receive the message to process it.

The following is a sample MDB:
@MessageDriven(mappedName = "weblogic.examples.ejb30.ExampleQueue", name = "WatchProcessMDB", activationConfig = {@ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue")})
  public class WatchProcessMDB implements MessageListener
  {
    @PersistenceContext(unitName = "reviewSession")
    private EntityManager em;

    @TransactionAttribute(value = javax.ejb.TransactionAttributeType.REQUIRED)
    public void onMessage(Message msg)
    {
      // do the business logic using the msg and the EntityManager em
    }
  }
The following is the sample sender, which is a stateless session bean:
/**
   * A Stateless session bean get invoked when a review is added. It will push
   * the review into a jms destination. The review added event will be handled
   * asynchronously.
   */
  @Stateless
  @Remote(ReviewListener.class)
  public class ReviewListenerBean implements ReviewListener
  {
    // The JNDI name of the Queue Connection Factory referenced here is
    // specified by the mappedName attribute of @Resource,
    // which can be overwritten in weblogic-ejb-jar.xml.
    @Resource(name = "jms/mdbQCF", mappedName = "weblogic.examples.ejb30.QueueConnectionFactory")
    private QueueConnectionFactory mdbQCF;
    // The JNDI name of the Queue referenced here is specified by the mappedName
    // attribute of @Resource,
    // which can be overwritten in weblogic-ejb-jar.xml.
    @Resource(name = "jms/mdbQueue", mappedName = "weblogic.examples.ejb30.ExampleQueue")
    private Queue mdbQueue;
    private QueueConnection qcon = null;
    private QueueSession qsession = null;
    private QueueSender qsender = null;

    @PostConstruct
    private void init()
    {
      try
      {
        qcon = mdbQCF.createQueueConnection();
        qsession = qcon.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
        qsender = qsession.createSender(mdbQueue);
      }
      catch (JMSException e)
      {
        throw new RuntimeException(e);
      }
    }

    public void reviewAdded(Review review)
    {
      try
      {
        ObjectMessage msg = qsession.createObjectMessage();
        msg.setIntProperty("reviewid", review.getId());
        qcon.start();
        qsender.send(msg);
      }
      catch (JMSException e)
      {
        throw new RuntimeException(e);
      }
    }

    @PreDestroy
    void cleanup()
    {
      // close sender, session and connection
    }
  }
From the code, the thing that links the MDB and the sender seems to be the queue named "weblogic.examples.ejb30.ExampleQueue" which is referenced to by both the MDB and the sender.

Use MDB in Web Application

First, the web.xml defines some references to the EJB.
<ejb-ref>
   <ejb-ref-name>ejb/ReviewManager<ejb-ref-name>
   <ejb-ref-type>Session</ejb-ref-type>
   <remote>examples.ejb.ejb30.service.ReviewService</remote>
   <ejb-link>domain.jar#ReviewManagerBean</ejb-link>
   </ejb-ref>
Here domain.jar is the jar file that contains the ReviewManagerBean.class and the file persistence.xml. The jar is the ejb module. The jsp and servlet files below are in a war file that is the web module.

In JSP code, it first gets a session bean ReviewManagerBean using JNDI and calls the business method of the bean.
<%
String SERVICE_NAME = "ReviewManagerBean";
ReviewService service = (ReviewService) session.getAttribute(SERVICE_NAME);
if (service == null) {
   Context ctx = new InitialContext();
   service = (ReviewService) ctx.lookup("java:comp/env/ejb/ReviewManager");
   session.setAttribute(SERVICE_NAME, service);
}
......
service.doSomeBusiness();
%>
The session bean ReviewManagerBean is injected a session bean ReviewListener, which is the message sender. The business method doSomeBusiness() of ReviewManagerBean will call the business method of the ReviewListener to send out the messages.
@Stateless(mappedName="ReviewService")
@Remote(ReviewService.class)
public class ReviewManagerBean extends ReviewServiceImpl
implements ReviewService {
@PersistenceContext(unitName = "reviewSession")
private EntityManager em;
@EJB ReviewListener listener;
The business method of ReviewManagerBean can be as follows:
pubilc void doSomeBusiness(){
// use the EntityManager em to do something
// use the ReviewListener listener to do something
}
A couple of notes here. (1) The method doSomeBusiness() uses both the EntityManager em and the session bean ReviewListener. So the transaction is cross both the database and JMS. (2) The jsp uses a session bean "A" which itself is injected a session bean "B". The jsp calls the business method of the session bean "A" which in turn calls a business method of "B". So there should be a transaction propagation here. (3) When the sender sends the message, is the MDB class loaded into the JVM already? Is there any configuration needed for this?

Monday, September 28, 2009

How to Use EJB 3.0 Session Bean in the Web Application

A sample that uses EJB 3.0 stateless session bean is described here. It is from the ejb3 example in weblogic 10.

Overview of the Whole Picture

Scenario #1: Remote Session Bean
The web.xml file has the following:
<ejb-ref>
    <ejb-ref-name>ejb/ReviewManager</ejb-ref-name>
    <ejb-ref-type>Session</ejb-ref-type>
    <remote>examples.ejb.ejb30.service.ReviewService</remote>
    <ejb-link>domain.jar#ReviewManagerBean</ejb-link>
    </ejb-ref>
Here domain.jar is the jar file that contains the ReviewManagerBean.class and the file persistence.xml. The jar is the ejb module. The jsp and servlet files below are in a war file that is the web module. The domain.jar and the web module are in the same ear file. The ejb module domain.jar does not have the descriptor file ejb-jar.xml because it uses ejb annotation. The ReviewManagerBean class is the following:
package examples.ejb.ejb30.session;
import examples.ejb.ejb30.mdb.ReviewListener;
import examples.ejb.ejb30.service.ReviewServiceImpl;
import examples.ejb.ejb30.service.ReviewService;
import javax.annotation.PostConstruct;
import javax.ejb.EJB;
import javax.ejb.Remote;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

/**
 * A Stateless Session Bean operating on a persistent domain model.
 * The EJB container registers the bean in JNDI global namespace.
 * The JNDI name for the bean is the simple name (i.e.
 * stripped of package name) of the business interface it implements.
 * For this example, the JNDI name will be ReviewService
 * which is specified by the annotation element mappedName="ReviewService".

* NOTE:This policy is ambiguous when two beans implements the same * business interface.

* The EJB contain also injects the bean with a instance of * {@link javax.persistence.EntityManager EntityManager} (or rather a proxy). * This EntityManager is used to interact with the persistent EJB domain * model. */ @Stateless(mappedName="ReviewService") @Remote(ReviewService.class) public class ReviewManagerBean extends ReviewServiceImpl implements ReviewService { @PersistenceContext(unitName = "reviewSession") private EntityManager em; @EJB ReviewListener listener; @PostConstruct public void init(){ // inject the EnityManager. injectEntityManager(em); addReviewAddedListener(listener); } }


In the jsp file, the bean is used as follows:
<%
private ReviewService serviceImpl;
String SESSION_BEAN_JNDI_NAME = "java:comp/env/ejb/ReviewManager";
InitialContext ctx = new InitialContext();
serviceImpl = (ReviewService) ctx.lookup(SESSION_BEAN_JNDI_NAME);

Artist artist = serviceImpl.newArtist(name);
%>  
In summary, the web component uses JNDI and InitialContext to get the session bean. A session bean is a service. It is injected an EntityManager instance by the J2EE container using the unitName. Each business method call is a transaction unit. The business method can just use the EntityManager to perform database activity without using any transaction maintence code such as beginTransaction, commit, or rollback. The container will take care of that.
Scenario #2: Local Session Bean
In this scenario, the session bean is local. No configuration is needed in web.xml. The session bean is the following:
@Stateless
@Local(Process.class)
public class ProcessBean {
//reference name and type inferred from variable.
@EJB ReviewListener listener;
@PersistenceContext(unitName = "reviewSession")
private EntityManager em;

public Book getRandomBook() {
List books = em.createQuery("select i from Book i").getResultList();
int size = books.size();
return size == 0 ? null : books.get(new Random().nextInt(size));
}

public void addReview(int uid, String reviewerName, int rating, String comments) {
Book book = em.find(Book.class, uid);
if (book == null)
throw new ObjectNotFoundException("Can't find book with id ["+uid+"]");
Reviewer reviewer = em.find(Reviewer.class, reviewerName);
if (reviewer == null) {
reviewer = new Reviewer(reviewerName, Person.Gender.MALE);
}
Review review = reviewer.review(book, rating, comments);
em.persist(reviewer);
listener.reviewAdded(review);
}
It is injected an EntityManager. In the web module, the servlet will use it as in the follows:
public class EJBRefSampleClientServlet extends HttpServlet {
@EJB
private examples.ejb.ejb30.ejbref.Process service_;
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
......
service_.addReview(uid, "guest", rate, comments);
......
}
We can see that here the servlet uses dependency injection to get the EJB. Local clients can use JNDI lookup as well as in the using remote EJB case, but dependency injection results in simpler code. See[4] for some examples.

Integration with JPA

In both of the scenarios above, the JSP or the Servlet does not use the JPA directly. Instead, the JSP/Servlet just uses a session bean and calls the business mehtod of the bean. The session bean implements the service by using the injected EntityManager from the J2EE container. But can the JSP/Servlet use the JPA directly? The answer is yes. But be very careful here. Since EntityManager is not thread-safe, the servlet should be injected an EntityManager and use it directly. There are two ways.
  1. Use @PersistenceUnit to inject an EntityManagerFactory into the servlet. This is to use application managed entity manager. In the service() method, you can then use the code like the following:
    EntityManager em = emf.createEntityManager();
    try{
      beginTransaction();
      em.persist(......)
      commitTransaction();
    }catch(Exception e){
      //rollback code
    }finally{
    em.close();
    }
    
  2. Use @PersistenceContext to declare a dependency on an EntityManager in the servlet and use JNDI to look up the EntityManager. You'll also need to write code to begin/commit/rollback the transaction.

How to Write a Session Bean

To create a remote session bean, use one of the following:
  1. Decorate the business interface with @Remote:
    @Remote
    public interface InterfaceName{...}
  2. Decorate the bean class with @Remote, specifying the business interface or interfaces:
    @Remote(InterfaceName.class)
    public class BeanName implements InterfaceName{...}

To create a local session bean, use one of the following:
  1. Create the business interface and the bean class without the @Remote or @Local annotation.
  2. Decorate the business interface with @Local:
    @Local
    public interface InterfaceName{...}
  3. Decorate the bean class with @Local, specifying the business interface or interfaces:
    @Local(InterfaceName.class)
    public class BeanName implements InterfaceName{...}
Although it is uncommon, it is possible for a session bean to allow both remote and local access. In this case, you can do one of the following:
  1. Use two business interfaces, one is decorated with @Remote and the other is decorated with @Local. Let the bean implement both these two interfaces
  2. The bean class explicitly designates the business interfaces by using @Remote and @Local.
Note that the same business interface cannot be both a local and remote business interface. See [5]. To create a stateless session bean for web service client, there are a few requirements. The bean is now an endpoint implementation class. Some requirements are:
  • The class must be annotated with either the javax.jws.WebService or javax.jws.WebServiceProvider.
  • The endpoint class must be annotated @Stateless.
  • The class must have a default public constructor.
  • Business methods that are exposed to web service clients must be annotated @WebMethod
The following is a sample from Java EE5 tutorial:
@Stateless
  @WebService
  public class HelloServiceBean{
    private String message = "Hello";
    public void HelloServiceBean(){}

    @WebMethod
    public String sayHello(String name){ 
      return message + name + ".";
    }
  }
There are some requirements on business methods.
  • If the bean allows remote access through a remote business interface, the arguemnts and return types must be legal types for the Java RMI API.
  • If the bean is a web service endpoint, the arguments and return types for the methods annotated @WebMethod must be legal types for JAX-WS.
  • The modifier must not be static or final.
The code of the ReviewManagerBean is the following:
@Stateless(mappedName="ReviewService")
@Remote(ReviewService.class)
public class ReviewManagerBean extends ReviewServiceImpl
implements ReviewService {
@PersistenceContext(unitName = "reviewSession")
private EntityManager em;
@EJB ReviewListener listener;

@PostConstruct
public void init(){
  // inject the EnityManager.
  injectEntityManager(em);
  addReviewAddedListener(listener);
}
The code contains the ReviewListener. This is not relevent to our topic now. It is used for other purpose. The important thing is how the session bean uses the JPA. The ReviewManagerBean lets the container to manage the transaction. The unitName is from persistence.xml. Notice that the transaction-type is JTA.
<?xml version="1.0"?>

<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
version="1.0">

<persistence-unit name="reviewSession" transaction-type="JTA">
<class>examples.ejb.ejb30.domain.Artist</class>
......
<properties>
<property name="kodo.ConnectionURL" value="jdbc:pointbase:server://localhost:9092/demo"/>
<property name="kodo.ConnectionDriverName" value="com.pointbase.jdbc.jdbcUniversalDriver"/>
<property name="kodo.ConnectionUserName" value="examples"/>
<property name="kodo.ConnectionPassword" value="examples"/>
<property name="kodo.jdbc.SynchronizeMappings" value="refresh"/>
</properties>
</persistence-unit>
</persistence>
ReviewManagerBean is injected an EntityManager instead of an EntityManagerFactory. The init() method calls injectEntityManager(em). But this is actually not needed. The reason why the ejb3 example code does this is that it wants to utilize the code ReviewServiceImpl which is for both container managed and application managed service. If you look at the code, you can see that the injected private instance variable em of ReviewManagerBean is not used directly. It will first get injected when the bean is created. And then the injectEntityManager method passes this instance to the parent class ReviewServiceImpl where the em is assigned to the private instance variable _em of ReviewServiceImpl. So if ReviewServiceImpl is not used, then the injection of em is enough. There would be no need to call injectEntityManager.
public void injectEntityManager(EntityManager em) {
_em = em;
}
The business method newArtist can be the following:
public Artist newArtist(String name) {
  if (name == null || name.trim().length() == 0)
   throw new RuntimeException("[" + name + "] can not be an Artist's name");
  EntityManager em = _em
  Artist artist = getPerson(Artist.class, name);
  if (artist == null) {
    artist = new Artist(name);
    em.persist(artist);
  } else {
    throw new ObjectExistedException("Artist named [" + name + "] exists");
  }
  return artist;
}

public  T getPerson(Class personType, String name) {
  return _em.find(personType, name);
}

Notes on Different Name Attributes in Anotation

  1. In @Stateless, @name() is the annotation equivalent of <ejb-name> in ejb-jar.xml. If no @Stateless name() is specified, it defaults to the unqualified bean class name.
  2. In @Stateless, mappedName() is used to assign a global JNDI name to the bean's remote interface.
  3. The mappedName only applies to Remote interface. If you're resolving a local interface the caller must be in the same application. For that, the beanName()/ejb-link should be used.
  4. There is a portability issue about mappedName. If a vendor doesn't support is you can resolve the EJB dependency using the vendor's config file without changing the source code of the standard .xml.
  5. If the .war file is not in the same .ear file as the ejb-jar, you have to use the Remote interface.
  6. beanName() and ejb-link only apply if the target remote ejb is defined within the same application as the referencing component.
See references [1] and [2] for more details.

References

[1] http://forums.sun.com/thread.jspa?threadID=5332558
[2] https://glassfish.dev.java.net/javaee5/ejb/EJB_FAQ.html#EJB_ejb-ref_ejb_local_ref
[3] http://weblogs.java.net/blog/ss141213/archive/2005/12/dont_use_persis_1.html
[4] http://www.developer.com/java/ejb/article.php/10931_3650661_5/EJB-3-Session-Beans.htm
[5] The Java EE5 Tutorial from Sun Microsystems.

How to Use Java Persistence API without other EJB Stuff

In EJB 3.0, the Entity beans are replaced by the Java Persistence API entities. An application can use JPA without touching any EJB stuff. The following is such an example about how to use JPA in a web application.

JPA Configuration

The JPA needs a file persistence.xml. Note that the transaction-type is "RESOURCE_LOCAL"
<?xml version="1.0"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
 version="1.0">
 <persistence-unit name="myService"
  transaction-type="RESOURCE_LOCAL">
  <class>examples.ejb.ejb30.domain.Item</class>
  <properties>
   <property name="xyz.ConnectionURL" value="..." />
   <property name="xyz.ConnectionDriverName" value="..." />
   <property name="xyz.ConnectionUserName" value="examples" />
   <property name="xyz.ConnectionPassword" value="examples" />
   <property name="xyz.jdbc.SynchronizeMappings"
    value="refresh" />
  </properties>
 </persistence-unit>
</persistence>

Web Module Configuration

In web.xml, a listener is needed:
<listener>
  <listener-class>example.ejb30.web.MyContextInitializer</listener-class>
</listener>
When initialized, the initializer will create an EntityManagerFactory and put it into the ServletContext. The code can be like the following. Notice that there is no object injection and JNDI lookup here.
public class MyContextInitializer implements ServletContextListener {
  private EntityManagerFactory emf;

  public void contextInitialized(ServletContextEvent servletContextEvent) {
    emf = Persistence.createEntityManagerFactory("myService");
    ......
    ServletContext context = servletContextEvent.getServletContext();
    context.setAttribute("coolService",emf);
}

  public void contextDestroyed(ServletContextEvent servletContextEvent) {
    //close EMF to free these resources maintained by EntityManagerFactory instances.
    emf.close();
  }
}

Using the JPA in Application Code

In a jsp file, it can be used as follows:
<%
EntityManagerFactory emf = (EntityManagerFactory) application.getAttribute("coolService");
MyServiceImpl service = new MyServiceImpl(emf);

service.doSomeBusiness();

%>
The method doSomeBusiness() needs to manage the transaction. The following is a sample method:
public Artist newArtist(String name) {
  if (name == null || name.trim().length() == 0)
   throw new RuntimeException("[" + name + "] can not be an Artist's name");
  EntityManager em = getEntityManager();
  beginTransaction();
  Artist artist = getPerson(Artist.class, name);
  if (artist == null) {
    artist = new Artist(name);
    em.persist(artist);
    commit();
  } else {
    rollback();
    throw new ObjectExistedException("Artist named [" + name + "] exists");
  }
  return artist;
}

protected final EntityManager beginTransaction() {
  EntityManager em = getEntityManager();
  if (!_isManaged && !em.getTransaction().isActive())
    em.getTransaction().begin();
  return em;
}

protected final void commit() {
  if (_isManaged)
    return;
  EntityManager em = getEntityManager();
  if (em.getTransaction().isActive())
    em.getTransaction().commit();
}

protected final void rollback() {
  if (_isManaged)
    return;
  EntityManager em = getEntityManager();
  if (em.getTransaction().isActive())
    em.getTransaction().rollback();
}
The tricky thing is how the method getEntityManager() is implemented by MyServiceImpl. The
weblogic 10 ejb3 example uses the following:
private final EntityManager getEM() {
  if (_em != null)
    return _em;
  if (_thread == null)
    _thread = new ThreadLocal<EntityManager>();
  EntityManager em = _thread.get();
  if (em != null)
    return em;
  if (_emf == null)
    return null;
  em = _emf.createEntityManager();
  _thread.set(em);
  return em;
}
Notice that ThreadLocal is used here. I think this is to ensure that only one EntityManager is used in the thread. And this is to resolve the thread safety issue for EntityManager because EntityManager is not thread-safe.

Tuesday, September 22, 2009

Various Springframework contexts in the J2EE application

A J2EE application can have EJB modules and web modules. These modules can integrate with the Springframework.

Integration of EJB and Springframework

Spring uses injection. The first obvious question is: How do the EJBs use the beans configured in Spring?
The answer seems to be that each Spring EJB bean has a private
instance variable beanFactoryLocator. This variable will store all the bean definitions from the various Spring xml files. (Note: It is a waste if there are many Spring EJBs. The javadoc below of AbstractEnterpriseBean says there is an option to use of a shared ApplicationContext between multiple EJBs.)
As a result of this bean configuration, the ejb-jar.xml will typically have the following for the EJB to indicate the path to the xml files to load the spring beans from:
<message-driven>
 <ejb-name>mdb1</ejb-name>
 <ejb-class>com.foo.Mdbean1</ejb-class>
 ......
 <env-entry>
  <env-entry-name>ejb/BeanFactoryPath</env-entry-name>
  <env-entry-type>java.lang.String</env-entry-type>
  <env-entry-value>
   classpath*:spring/fooContext.xml
  </env-entry-value>
 </env-entry>
 ......
</message-driven>
In the above, the value "ejb/BeanFactoryPath" should be written as is. It is hard-coded in the Spring class AbstractEnterpriseBean:
public static final String BEAN_FACTORY_PATH_ENVIRONMENT_KEY = "java:comp/env/ejb/BeanFactoryPath";
But this key might be able to be customized.

Now there is a house to store those beans. The outside code will need to interact with this house. So the next question is: How will the business logic use the beans in this house?
The answer seems to be that a bean in the house will be looked up using the bean factory and the bean name. The bean factory here refers to that bean "house". The following can be some code in some method of Mdbean1:
this.proxy = this.getBeanFactory().getBean(String
.valueOf(this.fooObjectLocator.getObject())));
The locator is a JndiObjectFactoryBean and is created in the constructor of the EJB. When the locator is created, you also set a jndi name. The following is some code from the constructor of Mdbean1.
if (this.fooObjectLocator == null)
    {
      this.fooObjectLocator = new JndiObjectFactoryBean();
      this.fooObjectLocator.setJndiName(FOO_OBJECT_PATH_ENVIRONMENT_KEY);
      try
      {
        this.fooObjectLocator.afterPropertiesSet();
      }
      catch (NamingException e)
      {
        LOGGER.fatal("OBJECT NOT FOUND[" + e.getMessage() + "]", e);
      }
    }
The spring EJBs inherit the following class:
public abstract class AbstractEnterpriseBean implements EnterpriseBean
{
  public static final String BEAN_FACTORY_PATH_ENVIRONMENT_KEY = "java:comp/env/ejb/BeanFactoryPath";

  /**
   * Helper strategy that knows how to locate a Spring BeanFactory (or
   * ApplicationContext).
   */
  private BeanFactoryLocator beanFactoryLocator;

  /** factoryKey to be used with BeanFactoryLocator */
  private String beanFactoryLocatorKey;

   ......

  void loadBeanFactory() throws BeansException
  {
    if (this.beanFactoryLocator == null)
    {
      this.beanFactoryLocator = new ContextJndiBeanFactoryLocator();
    }
    if (this.beanFactoryLocatorKey == null)
    {
      this.beanFactoryLocatorKey = BEAN_FACTORY_PATH_ENVIRONMENT_KEY;
    }

    this.beanFactoryReference = this.beanFactoryLocator
        .useBeanFactory(this.beanFactoryLocatorKey);

    // We cannot rely on the container to call ejbRemove() (it's skipped in
    // the case of system exceptions), so ensure the the bean factory
    // reference is eventually released.
    WeakReferenceMonitor.monitor(this,
        new BeanFactoryReferenceReleaseListener(this.beanFactoryReference));
  }
   .....
}
The javadoc of this class has the following:

* Provides a standard way of loading a Spring BeanFactory. Subclasses act as a
* facade, with the business logic deferred to beans in the BeanFactory. Default
* is to use a {@link org.springframework.context.access.ContextJndiBeanFactoryLocator},
* which will initialize an XML ApplicationContext from the class path (based on a JNDI
* name specified). For a different locator strategy, setBeanFactoryLocator
* may be called (before your EJB's ejbCreate method is invoked,
* e.g. in setSessionContext). For use of a shared ApplicationContext between
* multiple EJBs, where the container class loader setup supports this visibility, you may
* instead use a {@link org.springframework.context.access.ContextSingletonBeanFactoryLocator}.
* Alternatively, {@link #setBeanFactoryLocator} may be called with a custom implementation
* of the {@link org.springframework.beans.factory.access.BeanFactoryLocator} interface.


Various Spring contexts for EJB module, Web module, and Servlet, and their relationship

1. The ejb Mdbean1 has its Spring Context(the bean factory). For Spring message driven bean and stateless session bean, this bean factory is created in the ejb life cycle method ejbCreate(). Both the Spring class AbstractMessageDrivenBean and AbstractStatelessSessionBean have the following method:
public void ejbCreate() throws CreateException {
  loadBeanFactory();
  onEjbCreate();
}
The xml files that contain the Spring beans are defined in the ejb-jar.xml file:
<env-entry>
<env-entry-name>ejb/BeanFactoryPath</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value>
classpath*:spring/fooContext.xml
</env-entry-value>
</env-entry>
This Spring context is attached to Mdbean1 as an instance variable of type BeanFactoryLocator.

2. The web module has three Spring contexts. These contexts have nothing to do with the Spring context in the ejb module. The three contexts are:

(1) the parent context of the root web application context. It is created via the Spring ContextLoaderListener and the context parameters in the web.xml:
<context-param>
<param-name>locatorFactorySelector</param-name>
<param-value>classpath*:spring/beanRefContext.xml</param-value>
</context-param>
<context-param>
<param-name>parentContextKey</param-name>
<param-value>fooID</param-value>
</context-param>
......
......
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
Note that the param-name "locatorFactorySelector" is exactly the hard-coded value
LOCATOR_FACTORY_SELECTOR_PARAM in the code
servletContext.getInitParameter(LOCATOR_FACTORY_SELECTOR_PARAM);
And the param-name "parentContextkey" is exactly the hard-coded value LOCATOR_FACTORY_KEY_PARAM
in the code
servletContext.getInitParameter(LOCATOR_FACTORY_KEY_PARAM);
The two hard-coded values are defined in the class org.springframework.web.context.ContextLoader. There might be customized way to specify the key and the selector.
This context is created at the same time when the root web application context is created. And it is saved to the instance variable of the root web application context as its parent conetxt.
ApplicationContext parent = loadParentContext(servletContext);
this.context = createWebApplicationContext(servletContext, parent);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
Note: if the param "parentContextKey" is not specified in web.xml, the context will not be created. It will be null.
The actual method that creates the context is the API method from the javax interface ServletContextListener that the Spring class ContextLoaderListener implements:
public void contextInitialized(ServletContextEvent event) {
  this.contextLoader = createContextLoader(); 
  this.contextLoader.initWebApplicationContext(event.getServletContext());
}
(2) the root web application context. It is created via the same ContextLoaderListener that creates the parent context in (1) and the following parameter in the web.xml:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/foo1.xml /WEB-INF/foo-2.xml</param-value>
</context-param>
As shown by the code of ContextLoader in (1), it uses the context in (1) as its parent context. The context is attached to the ServletContext using a specific attribute.

(3) the context created by the Spring DispatcherServlet. It is created via the following in the web.xml file:
<servlet>
<servlet-name>xyz</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
The DispatcherServlet uses the servlet life cycle method init() to create the context and saves it to the private variable of type WebApplicationContext. This context uses the context created in (2) as its parent context. It gets the parent context using the ServletContext and the specific attribute. It uses the namespace to load the bean xml files. The servlet name in web.xml is "xyz". So it sets the namespace to "xyz-servlet". And by default, it will load the bean xml files from WEB-INF/xyz-servlet.xml.

How is the parent context used?

In the cases of the root web application context and the servlet Spring context, they have parent context and store it in the instance variable. What is the use of this parent context? I have not done any research on this. But I guess that when they look up a bean, they will first try to find it from the beans loaded through their own xml files. If they can not find the bean, then they will use the parent context to get it.

Some Important Beans in the Spring XML Configuration File

Beans for Transaction Management
Spring has its own transaction management mechanism. The following is a sample configuration in the bean xml file.
<bean id="myTransactionManager"
 class="org.springframework.transaction.jta.JtaTransactionManager">
</bean>
<bean id="matchAllWithPropReq"
 class="org.springframework.transaction.interceptor.MatchAlwaysTransactionAttributeSource">
 <property name="transactionAttribute">
  <value>PROPAGATION_REQUIRED,-MyCheckedException</value>
 </property>
</bean>

<bean id="matchAllTxInterceptor"
 class="org.springframework.transaction.interceptor.TransactionInterceptor">
 <property name="transactionManager">
  <ref bean="myTransactionManager" />
 </property>
 <property name="transactionAttributeSource">
  <ref bean="matchAllWithPropReq" />
 </property>
</bean>
The xml bean file that contains the business objects can have the following:
<bean id="autoProxyCreator"
 class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
 <property name="interceptorNames">
  <list>
   <idref bean="matchAllTxInterceptor" />
   <idref bean="other interceptors, can be user defined" />
  </list>
 </property>
 <property name="beanNames">
  <list>
   <idref local="foo1Bo" />
   <idref local="foo2Bo" />
   ......
   <idref local="foonBo" />
  </list>
 </property>
</bean>
Beans for View
The following bean is an example bean that maps the URLs to spring controllers in the Spring MVC framework.
<bean id="myUrlMapping"
 class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
 <property name="mappings">
  <props>
   <prop key="/foo1.htm">bar1Controller</prop>
   <prop key="/foo2.htm">bar2Controller</prop>
   ......
  </props>
 </property>
</bean>
The following is an example bean for message bundle.
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename">
<value>messages</value>
</property>
</bean>
The following is an example bean used for view resolver.
<bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
<property name="basename">
<value>views</value>
</property>
</bean>
In the WEB-INF/classes directory, you put two files messages.properties and views.properties. They can look like the following.
messages.properties:
messageId1=default message for Id1.
messageWithArguments=this message has arg1 {0} and arg2 {1}.
views.properties:
viewName1.(class)=org.springframework.web.servlet.view.InternalResourceView
viewName1.url=/jsp/view1.jsp
viewName2.(class)=org.springframework.web.servlet.view.InternalResourceView
viewName2.url=/jsp/view2.jsp

Wednesday, September 16, 2009

Configuration of Java Logger

Which logger is selected?

A commonly used logger is Apache Log4J. But there are also other loggers. And there are many default configuration which make the whole thing mysterious. Let’s take a look. In this analysis, we are using Apache commons-logging-1.0.4.jar and log4j-1.2.8.jar.

A typical first call to the logger is the following as in a class BatchJob.java:

public class BatchJob
{
  private static Log logger = LogFactory.getLog(BatchJob.class);
  ...
}

Most of the secrets are implemented in the following class
org.apache.commons.logging.impl.LogFactoryImpl extends LogFactory.
The class LogFactory is in the same package. The javadoc of the class LogFactoryImpl reveals how the logging implementation is selected. The javadoc is the following:

Concrete subclass of LogFactory that implements the following algorithm to dynamically select a logging implementation class to instantiate a wrapper for.
  1. Use a factory configuration attribute named org.apache.commons.logging.Log to identify the requested implementation class.
  2. Use the org.apache.commons.logging.Log system property to identify the requested implementation class.
  3. If Log4J is available, return an instance of org.apache.commons.logging.impl.Log4JLogger.
  4. If JDK 1.4 or later is available, return an instance of org.apache.commons.logging.impl.Jdk14Logger.
  5. Otherwise, return an instance of org.apache.commons.logging.impl.SimpleLog.
If the selected Log implementation class has a setLogFactory() method that accepts a LogFactory parameter, this method will be called on each newly created instance to identify the associated factory. This makes factory configuration attributes available to the Log instance, if it so desires. This factory will remember previously created Log instances for the same name, and will return them on repeated requests to the getInstance() method. This implementation ignores any configured attributes.

The selection algorithm will become clearer if you look at the actual code in the class LogFactoryImpl:

/**
* Return the fully qualified Java classname of the  Log 
* implementation we will be using.
*/
protected String getLogClassName() {
// Return the previously identified class name (if any)
if (logClassName != null) {
   return logClassName;
}
logClassName = (String) getAttribute(LOG_PROPERTY);
if (logClassName == null) { // @deprecated
   logClassName = (String) getAttribute(LOG_PROPERTY_OLD);
}
if (logClassName == null) {
  try {
    logClassName = System.getProperty(LOG_PROPERTY);
  } catch (SecurityException e) {;}
}
if (logClassName == null) { // @deprecated
  try {
    logClassName = System.getProperty(LOG_PROPERTY_OLD);
  } catch (SecurityException e) {;}
}
if ((logClassName == null) && isLog4JAvailable()) {
  logClassName = "org.apache.commons.logging.impl.Log4JLogger";
}
if ((logClassName == null) && isJdk14Available()) {
  logClassName = "org.apache.commons.logging.impl.Jdk14Logger";
}
if ((logClassName == null) && isJdk13LumberjackAvailable()) {
  logClassName = 
     "org.apache.commons.logging.impl.Jdk13LumberjackLogger";
}
if (logClassName == null) {
  logClassName = "org.apache.commons.logging.impl.SimpleLog";
}
return (logClassName);
}

In the test case that I executed with the batch job, if you put the log4j-1.2.8.jar in the classpath, the logger class used will be org.apache.commons.logging.impl.Log4JLogger. Otherwise, the logger class is org.apache.commons.logging.impl.Jdk14Logger.

How to configure log attributes.

One mistake that I made was to put something like the following in the command line:
-Dlog4j.configuration=file:/c:/workspaces/appName/src/config/jobs/job1.properties
Notice: In eclipse, the -Dlog4j.configuration must be set as VM arguments, not the Program arguments.
The purpose is for the logger to use the configuration in the job1.properties file. But it does not work! Finally I found that this is because I did not put log4j-1.2.8.jar on the classpath. Obviously the log4j.configuration option only works for Log4JLogger in log4j-1.2.8.jar, not other logger class such as Jdk14Logger !

Besides the command line option for specifying log attributes, a more familiar way is to use the log4j.properties file. From my test results, if you use Log4JLogger, you can put log4j.properties on the classpath, and it will be picked up by the logger. But what if you also specify the commandline –Dlog4j.configuration parameter? The answer is that the command line property file will override the log4j.properties file. This is not a surprise. I believe this is a convention of Java. For example, if you specify classpath using –cp option in the command line, it will override the environment variable CLASSPATH.

Advanced class loader stuff

Now the next thing that is interesting is how the LogFactoryImpl determines if log4j is available to use. The actual method is the following:

/**
* Is a Log4J implementation available?
*/
protected boolean isLog4JAvailable() {
  try {
    loadClass("org.apache.log4j.Logger");
    loadClass("org.apache.commons.logging.impl.Log4JLogger");
    return (true);
  } catch (Throwable t) {
    return (false);
  }
}
As a comparison, the following method determines if the jdk logger is available:

/**
* Return true if JDK 1.4 or later logging
* is available.  Also checks that the Throwable class
* supports getStackTrace(), which is required by
* Jdk14Logger.
*/
protected boolean isJdk14Available() {
  try {
    loadClass("java.util.logging.Logger");
    loadClass("org.apache.commons.logging.impl.Jdk14Logger");
    Class throwable = loadClass("java.lang.Throwable");
    if (throwable.
         getDeclaredMethod("getStackTrace", null) == null) {
      return (false);
    }
  return (true);
  } catch (Throwable t) {
    return (false);
  }
}.
In all cases, the essential method called is loadClass(), which is the following:

private static Class loadClass( final String name )
throws ClassNotFoundException
{
  Object result = AccessController.doPrivileged(
    new PrivilegedAction() {
      public Object run() {
        ClassLoader threadCL = getContextClassLoader();
        if (threadCL != null) {
          try {
            return threadCL.loadClass(name);
          } catch( ClassNotFoundException ex ) {
            // ignore
          }
        }
        try {
          return Class.forName( name );
        } catch (ClassNotFoundException e) {
          return e;
        }
      }
    });
  if (result instanceof Class)
    return (Class)result;
  throw (ClassNotFoundException)result;
}
Notice that a general programming rule is that we should not use exception for decision making. But in the special case here, we are using the ClassNotFoundException to determine if the logger implementation class is available.

The above method uses the following method, which is also in LogFactoryImpl:

/**
* Return the thread context class loader if available.
* Otherwise return null.
*
* The thread context class loader is available for JDK 1.2
* or later, if certain security conditions are met.
*
* @exception LogConfigurationException if a suitable class 
* loader cannot be identified.
*/
protected static ClassLoader getContextClassLoader()
throws LogConfigurationException
{
  ClassLoader classLoader = null;
  try {
    // Are we running on a JDK 1.2 or later system?
    Method method = Thread.class.
           getMethod("getContextClassLoader", null);
    // Get the thread context class loader (if there is one)
    try {
      classLoader = (ClassLoader)method.
           invoke(Thread.currentThread(), null);
    } catch (IllegalAccessException e) {
      throw new LogConfigurationException
            ("Unexpected IllegalAccessException", e);
    } catch (InvocationTargetException e) {
  /**
   * InvocationTargetException is thrown by 
   * 'invoke' when the method being invoked 
   * (getContextClassLoader) throws an exception.
   *
   * getContextClassLoader() throws 
   * SecurityException when the context class 
   * loader  isn't an ancestor of the calling class's 
   * class loader, or if security permissions are 
   * restricted.
   *
   * In the first case (not related), we want to ignore
   * and keep going.  We cannot help but also ignore 
   * the  second with the logic below, but other calls 
   * elsewhere (to obtain a class loader) will trigger 
   * this exception where we can make a distinction.
   */
       if (e.getTargetException() instanceof 
            SecurityException) {
         ;  // ignore
       } else {
   // Capture 'e.getTargetException()' exception for 
   // details alternate: log 'e.getTargetException()', 
   // and pass back 'e'.
         throw new LogConfigurationException
            ("Unexpected InvocationTargetException", 
              e.getTargetException());
       }
    }
  } catch (NoSuchMethodException e) {
    // Assume we are running on JDK 1.1
    classLoader = LogFactory.class.getClassLoader();
  }
  // Return the selected class loader
  return classLoader;
}

Configuration of java.util.logging.Logger

Some applications use java.util.logging.Logger. For example, the reference implementation of JSF from sun jsf-impl-1.2.jar uses this logger in the source code. How to configure it?
By default, this logger uses the configuration file in %JAVA_HOME%/jre/lib/logging.properties. In order to customize this, you can create a properties file, say C:\workshop\foo\logs\logging.properties. Then you need pass this as a parameter when invoking java:
-Djava.util.logging.config.file=C:\workshop\foo\logs\logging.properties 

The following is a sample file logging.properties:
#logging.properties
handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler
.level=FINE
java.util.logging.FileHandler.pattern = C:/workshop/foo/logs/java%u.log
java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter

This property file will cause the log to go to the file C:/workshop/foo/logs/java%u.log and the log level is FINE. Here %u is a count number. It will be 0, etc. See http://docs.oracle.com/javase/1.4.2/docs/api/java/util/logging/FileHandler.html for details about how to configure the location of this file.

Merge java.util.log into log4j

Say you are using Log4j in your application. And the application uses a third party library that uses java.util.logging. You can configure for each log. But the log messages will go to separate log files. You would like these log messages to go to the same log file in the time order that they are generated. How to do that? A web application using jsf-impl-1.2.jar of Sun is such an application because jsf-impl-1.2.jar uses java.util.logging. The following is one solution.
You can delegate the log from java.util.log to log4j. The website http://wiki.apache.org/myfaces/Trinidad_and_Common_Logging has a class JavaLoggingToCommonLoggingRedirector.java. To use this class in the web application. You can do the following.
  1. Copy the class JavaLoggingToCommonLoggingRedirector.java into your project. For example, you can have the following:
    package com.foo.example.util;
    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.logging.Handler;
    import java.util.logging.Level;
    import java.util.logging.LogManager;
    import java.util.logging.LogRecord;
    import java.util.logging.Logger;
    
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    
    /**
     * Writes JDK log messages to commons logging.
     */
    public class JavaLoggingToCommonLoggingRedirector {
            static JDKLogHandler activeHandler;
    
            /**
             * Activates this feature.
             */
            public static void activate() {
                    try {
                            Logger rootLogger = LogManager.getLogManager().getLogger("");
                            // remove old handlers
                            for (Handler handler : rootLogger.getHandlers()) {
                                    rootLogger.removeHandler(handler);
                            }
                            // add our own
                            activeHandler = new JDKLogHandler();
                            activeHandler.setLevel(Level.ALL);
                            rootLogger.addHandler(activeHandler);
                            rootLogger.setLevel(Level.ALL);
                            // done, let's check it right away!!!
    
                            Logger.getLogger(JavaLoggingToCommonLoggingRedirector.class.getName()).info("activated: sending JDK log messages to Commons Logging");
                    } catch (Exception exc) {
                            LogFactory.getLog(JavaLoggingToCommonLoggingRedirector.class).error("activation failed", exc);
                    }
            }
    
            public static void deactivate() {
                    Logger rootLogger = LogManager.getLogManager().getLogger("");
                    rootLogger.removeHandler(activeHandler);
    
                    Logger.getLogger(JavaLoggingToCommonLoggingRedirector.class.getName()).info("dactivated");
            }
    
            protected static class JDKLogHandler extends Handler {
                    private Map<String, Log> cachedLogs = new ConcurrentHashMap<String, Log>();
    
                    private Log getLog(String logName) {
                            Log log = cachedLogs.get(logName);
                            if (log == null) {
                                    log = LogFactory.getLog(logName);
                                    cachedLogs.put(logName, log);
                            }
                            return log;
                    }
    
                    @Override
                    public void publish(LogRecord record) {
                            Log log = getLog(record.getLoggerName());
                            String message = record.getMessage();
                            Throwable exception = record.getThrown();
                            Level level = record.getLevel();
                            if (level == Level.SEVERE) {
                                    log.error(message, exception);
                            } else if (level == Level.WARNING) {
                                    log.warn(message, exception);
                            } else if (level == Level.INFO) {
                                    log.info(message, exception);
                            } else if (level == Level.CONFIG || level == Level.FINE ) {
                                    log.debug(message, exception);
                            } else {
                                    log.trace(message, exception);
                            }
                    }
    
                    @Override
                    public void flush() {
                            // nothing to do
                    }
    
                    @Override
                    public void close() {
                            // nothing to do
                    }
            }
    }
    Note that I modified the class a little bit. I added the code "|| level == Level.FINE" to log FINE messages as DEBUG messages.
  2. In web.xml, add the following:
    <listener>
      <listener-class>
    com.foo.example.util.LogUtilListener</listener-class>
     </listener>
    
    The listener class is the following:
    package com.foo.example.util;
    
    import javax.servlet.ServletContextEvent;
    import javax.servlet.ServletContextListener;
    
    public class LogUtilListener implements ServletContextListener {
    
     public void contextInitialized(ServletContextEvent arg0) {
      JavaLoggingToCommonLoggingRedirector.activate();
     }
    
     public void contextDestroyed(ServletContextEvent arg0) {
      JavaLoggingToCommonLoggingRedirector.deactivate();
    
     }
    }
    
  3. Modify the log4j.properties to log the message from the third-party library. In our case, the library is jsf-impl-1.2.jar. Assume that you want to see the log messages from the package com.sun.faces.lifecycle in jsf-impl-1.2.jar. You can add the following:
    log4j.logger.javax.enterprise.resource.webcontainer.jsf=DEBUG,fileout
    
    Note that if you use
    log4j.logger.com.sun.faces=DEBUG,fileout 
    then it won't work even though the class LifecycleImpl is in the package com.sun.faces.lifecycle. This is surprising. But after looking at the source code of LifecycleImpl.java, you can see the reason. In that class, the logger is defined as follows:
    private static Logger logger = Util.getLogger(Util.FACES_LOGGER 
                + Util.LIFECYCLE_LOGGER);
    
    And Util.FACES_LOGGER has the value "javax.enterprise.resource.webcontainer.jsf" ! That is why we need to use this value in log4j.properties. You can also use the complete name "Util.FACES_LOGGER + Util.LIFECYCLE_LOGGER" which is equal to "javax.enterprise.resource.webcontainer.jsf.lifecycle". But since "javax.enterprise.resource.webcontainer.jsf" is a "parent" logger name of it, so that will cover the subpackages.
In the above example, I am not sure why LifecycleImpl uses "javax.enterprise.resource.webcontainer.jsf" instead of its package name as the logger name.
On the other hand, if you just use java.util.logging and check the log file, you can see that the log messages contain the package name and even the method name! The following is the sample:
Jan 30, 2012 3:03:07 PM com.sun.faces.lifecycle.LifecycleImpl execute
FINE: Skipping rest of execute() because of a reload
Jan 30, 2012 3:03:09 PM com.sun.faces.lifecycle.LifecycleImpl render
FINE: render(com.icesoft.faces.context.BridgeFacesContext@d0da01)
Jan 30, 2012 3:03:09 PM com.sun.faces.lifecycle.LifecycleImpl phase
FINE: phase(RENDER_RESPONSE 6,com.icesoft.faces.context.BridgeFacesContext@d0da01)
Jan 30, 2012 3:03:09 PM com.sun.faces.lifecycle.RenderResponsePhase execute
FINE: Entering RenderResponsePhase
Jan 30, 2012 3:03:09 PM com.sun.faces.lifecycle.RenderResponsePhase execute
FINE: About to render view /home.jspx
The corresponding log code in java in the class LifeCycleImpl.java is as follows:
public class LifecycleImpl extends Lifecycle {
...
  public void execute(FacesContext context) throws FacesException {
     ......
    for (int i = 1; i < phases.length; i++) { // Skip ANY_PHASE placeholder

            if (context.getRenderResponse() ||
                context.getResponseComplete()) {
                break;
            }

            phase((PhaseId) PhaseId.VALUES.get(i), phases[i], context);

            if (reload((PhaseId) PhaseId.VALUES.get(i), context)) {
                if (logger.isLoggable(Level.FINE)) {
                    logger.fine("Skipping rest of execute() because of a reload");
                }
                context.renderResponse();
            }
        }

    }
...
}
As the comparison, the log messages from log4j after the log merge are as follows:
2012-02-01 16:29:33,219 DEBUG [lifecycle]: Skipping rest of execute() because of a reload
2012-02-01 16:29:33,797 DEBUG [lifecycle]: render(com.icesoft.faces.context.BridgeFacesContext@8673f8)
2012-02-01 16:29:33,797 DEBUG [lifecycle]: phase(RENDER_RESPONSE 6,com.icesoft.faces.context.BridgeFacesContext@8673f8)
2012-02-01 16:29:33,797 DEBUG [lifecycle]: Entering RenderResponsePhase
2012-02-01 16:29:33,797 DEBUG [lifecycle]: About to render view /home.jspx
If you want to see the actual class name and method name of the log message from java.util.logging, you can modify the JDKLogHandler class:
public void publish(LogRecord record) {
  Log log = getLog(record.getLoggerName());
  String message = record.getMessage();
  Throwable exception = record.getThrown();
  String completeClassName = record.getSourceClassName();
  int pos = completeClassName.lastIndexOf(".");
  String className = completeClassName.substring(pos + 1);
  String methodName = record.getSourceMethodName();
  message = className + ":" + methodName + " " + message;
  Level level = record.getLevel();
              ...
}
Here we make use of the class java.util.logging.LogRecord. This class has the API that gives the class name and method name.

Note that the strategy used above to delegate java util log to log4j works for a single web application. If there are multiple web applications on the same server, the java util log in different web applications will be delegated to the same log file. To avoid this, you have to do something different. Reference[1] gives a solution for this.

References

  1. http://blog.mycila.com/2011/01/jdk-logging-per-webapp.html
  2. http://www.slf4j.org

Saturday, September 12, 2009

Blog Start

Today is Saturday. I created this google blog account. Toady's weather is mild. There is no rain. But it is sometimes cloudy and sometimes the sunshine come through the sky. I drove out in the morning and found that the top of some trees had already turned yellow or red. The fall season is coming.