Friday, January 9, 2015

Use JMX to change the log level dynamically

Use JMX to Change the Log level dynamically

In this note, we will create a program that can change the log level dynamically on the local or remote server. We will use Spring JMX exporter. Log4j is used. The applicaiton server is Weblogic 10.3.2. On the remote side, the application is deployed to a cluster of two weblogic managed servers weblogic01 and weblogic02. Note that on later versions of Weblogic such as version 11, the MBean server configurations may be different.

There are several files needed for this.

What are needed on the application side

1. In the application, create the following classes:

(1) IAppLogUtils.java

package com.yourcompany.app.server.utils;

public interface IAppLogUtils {
 public void updateAllLogLevels(String s);
}
(2) AppLogUtils.java
package com.yourcompany.app.server.utils;

import java.util.Enumeration;

import org.apache.log4j.Level;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;

public class AppLogUtils implements IAppLogUtils {
 public void updateAllLogLevels(String s) {
  Enumeration enumeration = LogManager.getCurrentLoggers();
  do {
   if (!enumeration.hasMoreElements())
    break;
   Logger logger = (Logger) enumeration.nextElement();
   logger.setLevel(Level.toLevel(s));
  } while (true);
 }
}
(3) In the application context xml file, add the following to use the Spring JMX exporter.
<bean id="jmxServerRuntime" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:comp/env/jmx/runtime"/>
</bean>

<bean id="appLogUtils" class="com.yourcompany.app.server.utils.AppLogUtils" />
  </bean>
 
 <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
  <property name="beans">
   <map>
    <entry key="APP:name=appLogUtils" value-ref="appLogUtils" /> 
   </map>
  </property>
   <property name="registrationBehaviorName" value="REGISTRATION_IGNORE_EXISTING"/>
  <property name="server" ref="jmxServerRuntime"/>
 </bean>
Two important notes here.

(a) A JVM can have multiple MBean servers. We will register our JMX bean on the weblogic runtime mbean server. Later on when we create the JMX client program to change the log level, we will connect to this mbean server. A test shows that if we do not specify the server property as in the above, Spring MBeanExporter will export the MBean to the JVM platform mbean server. On local machine, the jconsole program will detect this JVM platform mbean server. It does not need to have the username/password to access it.

(b) The value for the property "registrationBehaviorName" of the MBeanExporter is set. The default value is REGISTRATION_FAIL_ON_EXISTING, which will give error if Spring registers the Mbean with the same name twice. The error message is like this: "javax.management.InstanceAlreadyExistsException".

What are needed on the client side to invoke the JMX bean

It is relatively easy if the weblogic server is running locally. If the servers are remote, the program is still similar but a little bit more complex. The following program has code for both cases.

In the program, we assume that the two managed weblogic servers have the following host name and port number:

host1  14110
host2  14110

Note that the port numbers are the same because they are in the same cluster of weblogic managed servers. The weblogic admin server has a different port number. You can also connect to the admin server to get its JMX information. You need to use the username/password to connect to the admin server. Interestingly, you need to use the same username/password to connect to the managed weblogic servers for the JMX connection.

import java.io.IOException;
import java.util.Hashtable;
import java.util.Set;

import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;

import com.bea.core.repackaged.springframework.util.Assert;

public class WeblogicJMXClient {

