Title: Configuration and Assembly
Disclaimer that we do tweak and change this code frequently, without notice. It is the very heart of OpenEJB. To keep things tight and clean, we reserve the right to change it at anytime. Do not consider it a stable public API.
First a glimpse of how OpenEJB looks internally. Here‘s a test that builds OpenEJB using it’s internal API. This is somewhat similar to how you might see people constructing Jetty in code. All our internal tests look like this.
This usage involves no xml parsing or classpath scanning. If you don‘t give it to OpenEJB, OpenEJB doesn’t know about it. This is OpenEJB with all the magic stripped away. At a high level:
ConfigurationFactory
.org.apache.openejb.jee
package contains JAXB trees for ejb-jar.xml, beans.xml and all the Java EE deployment descriptors.ConfigurationFactory
will produce a fully canonical version of the app called the Info
tree by:Info
tree isAssembler
will build and start the application exactly as described in the Info
tree.An example of what this looks like in code
import javax.ejb.LocalBean; import javax.ejb.Stateful; import javax.naming.InitialContext; import junit.framework.TestCase; import org.apache.openejb.assembler.classic.Assembler; import org.apache.openejb.assembler.classic.SecurityServiceInfo; import org.apache.openejb.assembler.classic.TransactionServiceInfo; import org.apache.openejb.client.LocalInitialContextFactory; import org.apache.openejb.config.ConfigurationFactory; import org.apache.openejb.jee.EjbJar; import org.apache.openejb.jee.StatefulBean; public class StatefulTest extends TestCase { @Override protected void setUp() throws Exception { System.setProperty(javax.naming.Context.INITIAL_CONTEXT_FACTORY, LocalInitialContextFactory.class.getName()); ConfigurationFactory config = new ConfigurationFactory(); Assembler assembler = new Assembler(); assembler.createTransactionManager(config.configureService(TransactionServiceInfo.class)); assembler.createSecurityService(config.configureService(SecurityServiceInfo.class)); EjbJar ejbJar = new EjbJar(); ejbJar.addEnterpriseBean(new StatefulBean(MyBean.class)); assembler.createApplication(config.configureApplication(ejbJar)); } public void test() throws Exception { InitialContext context = new InitialContext(); MyBean myBean = (MyBean) context.lookup("MyBeanLocalBean"); assertEquals("pan", myBean.echo("nap")); } @Stateful @LocalBean public static class MyBean { public String echo(String string) { StringBuilder sb = new StringBuilder(string); return sb.reverse().toString(); } } }
Slightly more detailed account of the above. Our startup and deploy world is broken into two phases:
configuration (app.jar -> AppInfo) we build up a fully normalized and validated tree. Some of the steps are
assembly (AppInfo -> actual running app) we assemble a running app as detailed by the AppInfo
The listings above aren't necesarrily complete or perfectly ordered, but generally show the nature of the work done in each phase.
A goal is that nothing gets through configuration and into assembly if it can‘t actually be built. The configuration phase is where we’re supposed to wipe away any ambiguity, fully normalize the app, make sure it‘s internally consistent, spec compliant and generally good to go. If it’s not, no worries as we actually haven‘t built anything permanent yet. Everything in the configuration phase is temporary. If it fails the configuration phase we just issue an error and say “App will not be loaded” and that’s it, there's nothing to undo.
The output of the configuration phase is what we call Info objects and the root of that tree is OpenEjbConfiguration. These objects are all simple, serializable data types with no methods, no constructors and no code or logic of any kind. We even have a test that uses ASM to walk down the Info tree and check that everything is compliant to these strict rules.
All of the aforementioned configuration phase sits behind this info object tree and an interface that produces it:
The job of the OpenEjbConfigurationFactory is simply to produce an OpenEjbConfiguration tree. With this simple decoupling when the time comes we can actually support much different styles of use/topologies. For example, a cluster scenario. We could create an OpenEjbConfigurationFactory implementation that actually pulled the OpenEjbConfiguration from a central store or some sort of configuration server of our creation. Perhaps, someday we write an OpenEjbConfigurationFactory implementation to wrap the existing one and look for any changed files. If nothing has changed since last boot, we simple deserialize an OpenEjbConfiguration tree saved from a previous boot as a way of reducing startup time on very large apps.
The assembly phase is where real running things are actually built. This process is inherently ingrained in the details on how OpenEJB works internally. Keeping it separated from descriptor parsing, validation, resolving, etc. keeps the actual “openejb building” code as simple as possible. It also allows for some flexibility and change to take place architecturally with less chance of it rippling through the entire system. However it's also not so generic (like spring, etc.) that becomes very difficult to get things built in a certain way or in a certain order requiring you to jump through several hoops just to keep the generic system as beautiful as possible. It knows all the details on how to build each individual part and in what order to build them.
In OpenEJB, the Assembler is not supposed to be the gem of the project that we keep clean, motivating us to push complex things out into other areas for other people (usually users) to worry about. In fact, it‘s the opposite. The runtime system gets top priority on it’s architectural needs and the assembler gets last priority. If there‘s something we can do in the Assembler that saves the rest of the system from complexity, we gladly throw the Assembler on that grenade. Our philosophy is that you can’t make 100% of your system “perfect” all the time and sometime the mess has to go somewhere. The assembler is where. It's purposely not over architected so that it can continue to serve as a place to take up slack and not make all this stuff harder than it has to be.