This document is basically a brain dump of how V3 is organized today with some insights on how it could be re-architected along the V3 engineering effort.
Change History
1 ModulesGlassFish v3 is built around a Module subsystem specified by the Hundred Kilobyte Kernel (HK2) project. Subsystem functionalities are declared through a Contract/Service implementation paradigm. HK2 Contracts identify and describe the building blocks or the extension points of an application. HK2 Services implement the Contracts. See HK2 Componentsfor more details. 1.1 What is a Module?A Module is an encapsulated definition of reusable code. It offers Services, declares a Modules's dependencies on other Services, has public APIs, and a private set of classes implementing these APIs. To summarize, a Module :
1.2 How to define a Module?Defining a Module is not always easy and will in most cases be an iterative process. Answering the following questions help to define a Module:
If you answer 'yes' to any of the above questions, the code should be encapsulated in a Module. The easiest starting point is to check if your code offers more than one service. For instance, the current connector container offers 2 services to GlassFish v2: the JDBC driver container and the Java EE resource adapter container. These services could be defined as two Modules so they can be used independently. From there, you also need to look into other services that your "code" offers to GlassFish. Don't create a Module just for the fun of it. One should create a Module only if it contains reusable code that can be used independently by other components (or just like above to make things more encapsulated/efficient). 1.3 Module definitionOnce you have determined a set of classes that you want to offer as a Module, you need to start thinking about its definition. The definition is a name and a version that will be used by other modules to identify their dependency on your module. The next aspect of modularization is to figure out the exact dependencies that your modules have. For instance, you may depend on the com.sun.enterprise:config-api. Note: the name of the module is constructed from a maven pom.xml using a group id and artifact id). What else ? It is quite important to understand those dependencies, not only to successfully compile your Module(s), but also to understand what runtime Services are necessary for your module to function properly. So, here are suggested steps on how to define code into a Module:
Once you have that information, even in an initial raw format, you can start thinking about refactoring the code or reduce the dependency trail and so on. 2 Modules Management |
Classloader | Classes Loaded |
---|---|
System Class Loader | JDK classes |
Application Class Loader | all jar files present in the |
MaskingClassLoader | Parent classloader for all V3 modules, initialized by the glassfish-{version}.jar, its main responsibility is to mask the jdk classes. This means newer implementations of JDK bundled versions can be used without mechanisms such as the endorsed directories. |
com.sun.enterprise:v3-core class loader |
Although programming with Modules can be challenging, GlassFish cannot run efficiently based on user's actions alone. For example, if all the Modules forming a GlassFish distribution referenced each other, the entire set of Modules would be loaded at startup. This erases all the potential advantages of modularization.
To isolate the engineers from programming with Modules directly, there is a concept of Services in glassfish. These services are usually Plain Old Java Objects (POJOs) implementing an interface. At runtime, glassfish relies on a Services implementation lookup to find the Services for a particular interface (called a Contract).
Let's take an example. The com.sun.enterprise:appserv-api Module contains all GlassFish V3 public APIs. It contains an interface called Startup. That interface defines code that must be run when GlassFish is started (somewhat equivalent to the Lifecycle implementations in V2). Any Modules in the GlassFish distribution can have one-to-many implementations of the Startup interfaces called startup services.
When glassfish is started, the Startup implementation code from the com.sun.enterprise:v3-core Module described earlier will do the following HK2 lookup :
Collection<Startup> startups = habitat.getAllByContract(Startup.class);
Even if v3-core Module does not directly import the Modules implementing the startup Services, the collection returned by the above code will contain those implementations. This is an implicit import of instances from one Module to another while the classloaders are not directly referencing each other.
Each Module containing one-to-many Startup implementations can also bring up other Modules through the dependency mechanisms. For instance the HttpService module which contains a Startup service can declare its dependency on the Grizzly Module. Once the Startup services have run successfully, this is how the classloader hierarchy will look like.
Figure 1
In the example above, please note the following :
v3-core does not imports HttpService or DeploymentService Modules
appserv-api module defines the Startup interface which is the contract for the service and is imported by both the v3-core and the modules containing a Startup implementation.
A new type of Service or Contract is just a plain java interface annotated with the @Contract interface.
@Contract public interface Startup { }
Remember that @Contract annotated classes must be accessible from the Modules implementing the Contract (Service providers) as well as the Modules looking up such implementations. In the example above, the Startup interface is defined in the appserv-api module and is imported by both the v3-core module which requests all Startup implementations and by the modules defining the implementations (HttpService module)
Adding a new Service is as easy as defining a new POJO that implements a Contract and is annotated with the @Service interface.
@Service public class RandomService implements Startup, PostConstruct { public void postConstruct() { logger.info("I am not that useful yet"); } }
The above example shows how to implement a Service by implementing the PostConstruct ejb interface. Service implementations can be located in any module packaged in the distribution.
Configuration of a Service can come from either the configuration file (domain.xml) or other component implementations. When a Service needs to be configured from the domain.xml, it should declare itself with the @Configured interface.
The combination of @Attribute and @Element to bind to xml attributes and elements can be used to denote which information should be fetched from the configuration file.
@Configured public class HttpService implements Serializable { @Element protected AccessLog accessLog; @Element(required=true) protected List<HttpListener> httpListener = new ArrayList(); ... }
Although it could be conceivable that an annotated @Configured class is also a Service implementation, this sort of converged @Configured plus @Service component is discouraged. Most @Configured objects need to be available and be configured without an associated runtime (so the admin-gui can configure the http-service in the example used above without necessarily starting the http service). It is therefore recommended to split the configuration from the behavior into two POJOs and implement the following pattern using @ConfiguredBy:
@Service @ConfiguredBy(HttpService.class) public void GrizzlyService implements Startup { @Inject private HttpService config; ... }
A subset of services have already been identified and added to the org.jvnet.glassfish:appserv-api Module which represents the public APIs of the GlassFish implementation. Below is a brief introduction of these services. See later sections for more details:
Service | Description |
---|---|
Startup | services which are run upon GlassFish startup. |
Sniffer | services responsible for identifying deployment artifacts and setting up associated containers. |
ArchiveHandler | services that recognize application bundle types (WAR, EAR...) |
Deployer | services responsible for deploying artifacts to a particular container |
AdminCommand | services which represent a CLI administrative command |
GlassFish v3 needs to support the convergence model for applications where users can ship application bits targeted to different containers all in the same package. This is a break from how we have traditionally considered application delivery in the Java EE world where each container had a dedicated bundle format. For instance, web containers expect war files, ejb containers expect jar files.
In v3, several types of applications can be co-packaged in the same bundle while still requiring some form of cooperation among the application parts (requiring a single class loader to load the application bits to be shared by all containers). There cannot be an assumption on the format of the bundle where a container can load the application bits from, therefore the containers do not control how the class loader for the applications bits are created, this is handled through another type of deployment service called ArchiveHandler.
There can be only one ArchiveHandler for a particular bundle type. The archive handler implementation will be responsible for creating an appropriate application class loader that can successfully load classes and resources from that bundle type. GlassFish V3 will create a parent classloader to be used by the ArchiveHandler when creating the application class loader. This parent class loader will allow the Glassfish runtime to dynamically add imports to the application (see below). Each Sniffer implementation has the ability to setup their associated containers, if the bundle appears to contain components they support. Once the sniffers have run successfully, all Deployer services currently found by HK2 will have a chance to deploy bits of the bundle to their associated containers.
Deployers will either claim part of the application bits or not. If a particular deployer instance successfully process a deployment request using the class loader created by the ArchiveHandler. the public APIs associated with that container (eg the Java EE APIs) will be added to the parent class loader (create above for that purpose).
Remember that supporting different bundle types is a desired feature. Therefore, power users can have the ability to create/register new ArchiveHandlers. Since ArchiveHandlers are responsible for creating the classloaders, there is no possibility to directly add the container's public API to these classloaders. This is because the ArchiveHandlers are simple java.lang.ClassLoader implementations. This is the reason behind the creation of this dynamic parent class loader.
However, there is a slight limitation to this scheme: the ClassLoader implementation returned by the ArchiveHandler should support the registration of java.lang.instrument.ClassFileTransformer instances. This supports bytecode enhancement dependent technologies like JPA. The EjbClassLoader and WebClassLoader that we ship with V3 both support such a registration.
In the diagram below, we have two containers: one application being deployed to both containers and one random library module used by one of the container. Note the following :
In the second diagram, you will see a simplified version of the first diagram showing the relationships between two applications and the containers into which they are successfully deployed. Note the following :
Figure 2
Figure 3
Implementing persistence in V3 involves several challenges due to the following:
The following patterns have been identified :
The Stranger pattern is not necessarily supported by the JPA or Java EE specs.
To resolve the above problems, a shared library Module will be introduced. This Module will load all jar files located in the glassfish/lib and glassfish/javadb/lib directories (remember that v3 modules and jars will be relocated somewhere else). This shared library will have access to the JDBC drivers as well as javadb runtime.
The solution described above is illustrated by Figure 4 below. The number in parentheses indicates which class loader relationship that will satisfy the requirement.
Applications can load JDBC drivers using the Class.forName() API. The parent class loader of the application class loader (named the application container class loader) will include the shared library class loader. It also will give access to the public APIs of an application (1)
In Java SE mode, applications use javax.persistence.PersistenceProvider.createEntityManagerFactory() API to create an entity manager. Applications use a persistenceUnitName parameter to identify the provider in the persistence.xml file. HK2 will make the META-INF/services resolution for the PersistenceProvider.java implementation and JPA will be loaded like any other HK2 service.
The JDBC driver is provided by some non-portable persistence.xml properties and is loaded directly by the persistence provider (2)
In Java EE, there is a need to add ClassFileTransformers to the application class loader. This is a bit tricky as we don't necessarily provide the class loader for the application bits (see Deployment above). JPA infrastructure will have a Deployer implementation, therefore the prepare phase should be used by the JPADeployer to pre-load classes that may require bytecode enhancements. During the prepare phase, the JPADeployer should also add some java.lang.intrument.ClassFileTransformers) instances to the DeploymentContext. The Deployment backend will then recreate a new class loader, register all installed ClassFileTransformers in this new class loader instances and will invoke the load phase with the new classloader.
Normal HK2 Service resolution should suffice to resolve the JPA provider based on the META-INF/services entries.
Naming Service will be available though the HK2 Service Resolution Mechanism, from which the connection pool can be looked up. After wiring up the connection pool with the JPA provider, the entity manager can be created by the deployment code and injected in the application code.
Normal class loading delegation should be enough to support such use cases.
Figure 4
Containers are entities that can run applications (e.e., web container, ejb, etc.). Since such containers can be installed or removed from a GlassFish V3 installation, there is no special code referencing different application containers like Switch.java in V2.
Containers have the ability to install themselves in an existing GlassFish installation with the following Services implementations :
Like everything in V3, we rely on HK2 components to identify administrative commands. So, in this case, the AdminCommand interface is annotated with @Contract. As a result, to add a new command to GlassFish V3 is to create a Service implementing the AdminCommand interface and the runtime will find it :
package com.foo.bar; @Service(name="super") public class MySuperCommand implements AdminCommand { ... }
Now this is nice but we can do better. Since admin commands are Services, we can use injection. So, if you want to have access to the domain configuration, you do the following:
@Inject Domain domain;
and the domain information will be injected in the instance field before the command execute() is invoked. That's nice, but why not extend this to the command parameters. Too many times, I have seen command implementation code just ensure that all mandatory parameters to the command are present, values are correct, and extraction from the String[] representation.
This is where the new annotation @Param becomes useful. It can annotate any instance field of the command implementation with the @Param annotation to make it a command parameter. So for instance adding the following:
@Param String name
adds a name parameter to the command. Of course, parameters can be optional
@Param(optional=true) String myoptionalparam
or the default parameter (but there can only be one of course)
@Param(default=true) String path
so you can now do :
asadmin deploy --path foo.war
or
asadmin deploy foo.war
Also, the @Param tag is automatically linked to the required LocalStrings.properties resource file of your classloader. So, when you add the
com.sun.foo.bar.mysupercommand=Some random super command com.sun.foo.bar.mysupercommand.path=path to the file the command applies to.
strings to your LocalStrings.properties, the system will find them and use it for any type of error checking or help :
$prompt>asadmin super FAILURE : super command requires the path parameter : path to the file the command applies to. $prompt>asadmin super --help Some random super command Parameters : path : path to the file the command applies to.
The above behaviours are completely handled by the system. The command will not be executed as long as all mandatory parameters are provided by the users. There is more to do, of course, like automatic help page and localized page creation. Also, there should be some maven 2 plugins developed to automate page creation and ensure that all strings are properly localized.
Although this information will most likely change and is not entirely implemented, this is an introduction to how we will organize the source code in V3.
The bootstrap directory is not going to be moved forward, but we will use a directory layout to separate and represent modules segments. Functionalities linked to packaging are moved to the distribution modules.
Source code is managed using Subversion for now. However, we plan to move to mercurial at some point. The Subversion workspace is organized to follow the module infrastructure that we will be using in V3.
To checkout the entire glassfish workspace that are not yet used by the V3 runtime (and therefore were not moved to a corresponding module directory), you can do the following :
svn checkout https://svn.java.net/svn/glassfish-svn/trunk/v2
However, unless you plan to move code from the old modules to the new structure, you do not need to checkout the entire workspace, in general, you can just check out the code currently used in V3 by doing :
svn checkout https://svn.java.net/svn/glassfish-svn/trunk/v3
To subscribe to the svn notifications for the changes, send an e-mail to commits-subscribe@glassfish-svn.java.net
Modules are organized following the maven best practice pattern.
Although each module can use a dedicated scm repository, it is important to follow that structure for consistency and tool support.
See V3 module structure for a complete description. However, for the most simple cases, it should be the following files/directories:
File | Description |
---|---|
pom.xml | maven build file |
src/main/java | module's sources |
src/main/resources | module's resources like localized strings, xml... |
src/tests/... | module's unit tests |
In V2, the workspace was organized with sub-modules, each sub-module could be independently checked out or if you wanted to checkout the entire workspace, you would do so from the bootstrap module. For instance this is a simplified view of the V2 CVS workspace
Module | Description |
---|---|
bootstrap | contains all project wide settings, main build scripts etc. |
appserv-commons | contains code shared by the different GlassFish sub components (Web Container, Deployment, EJB container...) |
admin-core/config-api | contains the domain.xml binding classes generated from schema2beans |
ejb-api | EJB 3.0 javax.ejb API sources |
In the current V3 workspace, the sources which are currently used by the V3 runtime have been moved to a new location that reflect the module in which the .class files are packaged. The workspace is also organized in a tree like structure (as opposed to mainly flat like in V2 with all modules at the top-level).
Modules will be grouped into functional areas and moved under specific directories. A user that wishes to checkout the entire V3 workspace should do so by issuing the command described in IV.1. However, developers that wish to only work in a specific area should checkout the set of Modules that are identified in that area.
The following areas have been already identified. This will evolve over time:
Directory | Description |
---|---|
v3/web | web related technologies (jsp, jsf...) |
v3/ejb | ejb related technologies |
v3/core | v3 core infrastructures |
v3/admin | administration related modules |
v3/extras | adminGUI and other tools |
v3/api | Java EE APIS |
v3/build | Build related tools like maven plugins |
For instance, under glassfish_v3/web, you will find subdirectories for each module that are part of our web profile. Now, let's say you need to move some code from appserv-core to the new V3 module structure (in say web/appserv-webtier module), you could only checkout the appropriate modules :
svn checkout https://svn.java.net/svn/glassfish-svn/trunk/v3/appserv-core svn checkout https://svn.java.net/svn/glassfish-svn/trunk/v3/web/appserv-webtier
Once we will complete the switch to Mercurial, such tree based organization does not support checking out part of the tree. So, we will most likely move each sub-directory under glassfish_v3 to a new Hg repository.
Modules are grouped using a groupId as defined in the module's pom.xml. All GlassFish module should follow this consistent naming and directory structure
Main groupId | Function Area | Module name | Resulting maven Id | source location |
---|---|---|---|---|
org.glassfish | web | appserv-webtier | org.glassfish.web:appserv-webtier | v3/web/appserv-webtier |
org.glassfish | admin | config-api | org.glassfish.admin:config-api | v3/admin/config-api |
org.glassfish | core | kernel | org.glassfish.core:kernel | v3/core/kernel |
Basic understanding of maven and maven repositories is necessary to build V3. Also, at each level, users are able to do a mvn command to build a Module, create a functional set of Modules, or the entire V3 workspace.
Command | Description |
---|---|
mvn clean | cleans the binary outputs (.class, and .jar files) from the module target directory. Does not remove the Module artifact from the local maven repository. |
mvn install | builds and install the binary artifact in the local maven repository. |
mvn javadoc:javadoc | creates javadoc |
So for instance if I run mvn install from the v3 directory, I will build the entire GlassFish V3, if I build under v3/web I will build all the web related modules, if I build v3/web/appserv-webtier, I only build the appserv-webtier module.
Artifacts cannot be published directly to the global HTTP maven repositories because a certain number of integration tests need to happen. When a developer feels confident that his code is robust and has made enough integration tests on his local machine (by building a distribution and running tests), he will check in the sources. Hudson, the continuous building system, will monitor checkins and will trigger a build/test/publish cycle each time a developer checks in a change set.
One of the big activity of V3 will be to move code from their current module to a new directory that reflect the runtime module in which the .class will be packaged. How can you move files without loosing history :
svn checkout https://svn.java.net/svn/glassfish-svn/trunk/v2/appserv-core svn checkout https://svn.java.net/svn/glassfish-svn/trunk/v3/web/appserv-webtier
assuming you are located in your home directory, you now have 2 subdirectories, appserv-core and v3
cd ~/v3/web/appserv-webtier/src/main/java/com/sun/entreprise svn mv ~/v2/appserv-core/src/java/com/sun/enterprise/FooBar.java .
and commit to have the old file deleted from appserv-core and the new addition to v3/web/appserv-webtier without loosing the commit history. You can also move entire directories following the same pattern as above. Do not do svn rm and svn add as you would loose the SCM history associated with the file you are trying to move.
Distributions are created by assembling jars and other artifacts. There is a flexible distribution description mechanism based on maven pom file for binary dependencies and ant scripts for the other types of artifacts. For more details about this process, refer to V3DistributionAssembly
HK2, the java.net project for our module subsystem and lightweight component model, has a separate java.net project located at hk2.java.net.
HK2 sources are stored in a Subversion repository, so checking out sources is usually done with
svn checkout https://hk2.java.net/svn/hk2/trunk
TBD