        // this method changes the log level to "DEBUG" on the locally running weblogic server.
 public void testLocT3() throws Exception {
  JMXConnector jmxCon = null;

  try {
   JMXServiceURL serviceUrl = new JMXServiceURL("t3", "localhost",
     7001, "/jndi/weblogic.management.mbeanservers.runtime");

   System.out.println("Connecting to: " + serviceUrl);
   Hashtable env = new Hashtable();
   env.put(JMXConnectorFactory.PROTOCOL_PROVIDER_PACKAGES,
     "weblogic.management.remote");
   env.put(javax.naming.Context.SECURITY_PRINCIPAL, "username");
   env.put(javax.naming.Context.SECURITY_CREDENTIALS, "password");

   jmxCon = JMXConnectorFactory.newJMXConnector(serviceUrl, env);
   jmxCon.connect();
   MBeanServerConnection con = jmxCon.getMBeanServerConnection();

   Set<ObjectName> mbeans = con.queryNames(new ObjectName(
     "APP:name=appLogUtils"), null);
   ObjectName logService = (ObjectName) mbeans.iterator().next();

   Object result = con.invoke(logService, "updateAllLogLevels",
     new Object[] { "DEBUG" }, new String[] { String.class
       .getName() });
  } finally {
   if (jmxCon != null)
    jmxCon.close();
  }
 }

        
        /**
         * This method changes the log level to the specified value "INFO" or "DEBUG" on the remote 
         * weblogic server cluster
         */
 public void changDevLog(String logLevel) {
  Assert.isTrue("DEBUG".equals(logLevel) || "INFO".equals("logLevel"));
  JMXConnector jmxCon = null;
  // change on weblogic01
  try {
   jmxCon = getJMXConnector("t3", "host1",
     14110, "username", "password");
   changeLogLevel(jmxCon, logLevel);
  } catch (Exception e) {
   e.printStackTrace();
  } finally {
   if (jmxCon != null) {
    try {
     jmxCon.close();
    } catch (Exception ex) {
     ex.printStackTrace();
    }
   }
  }
  // change on weblogic02
  try {
   jmxCon = getJMXConnector("t3", "host2",
     14110, "username", "password");
   changeLogLevel(jmxCon, logLevel);
  } catch (Exception e) {
   e.printStackTrace();
  } finally {
   if (jmxCon != null) {
    try {
     jmxCon.close();
    } catch (Exception ex) {
     ex.printStackTrace();
    }
   }
  }
 }


 private JMXConnector getJMXConnector(String protocol, String host,
   int port, String username, String password) throws IOException {
  JMXConnector jmxCon = null;
  JMXServiceURL serviceUrl = new JMXServiceURL(protocol, host, port,
    "/jndi/weblogic.management.mbeanservers.runtime");
  System.out.println("Connecting to: " + serviceUrl);
  Hashtable env = new Hashtable();
  env.put(JMXConnectorFactory.PROTOCOL_PROVIDER_PACKAGES,
    "weblogic.management.remote");
  env.put(javax.naming.Context.SECURITY_PRINCIPAL, username);
  env.put(javax.naming.Context.SECURITY_CREDENTIALS, password);

  jmxCon = JMXConnectorFactory.newJMXConnector(serviceUrl, env);
  return jmxCon;
 }

 private void changeLogLevel(JMXConnector jmxCon, String level)
   throws Exception {
  jmxCon.connect();
  MBeanServerConnection con = jmxCon.getMBeanServerConnection();
  Set<ObjectName> mbeans = con.queryNames(new ObjectName(
    "APP:name=appLogUtils"), null);
  ObjectName logService = (ObjectName) mbeans.iterator().next();
  Object result = con
    .invoke(logService, "updateAllLogLevels",
      new Object[] { level }, new String[] { String.class
        .getName() });
 }

 public static void main(String[] args) throws Exception {
  WeblogicJMXClient c = new WeblogicJMXClient();
  //c.testLocT3();
         c.changDevLog("DEBUG");
  
 }
}

MBean Server on Weblogic

The following is about Weblogic 10.

At the core of any JMX agent is the MBean server, which acts as a container for MBeans.

In Weblogic, the JVM for an Administration Server maintains three MBean servers provided by BEA and optionally maintains the platform MBean server, which is provided by the JDK itself. The JVM for a Managed Server maintains only one BEA MBean server and the optional platform MBean server.

