blob: 3dfcff2bf628c0eb8d2c98ab05c5b9faab3835be [file] [log] [blame]
type=page
status=published
title=Adding Container Capabilities
next=session-persistence-modules.html
prev=adding-configuration-data.html
~~~~~~
= Adding Container Capabilities
[[GSACG00007]][[ghmon]]
[[adding-container-capabilities]]
== Adding Container Capabilities
Applications run on {productName} in containers. {productName}
enables you to create containers that extend or replace the existing
containers of {productName}. Adding container capabilities enables
you to run new types of applications and to deploy new archive types in
{productName}.
The following topics are addressed here:
* link:#ghpjl[Creating a `Container` Implementation]
* link:#ghozu[Adding an Archive Type]
* link:#ghphp[Creating Connector Modules]
* link:#gkane[Example of Adding Container Capabilities]
[[ghpjl]][[GSACG00132]][[creating-a-container-implementation]]
=== Creating a `Container` Implementation
To implement a container that extends or replaces a service in {productName}, 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.
You should also run the HK2 Inhabitants Generator utility on your class
files, which adds classes marked with the `@Service` annotation to the
`META-INF/hk2-locator/default` file in your JAR file. For more
information about the HK2 Inhabitants Generator, see the
https://hk2.java.net/inhabitant-generator.html[HK2 Inhabitants Generator
page].
[[ghogv]][[GSACG00234]][[marking-the-class-with-the-service-annotation]]
==== 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.
[source,java]
----
@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:
[source,java]
----
package com.example.containers;
...
@Service @jakarta.inject.Named("com.example.containers.MyContainer")
public class MyContainer implements Container {
...
}
----
[[ghohg]][[GSACG00235]][[implementing-the-container-interface]]
==== 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 {productName}
by allowing applications to be deployed and run within the {productName} 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 {productName}.
`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.
[[GSACG00064]][[ghojg]]
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
{productName}.
[source,java]
----
package com.example.containers;
@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;
}
}
----
[[GSACG00065]][[ghoiv]]
Example 7-2 Example Implementation of `Deployer`
[source,java]
----
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()
...
}
}
----
[[ghozu]][[GSACG00133]][[adding-an-archive-type]]
=== 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, {productName} 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`.
[[ghoyp]][[GSACG00236]][[implementing-the-archivehandler-interface]]
==== Implementing the `ArchiveHandler` Interface
An archive handler is responsible for handling the particular layout of
an archive. Jakarta 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`++'s++ 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`.
[[ghphp]][[GSACG00134]][[creating-connector-modules]]
=== 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. {productName} connector modules are separate
from the associated add-on module that delivers the container
implementation to allow {productName} to dynamically install and
configure containers on demand.
When a deployment request is received by the {productName} 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.
[[ghozd]][[GSACG00237]][[associating-file-types-with-containers-by-using-the-sniffer-interface]]
==== Associating File Types With Containers by Using the `Sniffer` Interface
Containers do not necessarily need to be installed on the local machine
for {productName} to recognize the container's application type.
{productName} 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, {productName} 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. {productName} 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 {productName}; 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`.
[[ghpbx]][[GSACG00172]][[making-sniffer-implementations-available-to-the-glassfish-server]]
===== Making `Sniffer` Implementations Available to the {productName}
Package `Sniffer` implementation code into modules and install the
modules in the as-install``/modules`` directory. {productName} will
automatically discover these modules. If an administrator installs
connector modules that contain`Sniffer` implementations while {productName} is running, {productName} will pick them up at the next
deployment request.
[[gkane]][[GSACG00135]][[example-of-adding-container-capabilities]]
=== Example of Adding Container Capabilities
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 link:#gkbah[Container
Component Code]
* Code for a web client of the container, which is shown in
link:#gkbcq[Web Client Code]
Code that defines the configuration data for the container component is
shown in link:adding-configuration-data.html#gkaal[Examples of Adding
Configuration Data for a Component].
Code for an `asadmin` subcommand that updates the configuration data in
this example is shown in link:extending-asadmin.html#gkbdf[Example 4-7].
[[gkbah]][[GSACG00238]][[container-component-code]]
==== Container Component Code
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.
[width="100%",cols="<50%,<50%",options="header",]
|===
|Class or Interface |Listing
|`Greeter` |link:#gkamr[Example 7-3]
|`GreeterContainer` |link:#gkand[Example 7-4]
|`GreeterContainer` |link:#gkamm[Example 7-5]
|`GreeterDeployer` |link:#gkalo[Example 7-6]
|`GreeterSniffer` |link:#gkaks[Example 7-7]
|===
[[GSACG00066]][[gkamr]]
Example 7-3 Annotation to Denote a Container's Component
This example shows the code for defining a component of the `Greeter`
container.
[source,java]
----
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();
}
----
[[GSACG00067]][[gkand]]
Example 7-4 Application Container Class
This example shows the Java language class `GreeterAppContainer`, which
implements the `ApplicationContainer` interface.
[source,java]
----
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;
}
}
----
[[GSACG00068]][[gkamm]]
Example 7-5 Container Class
This example shows the Java language class `GreeterContainer`, which
implements the `Container` interface.
[source,java]
----
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";
}
}
----
[[GSACG00069]][[gkalo]]
Example 7-6 Deployer Class
This example shows the Java language class `GreeterDeployer`, which
implements the `Deployer` interface.
[source,java]
----
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("/", ".");
}
}
----
[[GSACG00070]][[gkaks]]
Example 7-7 Sniffer Class
This example shows the Java language class `GreeterSniffer`, which
implements the `Sniffer` interface.
[source,java]
----
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];
}
}
----
[[gkbcq]][[GSACG00239]][[web-client-code]]
==== Web Client Code
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.
[width="100%",cols="<50%,<50%",options="header",]
|===
|Class or Resource |Listing
|`HelloWorld` |link:#gkaki[Example 7-8]
+
|`SimpleGreeter` |link:#gkalf[Example 7-9]
+
|Deployment descriptor |link:#gkaly[Example 7-10]
+
|===
[[GSACG00071]][[gkaki]]
Example 7-8 Container Client Class
[source,java]
----
import components.SimpleGreeter;
import java.io.IOException;
import java.io.PrintWriter;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.*;
import jakarta.servlet.http.*;
import jakarta.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();
}
}
}
----
[[GSACG00072]][[gkalf]]
Example 7-9 Component for Container Client
[source,java]
----
package components;
import org.glassfish.examples.extension.greeter.Greeter;
@Greeter(name="simple")
public class SimpleGreeter {
public String saySomething() {
return "Bonjour";
}
}
----
[[GSACG00073]][[gkaly]]
Example 7-10 Deployment Descriptor for Container Client
[source,xml]
----
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.1"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=
"http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd">
</web-app>
----