Oracle GlassFish Server Add-On Component Development Guide Release 3.1.2 Part Number E24927-01 |
|
|
View PDF |
Applications run on GlassFish Server in containers. GlassFish Server enables you to create containers that extend or replace the existing containers of GlassFish Server. Adding container capabilities enables you to run new types of applications and to deploy new archive types in GlassFish Server.
The following topics are addressed here:
Container
ImplementationTo implement a container that extends or replaces a service in GlassFish Server, you must create a Java programming language class that includes the following characteristics:
It is annotated with the org.jvnet.hk2.annotations.Service
annotation.
It implements the org.glassfish.api.container.Container
interface.
@Service
AnnotationAdd 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 { ... }
Container
InterfaceThe 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 GlassFish Server by allowing applications to be deployed and run within the GlassFish 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 GlassFish 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 7-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 GlassFish 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 7-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() ... } }
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, GlassFish 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
.
ArchiveHandler
InterfaceAn 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 ReadableArchive
source 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
.
Connector modules are small add-on modules that consist of application "sniffers" that associate application types with containers that can run the application type. GlassFish Server connector modules are separate from the associated add-on module that delivers the container implementation to allow GlassFish Server to dynamically install and configure containers on demand.
When a deployment request is received by the GlassFish Server runtime:
The current Sniffer
implementations are used to determine the application type.
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.
The Deployer
interface is used to prepare and load the implementation.
Sniffer
InterfaceContainers do not necessarily need to be installed on the local machine for GlassFish Server to recognize the container's application type. GlassFish 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, GlassFish 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:
Downloading the container's runtime (the first time that a container is used)
Installing the container's runtime (the first time that a container is used)
Setting up one or more repositories to access the runtime's classes (these are implementations of the HK2 com.sun.enterprise.module.Repository
interface, such as the com.sun.enterprise.module.impl.DirectoryBasedRepository
class)
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. GlassFish 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 GlassFish 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
.
Sniffer
Implementations Available to the GlassFish ServerPackage Sniffer
implementation code into modules and install the modules in the as-install/modules
directory. GlassFish Server will automatically discover these modules. If an administrator installs connector modules that containSniffer
implementations while GlassFish Server is running, GlassFish Server will pick them up at the next deployment request.
This example shows a custom container and a web client of the container. The example is comprised of the following code:
Code for the container, which is shown in Container Component Code
Code for a web client of the container, which is shown in Web Client Code
Code that defines the configuration data for the container component is shown in Examples of Adding Configuration Data for a Component.
Code for an asadmin
subcommand that updates the configuration data in this example is shown in Example 4-7.
The container component code is comprised of the classes and interfaces that are listed in the following table. The table also provides a cross-reference to the listing of each class or interface.
Class or Interface | Listing |
---|---|
|
|
|
|
|
|
|
|
|
Example 7-3 Annotation to Denote a Container's Component
This example shows the code for defining a component of the Greeter
container.
package org.glassfish.examples.extension.greeter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** * Simple annotation to denote Greeter's component */ @Retention(java.lang.annotation.RetentionPolicy.RUNTIME) public @interface Greeter { /** * Name to uniquely identify different greeters * * @return a good greeter name */ public String name(); }
Example 7-4 Application Container Class
This example shows the Java language class GreeterAppContainer
, which implements the ApplicationContainer
interface.
package org.glassfish.examples.extension.greeter; import org.glassfish.api.deployment.ApplicationContainer; import org.glassfish.api.deployment.ApplicationContext; import org.glassfish.api.deployment.archive.ReadableArchive; import java.util.List; import java.util.ArrayList; public class GreeterAppContainer implements ApplicationContainer { final GreeterContainer ctr; final List<Class> componentClasses = new ArrayList<Class>(); public GreeterAppContainer(GreeterContainer ctr) { this.ctr = ctr; } void addComponent(Class componentClass) { componentClasses.add(componentClass); } public Object getDescriptor() { return null; } public boolean start(ApplicationContext startupContext) throws Exception { for (Class componentClass : componentClasses) { try { Object component = componentClass.newInstance(); Greeter greeter = (Greeter) componentClass.getAnnotation(Greeter.class); ctr.habitat.addComponent(greeter.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; } public ClassLoader getClassLoader() { return null; } }
This example shows the Java language class GreeterContainer
, which implements the Container
interface.
package org.glassfish.examples.extension.greeter; import org.glassfish.api.container.Container; import org.glassfish.api.deployment.Deployer; import org.jvnet.hk2.annotations.Service; import org.jvnet.hk2.annotations.Inject; import org.jvnet.hk2.component.Habitat; @Service(name="org.glassfish.examples.extension.GreeterContainer") public class GreeterContainer implements Container { @Inject Habitat habitat; public Class<? extends Deployer> getDeployer() { return GreeterDeployer.class; } public String getName() { return "greeter"; } }
This example shows the Java language class GreeterDeployer
, which implements the Deployer
interface.
package org.glassfish.examples.extension.greeter; import org.glassfish.api.deployment.Deployer; import org.glassfish.api.deployment.MetaData; import org.glassfish.api.deployment.DeploymentContext; import org.glassfish.api.deployment.ApplicationContainer; import org.glassfish.api.deployment.archive.ReadableArchive; import org.glassfish.api.container.Container; import org.jvnet.hk2.annotations.Service; import java.util.Enumeration; @Service public class GreeterDeployer implements Deployer<GreeterContainer, GreeterAppContainer> { public MetaData getMetaData() { return null; } public <V> V loadMetaData(Class<V> type, DeploymentContext context) { return null; } public boolean prepare(DeploymentContext context) { return false; } public GreeterAppContainer load( GreeterContainer container, DeploymentContext context) { GreeterAppContainer appCtr = new GreeterAppContainer(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(Greeter.class)) { appCtr.addComponent(componentClass); } } catch(Exception e) { throw new RuntimeException(e); } } } return appCtr; } public void unload(GreeterAppContainer 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("/", "."); } }
This example shows the Java language class GreeterSniffer
, which implements the Sniffer
interface.
package org.glassfish.examples.extension.greeter; import org.glassfish.api.container.Sniffer; import org.glassfish.api.deployment.archive.ReadableArchive; import org.glassfish.api.admin.config.ConfigParser; import org.glassfish.examples.extension.greeter.config.GreeterContainerConfig; import org.jvnet.hk2.annotations.Service; import org.jvnet.hk2.annotations.Inject; import org.jvnet.hk2.component.Habitat; import com.sun.enterprise.module.Module; import java.util.logging.Logger; import java.util.Map; import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.Array; import java.net.URL; /** * @author Jerome Dochez */ @Service(name="greeter") public class GreeterSniffer implements Sniffer { @Inject(optional=true) GreeterContainerConfig config=null; @Inject ConfigParser configParser; @Inject Habitat habitat; public boolean handles(ReadableArchive source, ClassLoader loader) { return false; } public String[] getURLPatterns() { return new String[0]; } public Class<? extends Annotation>[] getAnnotationTypes() { Class<? extends Annotation>[] a = (Class<? extends Annotation>[]) Array.newInstance(Class.class, 1); a[0] = Greeter.class; return a; } public String getModuleType() { return "greeter"; } 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, GreeterContainerConfig.class); } } return null; } public void tearDown() { } public String[] getContainersNames() { String[] c = { GreeterContainer.class.getName() }; return c; } public boolean isUserVisible() { return true; } public Map<String, String> getDeploymentConfigurations (ReadableArchive source) throws IOException { return null; } public String[] getIncompatibleSnifferTypes() { return new String[0]; } }
The web client code is comprised of the classes and resources that are listed in the following table. The table also provides a cross-reference to the listing of each class or resource.
Class or Resource | Listing |
---|---|
|
|
|
|
Deployment descriptor |
Example 7-8 Container Client Class
import components.SimpleGreeter; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.annotation.WebServlet; import javax.servlet.*; import javax.servlet.http.*; import javax.annotation.Resource; @WebServlet(urlPatterns={"/hello"}) public class HelloWorld extends HttpServlet { @Resource(name="Simple") SimpleGreeter greeter; public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException { PrintWriter pw = res.getWriter(); try { pw.println("Injected service is " + greeter); if (greeter!=null) { pw.println("SimpleService says " + greeter.saySomething()); pw.println("<br>"); } } catch(Exception e) { e.printStackTrace(); } } }
Example 7-9 Component for Container Client
package components; import org.glassfish.examples.extension.greeter.Greeter; @Greeter(name="simple") public class SimpleGreeter { public String saySomething() { return "Bonjour"; } }
Example 7-10 Deployment Descriptor for Container Client
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/ Line break added for readability xml/ns/javaee/web-app_2_5.xsd"> </web-app>