Thursday, August 7, 2014

TomEE Startup - Source Code Explained

The TomEE website explains the TomEE's architecture in one sentence.
"We start with Apache Tomcat, add our jars and zip up the rest. The result is Tomcat with added EE features - TomEE"
In other words, TomEE is just adding jars to vanilla Tomcat and it does not change the one single line of the Tomcat code. So how these jars getting mixed up with Tomcat and cook the TomEE soup?

There are two (2) recipes:
  1. Add TomEE jars to Tomcat's lib directory
  2. Deploy TomEE jars as a droppin  war

Recipe 1 - Add TomEE jars to Tomcat's lib directory

 

  • The TomEE source code is built into set of jar files.
  • These jar files (including dependencies) are copied into the Tomcat /lib directory.
  • Register org.apache.tomee.catalina.ServerListener class in server.xml as a lifecycle listener.

This ServerListener is the trigger point of TomEE code.
Tomcat calls the ServerListener.lifecycleEvent method when a server lifecycle event occurres.

When Tomcat's "after_init" event occurred, the listener sets the required properties and calls the TomcatLoader.init.

TomcatLoader is the responsible class for initializing the rest.


Recipe 2 - Deploy TomEE jars as a droppin war

 

The goal of this deployment method is, load all the tomee libs to tomcat's common class loader and call the TomcatLoader.init method. 

To achieve this goal, a war file is created with the following directory structure

/ -
  | - lib                            - root-lib
  | - | - [All the TomEE jar files]
  | - WEB-INF
      | - classes
      |   | - juli.properties
      | - lib                         - webinf-lib
      |   | - tomee-loader*.jar
      | - web.xml

This war file has something unusual. that is duplicate lib directories. one is root-lib which exist on the root directory of the webapp. The other one is the typical lib directory which exist in the WEB-INF.

This war file is yet-another-war which exists on the webapp directory of the Tomcat. So tomcat will load this as a normal webapp in Tomcat startup.

The entire process is summarized to the following diagram.
A diagram explains the TomEE startup in code level


Explanation:

The Tomcat first reads the web.xml file under the WEB-INF. The web.xml file has defined the LoaderServlet with <load-on-startup> set to 1.

  <servlet>

    <servlet-name>LoaderServlet</servlet-name>

    <servlet-class>org.apache.tomee.loader.LoaderServlet</servlet-class>

    <load-on-startup>1</load-on-startup>

  </servlet>


This load-on-startup 1 signal the tomcat to load the LoaderServlet at the start up. This is the triggering point of the TomEE code with this recipe.


This org.apache.tomee.loader.LoaderServlet class is included in the tomee-loader*.jar file. So it should be placed in the WEB-INF/lib directory, otherwise Tomcat cannot resolve the LoaderServlet class.

What does the LoaderServlet do?


All the TomEE jars are placed in the lib directory which exists in the root of the tomee war. These libraries should ultimately be loaded and initialized within the Tomcat's common class loader.

The LoaderServlet sets the physical path of the webapp and call the TomcatEmbedder.embed() method. The Tomcat's classloader is passed as a parameter to this embed method.

 TomcatEmbedder.embed(properties, config.getClass().getClassLoader());

The TomcatEmbedder.embed() method creates a new WebappClassLoader (a mini webapp in other words). and load both tomee-loader*.jar and openejb-loader*.jar files to that WebappClassLoader using reflexion.

// Use reflection to add the tomee-loader.jar file to the repository of WebappClassLoader. 

File thisJar = getThisJar();

String thisJarUrl = thisJar.toURI().toString();

webappClClass.getMethod("addRepository", String.class).invoke(childCl, thisJarUrl);



// childCl.addRepository(openejb-loader.jar)

// Use reflection to add the openejb-loader.jar file to the repository of WebappClassLoader. 

File jarFile = findOpenEJBJar(openejbWar, OPENEJB_LOADER_PREFIX);

String openejbLoaderUrl = jarFile.toURI().toString();



webappClClass.getMethod("addRepository", String.class).invoke(childCl, openejbLoaderUrl);




**This mini webapp is optional for the LoaderServlet. The exact requirement of this Mini webapp will be discussed later.**

Finally, the TomcatHook.hook() mehtod is called using reflection by the TomcatEmbedder.

Important to notice that The TomcatHook class is loaded by the  newly created WebappClassLoader.

The TomcatHook sets some of the important properties and constructs an Embedder object. This Embedder is the class which does the magic of adding tomee libs to the Tomcat's common classloader.

// set the embedder

final Embedder embedder = new Embedder("org.apache.tomee.catalina.TomcatLoader");

SystemInstance.get().setComponent(Embedder.class, embedder);

embedder.init(properties); 


The embedder adds all the tomee, openejb & dependency jars to the classpath of the Tomcat common classloader.

FileUtils home = SystemInstance.get().getHome();

libsDir = home.getDirectory("lib");



classPath.addJarsToPath(libsDir); 


Now this Tomcat instance is similar to TomEE jars copied into the Tomcat's lib folder.

The Embedder calls the TomcatLoader.init method using reflection at last

// get the init method

Method init = loaderClass.getMethod("init", Properties.class);



// create the instance

Object instance = loaderClass.newInstance();



// invoke init method

Object value = init.invoke(instance, properties);



TomcatLoader does the initialization of the rest of the system.


Recipe 3 - Pre-cooked Recipe 2

 

One of the noticeable problem under the  dropin war deployment is, the possibility of deploying other webapps before the TomEE dropin war.

This delay is avoided by changing the triggering point of the war from Servlet to LifecycleListener. This lifecycle listener is named as "OpenEJBListener". This class resides in the tomee-loader*.jar

Steps:

  • put the TomEE dropin war into the webapp directory of Tomcat.
  • Copy tomee-loader*.jar file to Tomcat lib folder.
  • configure OpenEJBListener as a lifecycle listener in server.xml

The OpenEJBListener


When Tomcat starts, this lifecycle listener receives its first lifecycle event before deployment of any of the webapps.

The Listener does the following thing:
  1. Check whether the dropin war is available in the webapps directory
  2. extract if its still a war file
  3. sets the path of the webapp as a property.
  4. calls the TomcatEmbedder.embed
From this point onwards It is similar to the functionality of "Recipe 2".

The below diagram represents this architecture.


Why a mini-webapp

Time to explain the usage of mini-webapp creation of the TomcatEmbedder class.

The OpenEJBListener class is picked by Tomcat from the tomee-loader*.jar which is placed in the Tomcat/lib directory. When OpenEJBListener is calling the TomcatEmbedder, the TomcatEmbedder is loaded by the same Tomcat classloader. What we exactly need is to call the TomcatHook class within a WebappClassLoader. That is why the TomcatEmbedder creates a new WebappClassLoader and call the TomcatHook within that class loader.


In the LoaderServlet way, The TomcatEmbedder is loaded by a WebappClassLoader. So it was optional to create another WebappClassLoader and re-triggering the process.

Conclusion

The TomEE jars should be loaded to the Tomcat's Common classloader and calling the TomeeLoader within that class loader is the way that TomEE integrates with the Tomcat.

This is done in two ways.
  1. Put Tomee jars to lib folder and call TomeeLoader.
  2. pack tomee jars in a webapp and adding those jars to the ParentClassloader of that webapp's classloader. Call the TomeeLoader
  3. The 2nd way is triggered by either LoaderServlet or OpenEJBLIstener

No comments:

Post a Comment