Making the Most of Java 5.0: Enum Example

Tagged:  

"Hello World!" examples are great for general introductions to a concept, but often tend to lack much depth. So, as a follow-up to the original article on enums in Java 5.0, this article goes into a little more depth with a real-world example.

The Container Enum

Figure 1 below illustrates the enumeration of possible application servers to which an application can deploy. Why is this useful? Many organizations have more than one Java application server in different environments, and an EAR or WAR may need to be redeployed to different servers; or, you may be developing a common framework that will be used in different environments. It is often helpful in these situations to abstract many of the (static) variances between containers so that redeployment to a different environment can be done with a minimum of effort.

Figure 1: The Container enum.

public enum Container {
   WEBSPHERE, WEBLOGIC, JBOSS, NONE;
}

Now, let's say the application being designed requires an application-controlled JTA TransactionManager (if, for example, the standard way of doing XA transactions cannot be used). But how one is acquired varies pretty widely, depending on which environment the code is running. The variances here can be handled with an abstract method added to the enum, as in Figure 2.

Figure 2: The Container enum with factory methods.

public enum Container 
{
     WEBSPHERE() {
        private String[] factoryNames =
           {"com.ibm.ws.Transaction.TransactionManagerFactory",
            "com.ibm.ejs.jts.jta.TransactionManagerFactory",
            "com.ibm.ejs.jts.jta.JTSXA"};
        @Override
	public TransactionManager getTransactionManager() 
            throws Exception {
	     for (int i = 0; i < factoryNames.length; i++)  {
	         Method getTxMgr = null;
		 try {
		   //attempt to get class and method
		   Class<?> txMgrFactory = Class.forName(factoryNames[i]);
		   getTxMgr = txMgrFactory
                      .getMethod("getTransactionManager", null);	
		  } catch (Exception e) { 
		       continue;
		  }
		  //attempt to invoke method (let exception throw...)
                  return (TransactionManager) getTxMgr.invoke(null, null);
	     }
	     //if all else fails
	     return null;   //or throw exception
	}
     },

     WEBLOGIC() {
        @Override
	public TransactionManager getTransactionManager() 
          throws Exception {
           InitialContext ctx = null;
           try {
             ctx = new InitialContext();
             return (TransactionManager) ctx
               .lookup("java:comp/UserTransaction");
           } finally {
              if(ctx!=null) {
                 try { ctx.close(); } catch (NamingException e) {}
              }
           }
        }
     },

     JBOSS() {
      @Override
	public TransactionManager getTransactionManager() 
          throws Exception {
           InitialContext ctx = null;
           try {
             ctx = new InitialContext();
             return (TransactionManager) ctx
               .lookup("java:/TransactionManager");
           } finally {
              if(ctx!=null) {
                 try { ctx.close(); } catch (NamingException e) {}
              }
           }
        }
     },

    // Standalone environ or server w/o TM
    NONE() {
      @Override
	public TransactionManager getTransactionManager() 
          throws Exception {
	   //Use reflection to avoid necessary dependency
	   Class<?> clazz = Class.forName("org.objectweb.jotm.Jotm");
	   Constructor<?> constr = clazz.getConstructor(boolean.class, 
            boolean.class);
	   Object jotm = constr.newInstance(true, false);
	   Method getTxMgr = clazz.getMethod("getTransactionManager", 
            (Class[])null);
	   return TransactionManager.class.cast(getTxMgr
             .invoke(jotm, (Object[])null));
        }
    };

    // abstract method
    public abstract TransactionManager getTransactionManager() 
         throws Exception;
}

Here we are defining an abstract method getTransactionManager() as a factory method for getting or creating a JTA TransactionManager in a manner similar to the Spring Framework's JtaTransactionManager. Each enumeration instance must implement this method. In most cases, a JEE-compliant server will have a JNDI lookup that can be used. For WebSphere, there is no API for gettng a TransactionManager directly, but instead a time-honored backdoor for creating one. In the case of NONE (either a stand-alone environment or a server that has no TransactionManager), JOTM is used. Of course, the enum would be a lot shorter if the calls were delegated to factory classes, but that's an implementation detail.

Since the JMX default domain for most major Java application servers is self-describing, something like the following snippet of code can be used to "auto-sniff" the container environment:

     public Container lookupContainer() {
          String defaultDomain = ManagementFactory
              .getPlatformMBeanServer()
              .getDefaultDomain();
          try {
               return Container.valueOf(defaultDomain.toUpperCase());
          } catch (Exception e) { /*...assume none....*/ }
          return Container.NONE;   // or null if no default behavior
     }

After all of that, the client code to use it is a simple line:

lookupContainer().getTransactionManager():

Conclusion

The Container enum is a good illustration of the power of enumerations in Java compared to their C/C++ counterparts. By associating behavior with each enum instance, a smooth way of dispatching getTransactionManager() to the appropriate implementation has been created. This points out another fact: each enum instance (WEBSPHERE, WEBLOGIC, etc.) is implicitly a subclass of the enum class Container itself. This comes in handy since it means that many other object-oriented design patterns (Abstract Factory, Command, Visitor, and Strategy, to name a few) can be implemented using enums. Those, of course, are left as an exercise to the reader.

[...] Making the Most of Java 5.0: Enum Example By Brennan Spies examples are great for general introductions to a concept, but often tend to lack much depth. So, as a follow-up to the original article on enums in Java 5.0, this article goes into a little more depth with a real-world example. … Ajaxonomy - The Study of Ajax… - http://www.ajaxonomy.com [...]

[...] there's a second part to this article here. ShareCloseSocial WebE-mail [...]

[...] there's a second part to this article here. ShareCloseSocial WebE-mail [...]

is also possible to extend old enum with new values?
or override the old enums values in new one?

Not in Java 5. The older "Type-Safe Enum" pattern, you could extend these, of course, but it is not a good idea (due to the vagaries of static initialization); this is the reason enums in Java 5 were not made to be extended. You can get around this limitation by using interfaces, though, and having the enums implement these interfaces (Joshua Bloch describes this technique in "Effective Java" 2nd edition). I'll post an example later, when I get some time.

If you have two different enums that implement the same interface, you can extend the behavior of one with the other in a way similar to the Extension Object pattern.

If you need to add more enum values, this is not possible with the Java 5 enum. You can still do this with the "Type-Safe Enum" pattern, but I would steer clear of any static initialization logic in this case (such as the reverse lookup) and note that the behavior will be slightly different than the Java 5 version (i.e., you can't use them in switch statements, etc.).

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <pre> <div> <blockquote> <object> <embed> <img> <param>
  • Lines and paragraphs break automatically.
  • Web page addresses and e-mail addresses turn into links automatically.

More information about formatting options

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
Copy the characters (respecting upper/lower case) from the image.