The three MBean servers provided by BEA are:

  1. Domain Runtime MBean Server. Only the Administration Server hosts an instance of this MBean server. Its INDI name is "weblogic.management.mbeanservers.domainruntime".
  2. Runtime MBean Server. Each server in the domain hosts an instance of this MBean server. Its JNDI name is "weblogic.management.mbeanservers.runtime".
  3. Edit MBean Server. Only the Administration Server hosts an instance of this MBean server. Its JNDI name is "weblogic.management.mbeanservers.edit".

The JVM's platform MBean server is provided by the JDK that contain monitoring information for the JVM itself. You can register custom MBeans in this MBean server, but BEA recommends that you register them in its Runtime MBean Server.

You can also configure the WebLogic Server Runtime MBean Server to be the platform MBean server, in which case the platform MBean server provides access to JVM MBeans, Runtime MBeans, and active configuration MBeans that are on a single server instance.

Register an MBean on Weblogic

Local clients can access a WebLogic Server instance’s Runtime MBean Server through the JNDI tree instead of constructing a JMXServiceURL object. Only the Runtime MBean Server registers itself in the JNDI tree.

If the classes for the JMX client are located at the top level of an enterprise application (that is, if they are deployed from the application’s APP-INF directory), then the JNDI name for the Runtime MBean Server is: java:comp/jmx/runtime

If the classes for the JMX client are located in a Java EE module, such as an EJB or Web application, then the JNDI name for the Runtime MBeanServer is: java:comp/env/jmx/runtime

In our case for the bean AppLogUtils, we use the JNDI name "java:comp/env/jmx/runtime". This bean AppLogUtils is a spring bean and is used in a web module deployed on the Weblogic server. So according to the above stated, this bean is registered at the Weblogic runtime MBean server. And in our subsequent code we do see that the JMX client connects to the runtime MBean server to manage this bean because it uses the JNDI name "/jndi/weblogic.management.mbeanservers.runtime".

One thing that may be misleading is the JNDI name "java:comp/env/jmx/runtime". This name looks like a java standard since it does not contain any thing about Weblogic. But it may just be a proprietary name used by Weblogic for the java platform MBean server. As stated before, all the three Weblogic MBean servers ( Domain Runtime MBean server, Runtime MBean server, Edit MBean server ) have their JNDI name and the client can connect to them programmatically using these JNDI names remotely. But there does not seem to be a JNDI name for the platform MBean server for the remote client to connect to. The JNDI name "java:comp/env/jmx/runtime" is only used locally. The following is from Oracle document about how to access the JVM platform MBean server remotely:
"Remote access to the platform MBean server can be secured only by standard JDK 1.5 security features (see http://java.sun.com/j2se/1.5.0/docs/guide/management/agent.html#remote). If you have configured the WebLogic Server Runtime MBean Server to be the platform MBean server, enabling remote access to the platform MBean server creates an access path to WebLogic Server MBeans that is not secured through the WebLogic Server security framework."

JVM Platform MBean Server

Someone on the internet says the following: "Note that the Platform MBean Server is always present and is created by the JVM during startup." But I am not sure about the meaning of "always present" here. It seems to me that by default, JVM will not create the MBean server during the startup. I did a simple test by using JDK1.7. I created a simple class Temp1.java that just calls the Thread.sleep method so it will not stop immediately after being executed. Then if I simply run the class using the command "java Temp1", jconsole will fail to connect to it. Only if I use the command "java -Dcom.sun.management.jmxremote Temp1", will the jconsole be able to connect to it. After doing more searching, I found the following from java7 doc:

In the Java SE 6 platform, it is no longer necessary to set this system property. Any application that is started on the Java SE 6 platform will support the Attach API, and so will automatically be made available for local monitoring and management when needed.

In the above, the "system property" refers to "com.sun.management.jmxremote". For remote access, the document is the following:

To enable monitoring and management from remote systems, you must set the following system property when you start the Java VM.
com.sun.management.jmxremote.port=portNum

