Wednesday, November 14, 2012

The embedded EJB container in WebLogic Server 12c

The EJB 3.1 specification requires that all containers support embedded usage, with the additional requirement to support the EJB Lite feature set in an embedded container environment.

WebLogic 12c has had this feature since it's first release in Dec 2011, but apart from some sparse documentation on OTN, there was no material describing it's usage. I could not get it to work in a suitable manner, when I first attempted to write an Arquillian adapter earlier this year. Since then, I've had time to "peek" into it's internals and get it working.

Before we proceed, I'd like to issue a warning - use this at your own risk. Some of this is not documented in the WebLogic Server documentation at this time, and some use cases are most likely not supported by Oracle. These notes are based on the WLS 12c developer installation pack hosted on OTN.

The embedded EJB container provider

Starting the embedded container

The WLS embedded EJB container is initialized just like other embeddable containers:

EJBContainer container = javax.ejb.embeddable.EJBContainer.createEJBContainer();

At this point, an implementation of the javax.ejb.spi.EJBContainerProvider interface is located and loaded from the classpath. In the case of WLS 12c, the implementation and it's dependent classes can be found in weblogic.jar. It is also possible to use wlclient.jar or wlfullclient.jar instead, but I ended up running into CNFEs that I did not want to pursue; documentation of the required JARs for the embedded container would have helped here.

Note - copying over weblogic.jar and using it in your project is also bound to result in CNFEs. The weblogic.jar file uses relative paths in it's ClassPath manifest entry, and hence you would be required to replicate the entire file system structure of WL_HOME/modules and WL_HOME/server/lib at a minimum.

Note about using Surefire - If you're using Surefire to run your tests that depend on the EJBContainer API, you should add weblogic.jar as an additionalClasspathElement entry in your POM. Of course, you'll need to use an absolute path, but this way, you avoid the problems associated with installing weblogic.jar as a Maven artifact in your repository - you no longer need to figure out how to create a uber weblogic.jar or manage the transitive dependencies of the same.

Using the embedded container in a test

The most common use of an embedded container is in running tests. While embedded containers have their disadvantages over their standalone counterparts, developers tend to prefer embedded containers primarily for their startup performance.

For the sake of demonstrating a test involving an embedded container, consider a SLSB with a no-interface view:

package com.acme.ejb;

import javax.ejb.LocalBean;
import javax.ejb.Stateless;

@Stateless
@LocalBean
public class Greeter {

    public String sayHello(String name) {
        return "Hello " + name;
    }

}

The following JUnit4 test demonstrates the usage of the embedded container to test the SLSB:

package com.acme.test; 

import static org.hamcrest.core.IsEqual.equalTo; 
import static org.hamcrest.core.IsNull.notNullValue; 
import static org.junit.Assert.assertThat; 

import javax.ejb.embeddable.EJBContainer; 
import javax.enterprise.inject.spi.BeanManager; 
import javax.naming.Context; 

import org.junit.AfterClass; 
import org.junit.Before; 
import org.junit.BeforeClass; 
import org.junit.Test; 

import com.acme.ejb.Greeter; 

public class GreetingTest { 

    private static Greeter greeter; 
    private static EJBContainer ejbContainer; 
    private static Context ctx; 

    @BeforeClass 
    public static void setupContainer() throws Exception { 
        ejbContainer = EJBContainer.createEJBContainer(); 
        ctx = ejbContainer.getContext(); 
    } 

    @Before 
    public void setup() throws Exception { 
        greeter = (Greeter) ctx.lookup("java:global/classes/GreeterBean"); 
    } 

    @AfterClass 
    public static void shutdownContainer() throws Exception { 
        if (ejbContainer != null) { 
            ejbContainer.close(); 
        } 
    } 

    @Test 
    public void testGreeter() throws Exception { 
        String message = greeter.sayHello("World"); 
        assertThat(message, equalTo("Hello World")); 
    } 

}

While the @Test method itself completes in the order of milliseconds, the rest of the setup activity involving startup of the embedded container and deployment of the EJB runs into a few seconds (~20 on my workstation). If you are testing components of your application that use the EJB Lite features, this is a possible way to cut down on test execution time. I am of course assuming that you do not run into the typical issues involving embedded container usage.

