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?