In the property above, portNum is the port number through which you want to enable JMX RMI connections. Be sure to specify an unused port number. In addition to publishing an RMI connector for local access, setting this property publishes an additional RMI connector in a private read-only registry at the specified port using a well known name, "jmxrmi".

The Attach API mentioned above is worth studying. Ref[4] has some example code about the Attach API. The main ideas are the following.

  1. The Attach API is provided in the tools.jar file from JDK. The main class is VirtualMachine.
  2. The VirtualMachine class represents a specific Java virtual machine(JVM) instance. You connect to a JVM by providing the VirtualMachine class with the process id, and then you load a management agent to do your customized behavior.
  3. After you load the agent with loadAgent, you should call the detach method.
  4. A JMX agent exists in the managment-agent.jar file that comes with the JDK found in the same directory as tools.jar. The sample code is the following:
    VirtualMachine vm = VirtualMachine.attach(args[0]);
        String connectorAddr = vm.getAgentProperties().getProperty(
          "com.sun.management.jmxremote.localConnectorAddress");
        if (connectorAddr == null) {
          String agent = vm.getSystemProperties().getProperty(
            "java.home")+File.separator+"lib"+File.separator+
            "management-agent.jar";
          vm.loadAgent(agent);
          connectorAddr = vm.getAgentProperties().getProperty(
            "com.sun.management.jmxremote.localConnectorAddress");
        }
    
        JMXServiceURL serviceURL = new JMXServiceURL(connectorAddr);
        JMXConnector connector = JMXConnectorFactory.connect(serviceURL); 
        MBeanServerConnection mbsc = connector.getMBeanServerConnection(); 
        ObjectName objName = new ObjectName(
          ManagementFactory.THREAD_MXBEAN_NAME);
        Set mbeans = mbsc.queryNames(objName, null);
        for (ObjectName name: mbeans) {
          ThreadMXBean threadBean;
          threadBean = ManagementFactory.newPlatformMXBeanProxy(
            mbsc, name.toString(), ThreadMXBean.class);
          long threadIds[] = threadBean.getAllThreadIds();
          for (long threadId: threadIds) {
            ThreadInfo threadInfo = threadBean.getThreadInfo(threadId);
            System.out.println (threadInfo.getThreadName() + " / " +
                threadInfo.getThreadState());
          }
    
    
    
    In the above, args[0] should be the process ID of the target JVM.

It looks like that lots of operating system level functions are needed in the back of the scene for the code to work. You run the code containing the VirtualMachine class in your JVM to connect to the target JVM whose process ID is known to you. At the level of operating system, this is the communication between two processes. There is no port number involved in the above code. There is no TCP/IP in the above code.

One interesting consequence of the Attach API is this. Say that you run a program such as jconsole to try to connect to a JVM to get the JMX MBean server. Suppose that the connection works. Can you conclude from this that the MBean server has been started when the JVM is started? The answer is "NO". This is because that the MBean sever may have been started by your client program by using the Attach API!

References

  1. http://java.dzone.com/articles/taming-jmx-weblogic-server
  2. http://docs.spring.io/spring/docs/2.0.x/reference/jmx.html
  3. http://docs.oracle.com/cd/E11035_01/wls100/jmx/understandWLS.html#MBeanServers
  4. http://www.javaworld.com/article/2071330/the-attach-api.html

Tuesday, January 6, 2015

Order Management System

Order Management System (OMS) is a new software application that has been developed to help companies to manage orders, customers, product inventory, order shipment, etc. It is an excellent product that can greatly improve business. For details, see the following link: http:www.lakexy.com

Java Annotation Retention

Annotation Retention Policy

Java annotation works by adding some code to the java file or the compiled java class file. The notation used is the @Retention tag.

The following is the overall picture:
Source File( A.java) --> Compiled bytecode(A.class) --> Runtime memory after loaded by classloader

There are 3 Retention policies. In the following, we use a '*' to indicate that some annotation code has been inserted.

@Retention(RetentionPolicy.SOURCE)
A*.java --> A.class --> A-runtime

@Retention(RetentionPolicy.CLASS)
A*.java --> A*.class --> A-runtime
This is the default policy.

@Retention(RetentionPolicy.RUNTIME)
A*.java --> A*.class --> A*-runtime.
This is generally used for customer annotations.

Java Annotation Implementation

Java annotation is not part of the java standard edition. You can see that the package is javax.annotation. So a java server container such as Weblogic server and Tomcat serfver is generally needed for the annotation to work. The server container has to implement the annotation artifacts so it knows how to parse, compile and run the java classes with the annotations. Of course, you can also put the needed library for annotation on the classpath to run the java programs with annotations successfully without using a sever container.

Wednesday, October 29, 2014

To Take Foreign Language Input in JSF Applicaiton

The environment I use is the following: Tomcat 6, MySQL 5.5, JSF2 Facelets. The application needs to be able to allow the user to type in foreign characters such as Chinese characters and save them into the MySql database. To accomplish this, the following is the list of changes that will make it work. Some of them are actually not needed.
  1. The column that can take the input is "comment" in the table xyz. Its type is TEXT. Do the following to modify the character set of the column:
    alter table xyz modify comment text character set utf8;
    
  2. I use the file META-INF/context.xml for the datasource:
    <Context>
        <Manager pathname="" />
     <Resource name="jdbc/inventory" auth="Container" type="javax.sql.DataSource"
      url="jdbc:mysql://localhost:3306/yourDBName" 
      ...
         />
    </Context>
    
    Change the url to:
      url="jdbc:mysql://localhost:3306/yourDBName?useUnicode=true&amp;characterEncoding=UTF-8" 
    
  3. In the file C:\apache-tomcat-6.0.35\conf\server.xml, add a line URIEncoding="UTF-8" in the Connector configuration:
    <Connector port="8080" protocol="HTTP/1.1" 
                   connectionTimeout="20000" 
                   redirectPort="8443"
                   URIEncoding="UTF-8" />
    
    But this may not be needed.
  4. All my xhtml pages use a template. In the template file, there is the line
    <h:head>
     <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
            ...
    </h:head>
    
    Change the charset in the line to charset=utf-8. But this is actually not needed.
  5. I use log4j.xml for the log file. Add a line for Encoding as below. This way the log file will not just show ? for the Chinese characters.
    <appender name="rollingFileSize" class="org.apache.log4j.RollingFileAppender">
      <param name="File" value="${logfile}" />
      <param name="MaxFileSize" value="20MB" />
      <param name="MaxBackupIndex" value="10" />
      <param name="Encoding" value="UTF-8" />
      ...
     </appender>
    
  6. In the faces-config.xml file, there is usually the following:
    <application>
        ...
        <locale-config>
         <default-locale>en</default-locale>
         <supported-locale>es</supported-locale>
        </locale-config>
     </application>
    
    You can add the following to support Chinese.
     <supported-locale>zh_CN</supported-locale>
    
    But it is not needed for the purpose to accept Chinese character input.

Friday, October 17, 2014

Spring JMS Caching

Note: Spring 2.5.6 is used in this analysis.

Use Spring JmsTemplate to Send Message

when Spring JmsTemplate is used to send message, we can use the method

public void send(MessageCreator messageCreator){}. 
It will call
public Object execute(SessionCallback action, boolean startConnection){}.
The variable startConnection is false in this case. In our test case, that method will call
conToClose = createConnection();
This method is
protected Connection createConnection() throws JMSException {
  return getConnectionFactory().createConnection();
 }
After sending the message, it will finally call
ConnectionFactoryUtils.releaseConnection(conToClose, getConnectionFactory(), startConnection);

For a simple ConnectionFactory (for example:com.tibco.tibjms.TibjmsConnectionFactory)), when the send(...) method is called, it will create a new connection instance and a new session instance and a new producer to send the message. And then in the finally{} block, the connection and the session will be closed. And next time the send(...) is called, a new connection and session will be created.

