GlassFish V3 Extensions, part 5 : Wombat container

With the release of the GlassFish v3, we have built a Java EE 6 application server that is :

  • Modular : based on OSGi, GlassFish v3 is a modular application server with a set of 200+ modules loaded on demand when users deploy applications that use such features.
  • Extensible : well, that's obvious that anything built on OSGi is extensible at the very least, extensible the OSGi way, but we have also added a set of APIs for fine control over extensibility points added to the product.
  • Embeddable : want to test your web app in maven ? you got it...

I am not going to dwell on a long laundry list of features we support or changed in this release, this would require a book (seriously, we rewrote so many parts, it's monumental) so I will just demonstrate some of these extension points by adding a new container to GlassFish. This new container will run a new component type called the Wombat component. Therefore I am proposing to write a wombat-container implementation in this entry. Sources can be found here.

Wombat Component

A wombat component is just a POJO annotated with a @Wombat annotation. The annotation looks like :

@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
public @interface Wombat {
    /\*\*
     \* Name to disambiguate different wombats
     \*
     \* @return a good wombat name
     \*/
    public String name();
} 

Nothing fancy here, there is a mandatory name attribute that need to be set to disambiguate various wombat components your application might package. Each wombat component has a set of public methods that make up its interface and can be called from any client code. The lifecycle of the wombat components is managed by the wombat container and such wombat components should be injectable in any Java EE components (that supports injection of course) without much effort.

Let's have a look at my first Wombat component :

@Wombat(name="simple")
public class SimpleWombat {
     public String saySomething() {
        return "Bonjour";
    }
}

Wombat Container, core functionalities

When implementing a GlassFish container extension, you need to implement 4 distinct interfaces, let's review them now :

  1. Sniffer : a sniffer is a singleton object that is instantiated when the first deployment (or application reload on startup) is invoked. Its main responsibility is to sniff the deployable artifacts and indicate to the deployment backend whether or not the deployable artifact contains components it is interested in. To make it clear, it sniffs the jars you deploy and check wether or not it contains Wombat components. The idea behind the sniffers is that they should be extremely lightweight since all sniffers get a change to check anything deployed, so you certainly don't want the sniffer to spend too much time doing so or loading too many modules to do their work.
  2. Container : a container is a singleton object again that represents the lifecycle of the component container. It's instantiated at the first deployment of a component (basically when the sniffer claims that at least one component of a supported type has been found in the deployable artifact). So here, first time you deploy an application (.jar) that contains at least one annotated class with @Wombat, the container is instantiated. In theory when the last component is un-deployed, we could destroy the container but many containers do not support being restarted so it stays alive until next startup when it won't be instantiated again (since there are no more component to serve). 
  3. Deployer : a deployer is also a singleton, thread safe object that deploys components to the container. In this case, the deployer is responsible for loading the component class, ensuring that everything is fine with the components inside the deployable artifacts and return a instance of a ApplicationContainer. 
  4. ApplicationContainer : there is one instance of that type per application and per container. So for instance, say you deploy 2 war files with wombat components embedded in, each deployment cycle will create a new instance of ApplicationContainer. These instances are responsible for suspend/resume and start/stop operations on the components. 

Still with me ? Good, it's almost over with the theory, one more thing : It's important to understand how everything works when dealing with applications containing several component types. For instance, take a war file containing a servlet and a wombat component. In such cases, each sniffer will pick up the application, a container of each type will be instantiated and the application will be deployed using each deployer returned by each container. At the end, deployment will end up with a number of ApplicationContainer instances (2 in the war example above). 

 Let's look now how this is implemented, first the sniffer :

@Service(name="wombat")
public class WombatSniffer implements Sniffer {
    public boolean handles(ReadableArchive source, ClassLoader loader) {
        return false;
    }
    public Class<? extends Annotation>[] getAnnotationTypes() {
        Class<? extends Annotation>[] a = (Class<? extends Annotation>[]) 
						Array.newInstance(Class.class, 1);
        a[0] = Wombat.class;
        return a;
    }
    public String[] getContainersNames() {
        String[] c = { WombatContainer.class.getName() };
        return c;
    } 

The interesting methods are handles() and getAnnotationTypes(). getAnnotationTypes() should return all annotations that identify components implemented by this container. In our example it's the @Wombat annotation type. You can imaging for instance that the EJBSniffer is returning @Stateless and other EJB related annotations. The returned annotation types are used by the deployment infrastructure to scan for all annotations in one pass rather than relying on each sniffer doing a pass for its annotation types. handles() should return true if the sniffing of the deployable artifact revealed any component supported by this container, this is useful when component can be defined in xml deployment descriptors. In this particular case, it returns false all the time since we only use annotations.

 Once the sniffer handles() returns true or if one of the annotation returned by getAnnotationTypes() has been spotted, the Container identified by getContainerNames() is instantiated. Let's look at that code : 

@Service(name="org.glassfish.examples.extension.WombatContainer")
public class WombatContainer implements Container {
    public Class<? extends Deployer> getDeployer() {
        return WombatDeployer.class;
    }
    public String getName() {
        return "wombat";
    }

As you can see the wombat container does not have many things to do when it's brought into memory, our web container for instance is slightly more complicated... The interesting method is the getDeployer(), let's look at the deployer code now :

@Service
public class WombatDeployer implements Deployer<WombatContainer, WombatAppContainer> {
    public boolean prepare(DeploymentContext context) {
        return false;
    }
    public WombatAppContainer load(WombatContainer container, DeploymentContext context) {
        WombatAppContainer appCtr = new WombatAppContainer(container);
        ClassLoader cl = context.getClassLoader();
        ReadableArchive ra = context.getOriginalSource();
        Enumeration<String> entries = ra.entries();
        while (entries.hasMoreElements()) {
            String entry = entries.nextElement();
            if (entry.endsWith(".class")) {
                String className = entryToClass(entry);
                try {
                    Class componentClass = cl.loadClass(className);
                    // ensure it is one of our component
                    if (componentClass.isAnnotationPresent(Wombat.class)) {
                        appCtr.addComponent(componentClass);
                    }
                } catch(Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }
        return appCtr;
    }
    public void unload(WombatAppContainer appContainer, DeploymentContext context) { }
    public void clean(DeploymentContext context) { }
    private String entryToClass(String entry) {
        String str = entry.substring("WEB-INF/classes/".length(), entry.length()-6);
        return str.replaceAll("/", ".");
    }

As you can see, Deployer has 4 important methods, prepare, load, unload and clean. Prepare is called when the application is loaded, application containers cannot rely on the final class loader to be available during the prepare phase. The load phase loads the component container but does not start the component, it should not be possible to make client invocations yet. Unload and clean are the undeployment/unloading counterparts of the prepare and load methods. The load() method implementation is rather not sophisticated, it loads all classes from the archive and checks if the class is annotated with the @Wombat annotation. If so, it keeps track of all classes that are wombat components. 

Finally, the last part, the ApplicationContainer implementation :

public class WombatAppContainer implements ApplicationContainer {
    final WombatContainer ctr;
    final List<Class> componentClasses = new ArrayList<Class>();    
    public WombatAppContainer(WombatContainer ctr) {
        this.ctr = ctr;
    }
    void addComponent(Class componentClass) {
        componentClasses.add(componentClass);
    }                      
    public boolean start(ApplicationContext startupContext) throws Exception {
        for (Class componentClass : componentClasses) {
            try {
                Object component = componentClass.newInstance();
                Wombat wombat = (Wombat) componentClass.getAnnotation(Wombat.class);
                ctr.habitat.addComponent(wombat.name(), component);
            } catch(Exception e) {
                throw new RuntimeException(e);
            }
        }
        return true;
    }
    public boolean stop(ApplicationContext stopContext) {
        for (Class componentClass : componentClasses) {
            ctr.habitat.removeAllByType(componentClass);
        }
        return true;
    }
    public boolean suspend() {
        return false;
    }
    public boolean resume() throws Exception {
        return false;
    }
} 

As you can see the code is fairly straightforward once again. On start, we instantiate all wombat components the deployer found, then we add them to the habitat so they can be looked up using the NamingManager. The stop command undo what the start command did and we don't support suspend/resume in this example.

We now have all the code necessary to implement our wombat container. Let's look now at the optional features one might want to add to a GlassFish V3 container. 

Configuration

it's of course important to add configuration for our new wombat container and have that configuration store in our central configuration file, the domain.xml. This is particularly important to users so that they feel the extension is well integrated with the rest of GlassFish. Also by using the configuration infrastructure, you get REST access to the configuration for free and other freebies. To be able to save your configuration to the domain.xml, you only need to declare a few annotated interfaces (similar to JAXB interfaces). So let's take the following configuration format :

<wombat-container-config number-of-instances="5">
    <wombat-element foo="something" bar="anything"/>
</wombat-container-config>

As you can see, this xml snippet is just defining the content of the wombat container, it is agnostic under which element in the domain.xml it will added to or even where it will be stored, that magic is handled by the GlassFish configuration.

Let's have a look at our 2 annotated interfaces defining this configuration :

@Configured
public interface WombatContainerConfig extends Container {
    @Attribute
    public String getNumberOfInstances();
    public void setNumberOfInstances(String instances) throws PropertyVetoException;
    @Element
    public WombatElement getElement();
    public void setElement(WombatElement element) throws PropertyVetoException;
} 
@Configured
public interface WombatElement extends ConfigBeanProxy {
    @Attribute
    public String getFoo();
    @Attribute
    public String getBar();
}

It's pretty much self-explanatory, 2 classes for the 2 elements. The parent element define a sub-element annotated with @Element interface, the attributes are annotated with @Attribute. This is all you need to do read and store configuration data from our domain.xml. The configuration backend will take care of implementing these interfaces with the necessary hooks to read and write the XML correctly (or whatever format we might choose in a future release). The wombat container can use the getter methods to access the configuration as it is defined in the domain.xml it is running with. 

One last thing, how can you get the configuration added to the domain.xml when the container has been newly added (first time access). Let's revisit the sniffer class with some new methods since it is the natural place to handle first time initializations. I am not repeating the methods I already mentioned above : 

@Service(name="wombat")
public class WombatSniffer implements Sniffer {
    @Inject(optional=true)
    WombatContainerConfig config=null;
    @Inject
    ConfigParser configParser;
    @Inject
    Habitat habitat;
    public Module[] setup(String containerHome, Logger logger) throws IOException {
        if (config==null) {
            URL url = this.getClass().getClassLoader().getResource("init.xml");
            if (url!=null) {
               configParser.parseContainerConfig(habitat, url, WombatContainerConfig.class);
            }
        }
        return null;
    } 

 The key here is that we have an optional dependency on WombatContainerConfig so if is not present in the domain.xml, the instance variable will remain null. When the first wombat deployment is under way, deployment infrastructure will call setup(). Within the setup method implementation, if the instance is null, that mean there is no wombat container configuration present in the domain.xml and we add it using the small xml snippet mentioned above (and packaged in the container jar file in the init.xml). The config parser is a utility API that can be used to parse a random xml snippet and add it to the right location in the domain.xml. By the way, location, location, yes but how is that location defined ? Look at WombatContainerConfig, it extends the Container interface, that's the marker interface that tell the configuration backend that it's a container configuration (we have a few documented extension hooks like this). After set up is called, the domain.xml will look like : 

<domain....>
.... 
   <configs>
    <config name="server-config">
      <wombat-container-config number-of-instances="5">
        <wombat-element foo="something" bar="anything" />
      </wombat-container-config>
      <http-service>
.... 
</domain>  

as you can see wombat-container-config element was added to the right location under config so that when clustering is enabled, the wombat container config can be referenced or have values specific to a node or a configuration.

Extras

 Now that we have added configuration, it would be nice to be able to change it. The simplest way to do that is to add a CLI command, but you can also add an admin GUI plugin if you feel more adventurous, it's not any harder. Let's look at the CLI admin command implementation.

@Service(name="wombat-config")
public class WombatConfigCommand implements AdminCommand {
    @Param
    String instances;
    @Inject
    WombatContainerConfig config;
    public void execute(AdminCommandContext adminCommandContext) {
        try {
            ConfigSupport.apply(new SingleConfigCode<WombatContainerConfig>() {
                public Object run(WombatContainerConfig wombatContainerConfig) 
			throws PropertyVetoException, TransactionFailure {
                    wombatContainerConfig.setNumberOfInstances(instances);
                    return null;
                }
            }, config);
        } catch(TransactionFailure e) {            
        }
    }
}

As you can see, he too gets the Wombat configuration injected and the command is defining a single parameter (instances) which will be used to change the number of instances attribute on the wombat configuration. Like any configuration change in GlassFish V3, the command must use a Configuration change transaction to ensure some ACID properties to the configuration change. 

This command can be invoked by doing :

asadmin wombat-config --instances 10

and you will see the wombat-config xml snippet in domains/domain1/config/domain.xml change from 5 (it's initial value) to 10. There are more extensions capability like I mentioned earlier, you can add an admin GUI plugin, you can have some code run when the user creates a new domain (to get once again some default configuration stored in the newly created domain.xml) and few other advanced goodies.

The client

So we have now a complete container implementation for wombat components, let's build it and install it in glassfish :

cd wombat-container
mvn install
cp target/wombat-container.jar <glassfishv3>/glassfish/domains/domain1/autodeploy/bundles

Now we need to create some wombat components and use them inside a client like a servlet. To do that, I am defining a web application which will contain a simple servlet and a single wombat component. Please note that this servlet is a converged application since it contains both Java EE artifacts like the servlet and foreign components like the Wombat component. The structure of this application is however a simple war file, no need to have to package the wombat components in a different file.

./pom.xml
./src
./src/main
./src/main/java
./src/main/java/components
./src/main/java/components/SimpleWombat.java
./src/main/java/HelloWorld.java
./src/main/webapp
./src/main/webapp/WEB-INF
./src/main/webapp/WEB-INF/web.xml

let's look at the sources, SimpleWombat.java first :

@Wombat(name="simple")
public class SimpleWombat {
    public String saySomething() {
        return "Bonjour";
    }
}

and the servlet :

@WebServlet(urlPatterns={"/hello"})
public class HelloWorld extends HttpServlet {
    @Resource(name="Simple")
    SimpleWombat wombat;  
    public void doGet(HttpServletRequest req, HttpServletResponse res)
            throws IOException, ServletException {
        PrintWriter pw = res.getWriter();
        try {
            pw.println("Injected service is " + wombat);
            if (wombat!=null) {
                pw.println("SimpleService says " + wombat.saySomething());
                pw.println("<br>");
            }
  	} catch(Exception e) {
        	e.printStackTrace();
        }
    }
} 

to use the client, just build and deploy

cd webclient
mvn install
asadmin deploy target/webclient.war

and access the page at http://localhost:8080/webclient/hello 

As you can see, I reuse the same @Resource type of dependency I used to inject my OSGi declarative services in my previous blog so you can really use only Java EE APIs to use extended features.  

Conclusion

 In this blog entry, I demonstrated how easy it can be to add a new container for new component types to GlassFish V3. You might ask yourself why would you ever need to do that and in all honesty, very few people will need to but the truth is that certain domain specific problems could be easily resolved by added domain specific components with their specific lifecycle, responsibilities or invocation methods. Also with the various web related framework flourishing, the scripting languages bases solutions, there are more and more application types these days. One more thing, we used the infrastructure described above to implement all of the Java EE containers (like EJB, Web, JPA, WebServices...) and others like our Rails containers so I am pretty confident you can leverage these features, we tested them well...

Comments:

Thanks for a good introduction to GlassFish custom containers.
How can I make it possible to inject EJBs or other resources into the Wombat components?

Regards
Patrik

Posted by Patrik on January 24, 2010 at 05:18 AM PST #

Post a Comment:
Comments are closed for this entry.
About

dochez

Search

Categories
Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today