I found it a bit surprising when I had to use the java:global namespace to lookup the deployed SLSB. Although the container bound the SLSB to several JNDI names, including the java:app and java:module namespaces, using the other names during lookup failed. This is either a bug, or a feature - WLS allows lookups in these namespaces from within the SLSB but not from the test class.


Note - the embedded container of WLS does not truly shutdown when the EJBContainer.close() method is invoked. A few user (non-daemon) threads continue to run, and therefore a complete shutdown occurs only when System.exit/Runtime.exit is invoked. Failure to invoke System.exit/Runtime.exit would leave the JVM in a running state. TestRunners usually do not have this problem, but if you're starting the embedded container in a main() method, you would need to terminate the JVM explicitly.

Permgen space issues

The embedded EJB container of WLS is not as lightweight as I expected it to be. It runs into issues with the space allocated to the permanent generation consistently. I used the below listed JVM flag to boot up the JVM that eventually runs the embedded container, to make the problem go away.

-XX:MaxPermSize=128M

The JVM had a max heap utilization of ~300 MB, and a max perm gen size of ~100 MB for the deployment of a single SLSB. Apparently, other services were also started along with the EJB container, and I didn't find a way to prevent them from starting (maybe they are necessary).

You may need to increase the heap-size as well as the permanent generation size if your application is larger.

Using an existing WLS domain

Note - this appears to be an undocumented feature at the moment. Use it at your own risk.

The embedded EJB container creates a new WebLogic domain upon initialization; the domain is created in the "java.io.tmpdir" directory. Sometimes, you might want to use an existing WLS domain, especially if you'd like to cut down on startup time. This is possible, but the mechanism is very kludgy and requires some knowledge of the WLS container startup mechanisms.

Unlike the GlassFish embedded container that supports usage of an existing GlassFish installation and instance, through the setInstallRoot() and setInstanceRoot() methods of the BootStrapProperties class, embedded WLS does not appear to have any documented/supported mechanism at the moment. However, a peek into the embedded container reveals a few things. The embedded EJB container looks for an existing WLS domain at the location defined by the "weblogic.RootDirectory" system property. Using this property one could point the embedded container to use a previously created WLS 12c domain, like so:

System.setProperty("weblogic.RootDirectory", "/home/vineet/Oracle/Middleware/user_projects/domains/embedded_domain");

This is still not sufficient though. The embedded container looks for a marker file ".embed-server-marker" at the root of the domain directory. An absence of the marker file will result in the container throwing an exception on startup. You can create a marker file in Linux, like so:

touch ~/Oracle/Middleware/user_projects/domains/embedded_domain/.embed-server-marker


Again, this is not sufficient to operate the embedded container with an existing domain. The boot identity would have to be passed to the container at startup. This is done via the "weblogic.management.username" and "weblogic.management.password" properties, like so:

System.setProperty("weblogic.management.username","weblogic");
System.setProperty("weblogic.management.password","welcome1"); 

Finally, (no, it's not over yet) you'll need to configure the domain to use a special startup class. In your $DOMAIN_HOME/config/config.xml file, add the following startup class that targets the admin server of your domain:

  <startup-class>
    <name>embedded-server-startup-class</name>
    <target>myserver</target>
    <class-name>weblogic.server.embed.internal.EmbeddedServerStartupClass</class-name>
    <failure-is-fatal>true</failure-is-fatal>
    <load-after-apps-running>true</load-after-apps-running>
  </startup-class> 

It is important to note that the Admin Server of the WLS domain is the one that is started in the embedded container.

This is also the time to reflect on why the marker file created earlier is important - domains created by the embedded container are configured in a special way (with the startup class at a minimum), and are possibly not recommended for continuous development. My assumption is that using the embedded container against an existing domain might change the domain configuration in an irrevocable manner, and hence the absence of marker file protects a typical WLS domain from these changes.

1 comment:

ناصر said...

really useful!
Can you tell about data source configuration?