But if the Spring SingleConnectionFactory is used, the connection will be reused. But the session and the producer are not reused. You can construct a SingleConnectionFactory to wrap a simple connection factory:

public SingleConnectionFactory(ConnectionFactory targetConnectionFactory);

The class CachingConnectionFactory extends the Spring SingleConnectionFactory. When it is used,the connection, session, and the producer will all be reused. You can construct a CachingConnectionFactory to wrap a simple factory factory:

public CachingConnectionFactory(ConnectionFactory targetConnectionFactory);

For the CachingConnectionFactory, when the

conToClose = createConnection();
is executed, it will return a proxy of the connecion. The proxy is of the type SingledConnectionFactory@SharedConnectionInvocationHandler. The trick is in the "close" method of the connection. When the "finally" block closes the connection,the proxy just does nothing! The connection and the session will be reused next time the send(...) method is called.

Recovery

Note that when a JmsTemplate bean is created, no matter what connection factory is used, the connection is not established. The JMS connection is created the first time a message needs to be sent. Because of this, the application can start successfully even if the JMS connection does not work at the startup time.

When a message needs to be sent, JmsTemplate will create a connection. If there is an exception occuring, the process will be stopped.There is no retry.

We can create a mechanism to have the retry ability. Instead of calling the send(messageCreator) method of JmsTemplate directly, we can wrap it as follows:

 void mySend(MessageCreator messageCreator) throws JmsException {  
   while (true) {
      try {
         this.jmsTemplate.send(messageCreator);
         break;
      } catch (JmsException e) {
         handleException(e, tries);
      }
   }
 }
