Recently, SpringSource released the first version of their DM server. The SpringSource DM Server provides the ability to build enterprise web applications. In the basis, S2DM is a fine mixture of Equinox and Tomcat.
Building OSGi-based web applications was already possible, but it is tedious and error prone work. The typical hello-world example was easy to get going, but as soon as Hibernate or any other framework that helps in larger applications show up, so do your good old class loading problems. For classes to be visible in OSGi, a bundle must declare an Import-Package entry in the Manifest file. Chances are small that Hibernate (even if it were packaged as an OSGi bundle) has these entries for your persistent classes. This is where S2DM server comes in. It allows the more-than-hello-world web applications to be deployed in an environment where you can benefit from the best of OSGi, without the class loading misery. To do this, they have included some extra Manifest entries that are translated to OSGi-compliant directives at load time.
Comparable to the WAR, or better, EAR file, S2DM server supports the PAR file. A PAR file is much like a Jar, with some special headers in the Manifest file, containing all your bundle jars. Some of these jars may contain web bundles, while other typically contain domain classes or the service layer implementation. Contrary to EAR files, a PAR should only contain your own code. It is best practice to deploy frameworks and third party libraries separately. I’ll explain why later on.
With enterprise applications come enterprise development processes, using continuous integration, build servers and maven. In this post, I’ll lay out what you need to get maven to build S2DM server compliant bundles, and better, PAR files.
OSGi manifest headers
But before we move straight to maven, let’s have a look at what a Spring DM bundle looks like. Basically, a Spring DM bundle is an ordinary OSGi bundle. It has the same requirements for the MANIFEST.MF file, of which the major headers are listed below:
Bundle-SymbolicNameis the only mandatory manifest header. It represents the technical name of your bundle. This value, in combination with the Bundle-Version, if present, must be unique.
Bundle-Versionis not mandatory, but defaults to 0.0.0, which isn’t a meaningful version. An OSGi compatible version number has the format: <major>.<minor>.<micro>-<classifier>.
Import-Packageheader allows you to specify a list of packages that your bundle requires to operate. It is also possible to specify version ranges and that some packages are optional.
DynamicImport-Packagecan be used to dynamically import packages at the moment they are required. Use a wildcard (*) to set package patterns to limit the dynamic import to.
The last entry I’ll mention here is
Export-Package. It specifies the packages of the current bundle that form its API. Classes in these packages are available for other bundles to use.
For more details about OSGi manifest headers, see the OSGi-core specification, section 3.2.
The OSGi specification requires you to import a package (which has to be exported by another bundle) for a class loader to find it. This lead to problems when using frameworks such as Hibernate, which has to instantiate classes from your application. There is no way a Hibernate OSGi bundle can know beforehand which classes it will instantiate. Another problem is that the number of packages you will have to import is probably very large. Frameworks sometimes offer large amounts of classes that have been grouped into numerous packages. To mention each of those packages in your
Import-Package manifest header is a lot of work. You could use the
DynamicImport-Package to import all packages you require, but that will create as many problems as it solves. OSGi is designed to only resolve bundles when all requirements have been met. By dynamically importing packages, the OSGi framework has no way to know whether requirements are met, and will resolve your bundle. During runtime, you could get class loader exceptions.
Spring DM specific manifest headers
To counter both the large number of imports and the class loading problems, Spring DM introduced some additional manifest headers. Not that these are not (yet) official OSGi compatible headers and are only evaluated by Spring DM compatible OSGi environments, such as Spring DM Server, for which there is a community and an enterprise version.
The most important additional Spring DM manifest headers for bundles are:
Import-Librarycan be used to import a library. A library is a specification of a set of bundles that should be imported as a whole. Examples of libraries are Hibernate and the Spring Framework.
Import-Bundleis used to import all the packages from a specific bundle, such as Spring-Core. Typically, this header is used in library specifications, but it may also be used in bundles manifests.
Module-Typespecifies the type of this bundle. At the time of writing, only “Web” is supported. When adding this header to the manifest, you indicate that this bundle is to be treated as a Web Module.
Web-context pathspecifies the URL in which a Web Module must be deployed. It default to the module’s file name minus the extension.
Web-FilterMappingsis used to specify the filter names (that reference spring bean names) and their URL mappings
Web-DispatcherServletUrlPatternsindicates the URLs that have to be mapped to the DispatcherServlet. In Spring DM, a dispatcher servlet is automatically initialized with the context files in
Creating an OSGi bundle with maven
Now that we know which headers have to be generated, lets move on to maven to find how we can generate a manifest file for them. Obviously, you could generate one yourself. This is, however, tedious and error prone. It is best to conform yourself to some design standards, such as using
.impl packages to store classes that are not part of your public API. When you do so, the maven-bundle-plugin.
The maven-bundle-plugin is used to generate an OSGi bundle from your module. In order to make it work, you have to make a few modifications to an otherwise normal pom.xmlfile:
The <packaging> element value must be set to bundle:
Configure the org.apache.felix:maven-bundle-plugin
<build> <plugins> <plugin> <groupId>org.apache.felix</groupId> <artifactId>maven-bundle-plugin</artifactId> <extensions>true</extensions> <configuration> <instructions> ... </instructions> </configuration> </plugin> </plugins> </build>
This configuration will generate OSGi compliant manifest files. The
Bundle-SymbolicName will be formed from your modules groupId and artifactId and the
Bundle-Version will be set to you module’s version. By default, all packages that are mentioned in your source code’s import statements will be imported by your bundle, even if these packages are also included in your bundle (see this post on the OSGi Alliance website). Furthermore, all packages available in your bundle will be exported, even the
Bundle-Name: Spring DM Persistence
Created-By: Apache Maven Bundle Plugin
I am not going into more detail about how to configure the bundle plugin to keep implementation packages private. See the felix documentation for more information about that.
Adding Spring DM manifest headers
Well, that actually quite easy. All you have to do is specify the name of the header as a sub element in the
<instructions> element and the value of the header as the value of that element, for example:
The problem is to find out which bundles or libraries there are to import, and which versions they have. That’s why Spring started the Spring DM bundle repository. You can use it to find libraries and OSGi bundles for most commonly used frameworks. For each of thus libraries or bundles, it will tell you which maven dependency to use (for compilation) and which manifest entry has to be added to your bundle. Make sure that your maven dependencies and manifest entries are updated, otherwise you could run into problems at runtime.
For maven to find the artifacts in the bundle repository, you have to configure some repositories. You can find them here.
If your bundle is a Web Bundle, add the required
<Module-Type>Web</Module-Type> element to the instructions, as well as any other required Web-* headers.
That’s actually it. That’s all it takes to generate Spring DM specific manifest entries.
Creating a PAR deployment unit
The PAR file is the deployment unit for Spring DM applications. It is a Jar file with special manifest headers that contains all application specific bundles. You should never include framework bundles in the par file. In conventional environments, class loaders would prevent you from having different versions of the same class on your class path. Therefore, you had to add frameworks to the WAR file, to prevent version conflicts with other applications. In OSGi, there is no problem with having the same class multiple times. You just specify a version restriction in your import headers and OSGi takes care of the resolution process. To keep PAR files small, you can deploy required libraries and bundles in the /repository/libraries/usr and /repository/bundles/usr folders respectively. Any application can reuse any existing framework bundles.
The PAR file has another advantage. Spring DM Server prevents bundles from outside the PAR file from accessing bundles inside the PAR file. This way, you can deploy applications without running the risk of classes and services intermingling.
Now, let’s get back to maven to create ourselves a par file. First, create a new module in your maven project. This module’s pom file should have
<packaging> set to
par. Next, add dependencies to each module you want to be included in the par file. The par plugin does not include transitive dependencies, since framework bundles should not be included. Finally, add the maven-par-plugin to your plugin configuration.
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-par-plugin</artifactId> <version>0.2.0</version> <extensions>true</extensions> <configuration> <fqn>true</fqn> <applicationSymbolicName>gridshore.samples.springdm</applicationSymbolicName> <applicationVersion>1.0.0</applicationVersion> <applicationDescription>Gridshore Spring DM sample</applicationDescription> <applicationName>Gridshore Spring DM sample</applicationName> </configuration> <executions> <execution> <goals> <goal>par</goal> </goals> <phase>package</phase> </execution> </executions> </plugin> </plugins> </build>
mvn install and see your PAR being created for you. Since your PAR does not contain framework bundles, you have to install them separately. To make sure that all dependencies are available, you can use the maven-assembly-plugin to create a zip file with the PAR file as well as all the framework bundles you require.