Sun GlassFish Enterprise Server v3 Prelude Add-On Component Development Guide

Chapter 6 Adding Container Capabilities

Applications run on Enterprise Server in containers. Enterprise Server enables you to create containers that extend or replace the existing containers of Enterprise Server. Adding container capabilities enables you to run new types of applications and to deploy new archive types in Enterprise Server.

The following topics are addressed here:

Creating a Container Implementation

To implement a container that extends or replaces a service in Enterprise Server, you must create a Java programming language class that includes the following characteristics:

Marking the Class with the @Service Annotation

Add a com.jvnet.hk2.annotations.Service annotation at the class definition level to identify your class as a service implementation.

@Service
public class MyContainer implements Container {
...
}

To avoid potential name collisions with other containers, use the fully qualified class name of your container class in the @Service annotation's name element:

package com.example.containers;
...

@Service(name="com.example.containers.MyContainer")
public class MyContainer implements Container {
...
}

Implementing the Container Interface

The org.glassfish.api.container.Container interface is the contract that defines a container implementation. Classes that implement Container can extend or replace the functionality in Enterprise Server by allowing applications to be deployed and run within the Enterprise Server runtime.

The Container interface consists of two methods, getDeployer and getName. The getDeployer method returns an implementation class of the org.glassfish.api.deployment.Deployer interface capable of managing applications that run within this container. The getName method returns a human-readable name for the container, and is typically used to display messages belonging to the container.

The Deployer interface defines the contract for managing a particular application that runs in the container. It consists of the following methods:

getMetaData

Retrieves the metadata used by the Deployer instance, and returns an org.glassfish.api.deployment.MetaData object.

loadMetaData

Loads the metadata associated with an application.

prepare

Prepares the application to run in Enterprise Server.

load

Loads a previously prepared application to the container.

unload

Unloads or stops a previously loaded application.

clean

Removes any artifacts generated by an application during the prepare phase.

The DeploymentContext is the usual context object passed around deployer instances during deployment.


Example 6–1 Example Implementation of Container

This example shows a Java programming language class that implements the Container interface and is capable of extending the functionality of Enterprise Server.

package com.example.containers;
contains
@Service(name="com.example.containers.MyContainer")
public class MyContainer implements Container {
	public String getName() {
		return "MyContainer";
	}

	public Class<? extends org.glassfish.api.deployment.Deployer> getDeployer() {
		return MyDeployer.class;
	}
}


Example 6–2 Example Implementation of Deployer

package com.example.containers;

@Service
public class MyDeployer {

	public MetaData getMetaData() {
		return new MetaData(...);
	}

	public <V> v loadMetaData(Class<V> type, DeploymentContext dc) {
		...
	}

	public boolean prepare(DeploymentContext dc) {
		// performs any actions needed to allow the application to run, 
		// such as generating artifacts
		...
	}

	public MyApplication load(MyContainer container, DeploymentContext dc) {
		// creates a new instance of an application
		MyApplication myApp = new MyApplication (...);
		...
		// returns the application instance
		return myApp;
	}

	public void unload(MyApplication myApp, DeploymentContext dc) {
		// stops and removes the application 
		...
	}

	public void clean (DeploymentContext dc) {
		// cleans up any artifacts generated during prepare()
		...
	}
}

Adding an Archive Type

An archive type is an abstraction of the archive file format. An archive type can be implemented as a plain JAR file, as a directory layout, or a custom type. By default, Enterprise Server recognizes JAR based and directory based archive types. A new container might require a new archive type.

There are two sub-interfaces of the org.glassfish.api.deployment.archive.Archive interface, org.glassfish.api.deployment.archive.ReadableArchive and org.glassfish.api.deployment.archive.WritableArchive. Typically developers of new archive types will provide separate implementations of ReadableArchive and WritableArchive, or a single implementation that implements both ReadableArchive and WritableArchive.

Implementations of the ReadableArchive interface provide read access to an archive type. ReadableArchive defines the following methods:

getEntry(String name)

Returns a java.io.InputStream for the specified entry name, or null if the entry doesn't exist.

exists(String name)

Returns a boolean value indicating whether the specified entry name exists.

getEntrySize(String name)

Returns the size of the specified entry as a long value.

open(URI uri)

Returns an archive for the given java.net.URI.

getSubArchive(String name)

Returns an instance of ReadableArchive for the specified sub-archive contained within the parent archive, or null if no such archive exists.

exists()

Returns a boolean value indicating whether this archive exists.

delete()

Deletes the archive, and returns a boolean value indicating whether the archive has been successfully deleted.

renameTo(String name)

Renames the archive to the specified name, and returns a boolean value indicating whether the archive has been successfully renamed.

Implementations of the WritableArchive interface provide write access to the archive type. WritableArchive defines the following methods:

create(URI uri)

Creates a new archive with the given path, specified as a java.net.URI.

closeEntry(WritableArchive subArchive)

Closes the specified sub-archive contained within the parent archive.

closeEntry()

Closes the current entry.

createSubArchive(String name)

Creates a new sub-archive in the parent archive with the specified name, and returns it as a WritableArchive instance.

putNextEntry(String name)

Creates a new entry in the archive with the specified name, and returns it as a java.io.OutputStream.