In this code, if there is anything wrong, handleException can handle it any way you want. For example, it can sleep a while and let the loop execute again. You can configure the number of the retries. Then after the maximum retries have been attempted unsuccessfully, handleException will throw the exception that will end the while-loop.

Note that everytime the send(messageCreator) method of JmsTemplate is called, it will create a connection, session,and producer to send the message. The connection is created from a connection factory. So if it is a cached factory such as the CachingConnectionFactyory or SingleConnectionFactory, the same connection may be returned. But the connection may be bad. So in handleException, you can call the onException() method of SingleConnectionFactory, which will reset the connection. And the next time the connection factory will create a brandnew connection when asked.

Test Results

The following is the actual test data. I used the debug in running the test. Tibco JMS is used on the backend. The basic code is the following:

        public void process(String[] args) throws Exception {  
  IJmsSender jmsSender = (IJmsSender) getBean("jmsSender");
  test(jmsSender);
 }

 private void test(IJmsSender jmsSender) {
  try {
   String msg = "msg1";
   jmsSender.sendMessage(msg);
   // second time
   msg = "msg2";
   jmsSender.sendMessage(msg);
   // third time
   msg = "msg3";
   jmsSender.sendMessage(msg);
  } catch (Exception e) {
   e.printStackTrace();
  }
 }
The jmsSender is just a POJO. It uses the following method to send message:
       public void sendMessage(final String message) {
  jmsTemplate.send(new MessageCreator() {
   @Override
   public Message createMessage(Session session) throws JMSException {
    BytesMessage bytesMsg = session.createBytesMessage();
    bytesMsg.writeBytes(message.getBytes());
    return bytesMsg;
   }

  });
 } 

To check the connection, session and producer object details, I found these objects have the objectDelegate attribute. That attribute is an object and it contains many data. The important one is the id value: _connid, _session_id, and _prodid. In the test, the message is sent 3 times.

If TibjmsConnectionFactory is used, every time a new _connid, _sessionid, and _prodid will be generated.

If SingleConnectionFactory is used, the results are below for the 3 sending:

                        
                        SingleConnectionFactory
connection objectDelegate          session objectDelegate              producer objectDelegate
(TibjmsConnection)                  (TibjmsxSessionImpl)                (TibjmsTopicPublisher)
_connid                              _sessionid                         _prodid
51292                              103950                               45146
51292                              103977                               45155
51292                              103978                               45156

If CachingConnectionFactory is used, the results are below for the 3 sending:

                        
                        CachingConnectionFactory
connection objectDelegate          session objectDelegate              producer objectDelegate
(TibjmsConnection)                  (TibjmsxSessionImpl)                (TibjmsTopicPublisher)
_connid                              _sessionid                         _prodid
51306                              103981                               45157
51306                              103981                               45157
51306                              103981                               45157

CachingConnectionFactory's cacheProducers Property

CachingConnectionFactory has a property cacheProducers. It is not inherited from SingleConnectionFactory. Its default value is true. If the value of this property is set to false, the producer will not be reused. So in the test case for CachingConnectionfactory above, if cacheProducers is false, the _connid and _sessionid will still be the same in the three message sendings. But the _prodid will be all different!

Note that according to the javadoc of the class, the cache is to cache JMS MessageProducers per JMS session instance (more specifically: one MessageProducer per Destination and Session).

CachingConnectionFactory's cacheConsumers Property

CachingConnectionFactory has a property cacheConsumers. It is not inherited from SingleConnectionFactory. Its default value is true. For JmsTemplate to send messages, this value does not affect the caching of the connection, session and the producer. So if the value is set to false, the 3 sendings in the above test ( suppose cacheProducers is the default true ) will use the same _connid, _sessionid, and _prodid.

Note that according to the javadoc of the class, the cache is to cache JMS MessageConsumers per JMS Session instance ( more specifically: one MessageConsumer per Destination, selector String and Session). The Durable subscribers will only be cached until logical closing of the Session handle.

SingleConnectionFactory's reconnectOnException Property

The class SingleConnectionFactory has a property reconnectOnException. The basic work flow is the following.

1. An application will first call the createConnection method of the Factory to get a JMS connection. The method in SingleConnectionFactory is

public Connection createConnection() throws JMSException {
  synchronized (this.connectionMonitor) {
   if (this.connection == null) {
    initConnection();
   }
   return this.connection;
  }
 }
Since the connection is initially null, it will call initConnection() to create a connection.

2. The initConnection() method is

public void initConnection() throws JMSException {
  if (getTargetConnectionFactory() == null) {
   throw new IllegalStateException(
     "'targetConnectionFactory' is required for lazily initializing a Connection");
  }
  synchronized (this.connectionMonitor) {
   if (this.target != null) {
    closeConnection(this.target);
   }
   this.target = doCreateConnection();
   prepareConnection(this.target);
   if (logger.isInfoEnabled()) {
    logger.info("Established shared JMS Connection: " + this.target);
   }
   this.connection = getSharedConnectionProxy(this.target);
  }
 }
Inside this method, it calls prepareConnection. This method will check if the attribute reconnectOnException is true. If yes, it will add this SingleConnectionFactory instance as an internal listener to the listener chain. Note that the SingleConnectionFactory implements the ExceptionListener interface. So it is also an exception listener.

3. The user of the JMS connection will use the connection. Since the SingleConnectionFactory implements the connection proxy, it will not close the actual connection when the close method is called. So next time the user calls the createConnection method, the connection is not null and the same connection will be returned.

4. Now if for some reason, the SingleConnectionFactory is invoked as an Exception listener, it will execute the mehtod

 /**
  * Exception listener callback that renews the underlying single Connection.
  */
 public void onException(JMSException ex) {
  resetConnection();
 }
/**
  * Reset the underlying shared Connection, to be reinitialized on next access.
  */
 public void resetConnection() {
  synchronized (this.connectionMonitor) {
   if (this.target != null) {
    closeConnection(this.target);
   }
   this.target = null;
   this.connection = null;
  }
 }