Implementing the ArchiveHandler Interface

An archive handler is responsible for handling the particular layout of an archive. Java EE defines a set of archives (WAR, JAR, and RAR, for example), and each of these archives has an ArchiveHandler instance associated with the archive type.

Each layout should have one handler associated with it. There is no extension point support at this level; the archive handler's responsibility is to give access to the classes and resources packaged in the archive, and it should not contain any container-specific code. The java.lang.ClassLoader returned by the handler is used by all the containers in which the application will be deployed.

ArchiveHandler defines the following methods:

getArchiveType()

Returns the name of the archive type as a String. Typically, this is the archive extension, such as jar or war.

getDefaultApplicationName(ReadableArchive archive)

Returns the default name of the specified archive as a String. Typically this default name is the name part of the URI location of the archive.

handles(ReadableArchive archive)

Returns a boolean value indicating whether this implementation of ArchiveHandler can work with the specified archive.

getClassLoader(DeploymentContext dc)

Returns a java.lang.ClassLoader capable of loading all classes from the archive passed in by the DeploymentContext instance. Typically the ClassLoader will load classes in the scratch directory area, returned by DeploymentContext.getScratchDir(), as stubs and other artifacts are generated in the scratch directory.

expand(ReadableArchive source, WritableArchive target)

Prepares the ReadableArchivesource archive for loading into the container in a format the container accepts. Such preparation could be to expand a compressed archive, or possibly nothing at all if the source archive format is already in a state that the container can handle. This method returns the archive as an instance of WritableArchive.

Creating Connector Modules

Connector modules are small add-on modules that consist of application “sniffers” that associate application types with containers that can run the application type. Enterprise Server connector modules are separate from the associated add-on module that delivers the container implementation to allow Enterprise Server to dynamically install and configure containers on demand.

When a deployment request is received by the Enterprise Server runtime:

  1. The current Sniffer implementations are used to determine the application type.

  2. Once an application type is found, the runtime looks for a running container associated with that application type. If no running container is found, the runtime attempts to install and configure the container associated with the application type as defined by the Sniffer implementation.

  3. The Deployer interface is used to prepare and load the implementation.

Associating File Types with Containers Using the Sniffer Interface

Containers do not necessarily need to be installed on the local machine for Enterprise Server to recognize the container's application type. Enterprise Server uses a “sniffer” concept to study the artifacts in a deployment request and to choose the associated container that handles the application type that the user is trying to deploy. To create this association, create a Java programming language class that implements the org.glassfish.api.container.Sniffer interface. This implementation can be as simple as looking for a specific file in the application's archive (such as the presence of WEB-INF/web.xml), or as complicated as running an annotation scanner to determine an XML-less archive (such as enterprise bean annotations in a JAR file). A Sniffer implementation must be as small as possible and must not load any of the container's runtime classes.

A simple version of a Sniffer implementation uses the handles method to check the existence of a file in the archive that denotes the application type (as WEB-INF/web.xml denotes a web application). Once a Sniffer implementation has detected that it can handle the deployment request artifact, Enterprise Server calls the setUp method. The setUp method is responsible for setting up the container, which can involve one or more of the following actions:

The setUp method returns an array of the com.sun.enterprise.module.Module objects required by the container.

The Sniffer interface defines the following methods:

handles(ReadableArchive source, ClassLoader loader)

Returns a boolean value indicating whether this Sniffer implementation can handle the specified archive.

getURLPatterns()

Returns a String array containing all URL patterns to apply against the request URL. If a pattern matches, the service method of the associated container is invoked.

getAnnotationTypes()

Returns a list of annotation types recognized by this Sniffer implementation. If an application archive contains one of the returned annotation types, the deployment process invokes the container's deployers as if the handles method had returned true.

getModuleType()

Returns the module type associated with this Sniffer implementation as a String.

setup(String containerHome, Logger logger)

Sets up the container libraries so that any dependent bundles from the connector JAR file will be made available to the HK2 runtime. The setup method returns an array of com.sun.enterprise.module.Module classes, which are definitions of container implementations. Enterprise Server can then load these modules so that it can create an instance of the container's Deployer or Container implementations when it needs to. The module is locked as long as at least one module is loaded in the associated container.

teardown()

Removes a container and all associated modules in the HK2 modules subsystem.

getContainerNames()

Returns a String array containing the Container implementations that this Sniffer implementation enables.

isUserVisible()

Returns a boolean value indicating whether this Sniffer implementation should be visible to end-users.

getDeploymentConfigurations(final ReadableArchive source)

Returns a Map<String, String> of deployment configuration names to configurations from this Sniffer implementation for the specified application (the archive source). The names are created by Enterprise Server; the configurations are the names of the files that contain configuration information (for example, WEB-INF/web.xml and possibly WEB-INF/sun-web.xml for a web application). If the getDeploymentConfigurations method encounters errors while searching or reading the specified archive source, it throws a java.io.IOException.

Making Sniffer Implementations Available to the Enterprise Server

Package Sniffer implementation code into modules and install the modules in the as-install/modules directory. Enterprise Server will automatically discover these modules. If an administrator installs connector modules that containSniffer implementations while Enterprise Server is running, Enterprise Serverwill pick them up at the next deployment request.