So after this, the connection will be closed and set to null.

5. The next time the createConnection method is called, since the connection is null, a new connection will be created.

Now a big question. When will the SingleConnectionFactory be invoked as an Exception listener? If you look at the code of JmsTemplate, its send() method can throw JmsException. The user will handle the JmsException. Nowhere is the Exception listener invoked in JmsTemplate.

After reading more code, I found that the ExceptionListener is invoked by the standard JMS contract. The JMS provider should invoke the ExceptionListener when it detects a serious problem. All the programmers need to do is to register the ExceptionListener on the connection using the Standard JMS API.

In the method prepareConnection(Connection con) of SingleConnectionFactory, the last line is

   con.setExceptionListener(listenerToUse);
The setExceptionListner method is an API method of javax.jms.Connection. Its javadoc is the following:
   
       Sets an exception listener for this connection.
       If a JMS provider detects a serious problem with a connection, it informs the 
connection's ExceptionListener, if one has been registered. It does this by calling 
the listener's onException method, passing it a JMSException object describing the 
problem.
       An exception listener allows a client to be notified of a problem 
asynchronously. Some connections only consume messages, so they would have no other 
way to learn their connection has failed.
   
       A connection serializes execution of its ExceptionListener.
   
       A JMS provider should attempt to resolve connection problems itself before it 
notifies the client of them.
   
       Parameters:
           listener - the exception listener 
       Throws:
           JMSException - if the JMS provider fails to set the exception listener for 
this connection.
       void 
    setExceptionListener(ExceptionListener listener) throws JMSException;
But what is considered "a serious problem" by a JMS provider? It is still not clear. A JMSException thrown in the user code does not seem to be related to this issue. When a JMSException is thrown during the runtime, JmsTemplate will just convert it to a JmsException and throw it back to the user for handling. The method is below.
    public Object execute(SessionCallback action, boolean startConnection) throws JmsException{
     try{
      ..........
     }catch(JMSException es){
       throw convertJmsAccessException(ex);
     }finally{
       ..........
     }
  }
I did a test to let the MessageCreator throw a JMSException when the second message is sent. And I catch the exception outside the jmsTemplate class and just ignore it. The third message still uses the same _connid, _sessonid, and _prodid.

By the way, note that JmsException is different from JMSException. The former is a Spring class. The later is a standard java class.

Friday, October 3, 2014

On the "nillable" Attribute in XML Schema

In the XSD, you can define an element that is nillable:
  <xs:element name="abc" nillable="true" minOccurs="1" type="xs:string"/>
What does this "nillable" exactly mean?

If the element has to be there and you want it to be able to have null value, then you need to define nillable="true". And you can do the following in the XML file:

  <abc xsi:nil="true" />
The JAXB unmarshaller will create the object with abc=null.

Note that if you do <abc/> or <abc></abc>, then the element "abc" will have the value "". It is not null.

If the element does not need to be there, i.e, if minOccurs="0", then you do not need to have nillable="true". You can simply skip this element in the XML file. And the JAXB unmarshaller wil create an object with abc=null.

For element of type int, JAXB will create the Integer object for the element. So the effect will be the same as String when an int is nillable.

For JAXB marshaller, if the object has abc=null, then if in the schema minOccurs="0" for the element, no matter whether abc is nillable or not, the generated XML file will not have this element. But If minOccurs="1", then if nillable="false", the generated XML file will not have this element; if nillable="true", the generated XML file will be <abc xsi:nil="true" />.

Saturday, September 13, 2014

Fifth year of blog

Yesterday is Sept. 12. It's been 5 years since this blog was created. This past summer was not hot in Pittsburgh. I think there were only several days with the temperature over 90F. Today's temperature is 44 - 65, cloudy. There was some rain yesterday.

I do not have a good harvest in the garden. The tomatoes did not grow well. Probably it is because that I have planted the tomatoes in the same area in the last several years.