Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Releasing for JSE, NetBeans Platform and OSGi in a Single Shot

DZone's Guide to

Releasing for JSE, NetBeans Platform and OSGi in a Single Shot

· Java Zone
Free Resource

Build vs Buy a Data Quality Solution: Which is Best for You? Gain insights on a hybrid approach. Download white paper now!

With the upcoming release of jrawio 1.6, I'm starting to release the artifacts in three versions:

  1. an artifact for inclusion in standard JSE projects, that is a jar that just needs to be put in the classpath
  2. an artifact to be used in the NetBeans Platform, that is a nbm that contains some information in the manifest that, among other things, describes which packages are exported
  3. an artifact to be used in an OSGi container, that is a jar with some information in the manifest similar to those for the NetBeans Platform; in this way, people working e.g. with Eclipse RCP can integrate jrawio in their projects.

Actually, artifacts #1 and #3 are the same file that can be used in both contexts. Probably in future I could be able to have this single file to serve also for case #2 (see the discussion at the end of this article), but today it's not the case.

In this post I'm going to describe how I managed to achieve those goals with a Maven project.

As as side note, I think this could be my first post about Maven. In the past I've frequently expressed my disappointment about Maven and its complexity. While I'm still keeping the same arguments, and in addition - having worked with it for some months now - I also found many traces of bad design and implementation, I've resorted to converting my projects from Ant to Maven for a number of reasons, including the fact that Maven provides plugins ready for delivering in the NetBeans Platform and OSGi environments. A more comprehensive discussion about Maven from my own perspective will come in future when I've finished the migration of all my projects.

First, let me quickly introduce the structure of the jrawio project from the software building point of view. It is composed of two plain JSE modules, named "codec" and "processor", with the latter depending on the former. While releases earlier than 1.6 distributed two artifacts, one per module, starting from 1.6 there a single artifact is released; nevertheless, I kept the sources in two separate modules because they are different enough to be considered two different components.

In the rest of the article I'm assuming that the reader has got basic knowledge of Maven, the NetBeans Platform and/or OSGi.

Ok, before going on one might ask: why are you doing this? Answer: because it seems to me that component frameworks are getting momentum. The NetBeans Platform makes it possible for me to build complex projects such as blueMarine; OSGi is getting more and more popular, and with Java 7 we will have components integrated in the JDK (in a way or another). So far, the common practice is that people release plain JSE libraries, and developers wanting to integrate them into a component framework do the needed work (for instance, with the NetBeans Platform, by creating a wrapper module). But why delegate this job to users? It's a silly way to waste time, as everybody will do more or less the same thing. It's better that the library provider takes charge of this step, also because the library provider can control in a better way what is put into the manifest metadata.

Generating the JAR for JSE and OSGi

First, let me thank Felix Meschberger who provided me with a patch for making the OSGi stuff work.

The creation of the artifact for cases #1 and #3 is managed by a specific module (it.tidalwave.imageio.raw) whose pom is the following one:

<?xml version="1.0" encoding="UTF-8"?>
<project ...>

<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>it.tidalwave.imageio</groupId>
<artifactId>jrawio</artifactId>
<version>1.6.0-SNAPSHOT</version>
</parent>

<artifactId>it.tidalwave.imageio.raw</artifactId>
<packaging>bundle</packaging>
<name>jrawio - JAR artifact</name>

<dependencies>
<dependency>
<groupId>it.tidalwave.imageio</groupId>
<artifactId>codec</artifactId>
<version>1.6.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>it.tidalwave.imageio</groupId>
<artifactId>processor</artifactId>
<version>1.6.0-SNAPSHOT</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<instructions>
<Export-Package>it.tidalwave.imageio.*;-split-package:=merge-first</Export-Package>
<Bundle-Name>jrawio</Bundle-Name>
<Bundle-SymbolicName>it.tidalwave.imageio.raw</Bundle-SymbolicName>
<Bundle-DocURL>http://jrawio.rawdarkroom.org</Bundle-DocURL>
<Embed-Dependency>processor;codec;inline=true</Embed-Dependency>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>

The job is done by the maven-bundle-plugin. The relevant instructions for the bundler are:

  • Export-Package. It's the list of the packages that are made available outside of the bundle. The interesting part is the option -split-package:=merge-first. It happens that both modules, codec and processor, have a few packages in common (the bundle calls this scenario "split-package"). Usually it's a sign of bad design (low cohesion?) - in fact the package design of jrawio needs a heavy refactoring. The option tells the bundler to merge the packages and that in case of conflicts the first included module "wins". After the refactoring is done, thus the package clash is resolved, I'll change this to "error", so the bundle will stop and warn me if I miss to fix something.
  • Bundle-Name. It's the name of the bundle as people would read it.
  • Bundle-SymbolicName. It's a fully qualified and unique name of the bundle.
  • Bundle-DocURL. It's a URL pointing to the website of the project delivering the bundle.
  • Embed-Dependency. This instruction lists the modules to put in the bundle ("processor" and "codec") and also tells the bundler that they must be copied inside the bundle, rather than referred externally.

 

At the moment the API of jrawio is not official, but people use it. For this reason, all the packages are public. In future, a refactoring will introduce a clear separation between public packages and implementation packages and this section will change.


The produced MANIFEST.MF is the following (Import-Package and Export-Package have been shortened as they are very long):

Manifest-Version: 1.0
Implementation-Build: 47ea1563549d+
Built-By: fritz
Created-By: Apache Maven Bundle Plugin
Bundle-License: http://www.apache.org/licenses/LICENSE-2.0.html
Import-Package: it.tidalwave.imageio.arw,it.tidalwave.imageio.cr2,it.t
idalwave.imageio.crw,it.tidalwave.imageio.dcr,it.tidalwave.imageio.de
m ...
Bnd-LastModified: 1253050787815
Export-Package: it.tidalwave.imageio.decoder;uses:="javax.imageio.stre
am,javax.annotation,it.tidalwave.imageio.io,it.tidalwave.imageio.util
o.spi,javax.imageio.metadata,org.w3c.dom" ...
Bundle-Version: 1.6.0.SNAPSHOT
Bundle-Name: jrawio
Bundle-Description: Camera raw support in Java
Build-Jdk: 1.5.0_20
Private-Package: it.tidalwave.imageio.impl,
Bundle-DocURL: http://jrawio.rawdarkroom.org
Bundle-ManifestVersion: 2
Bundle-Vendor: Tidalwave s.a.s.
Bundle-SymbolicName: it.tidalwave.imageio.raw
Tool: Bnd-0.0.311
Embed-Dependency: processor;codec;inline=true

 

Generating the NBM for the NetBeans Platform

Also this artifact is created by a specific module (it-tidalwave-imageio-raw, which is the exact name of the .nbm artifact it's going to produce), whose pom is the following:

<?xml version="1.0" encoding="UTF-8"?>
<project ... >

<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>it.tidalwave.imageio</groupId>
<artifactId>jrawio</artifactId>
<version>1.6.0-SNAPSHOT</version>
</parent>

<artifactId>it-tidalwave-imageio-raw</artifactId>
<packaging>nbm</packaging>
<name>jrawio - NBM artifact</name>

<dependencies>
<dependency>
<groupId>it.tidalwave.imageio</groupId>
<artifactId>it.tidalwave.imageio.raw</artifactId>
<version>1.6.0-SNAPSHOT</version>
</dependency>
<!-- These dependencies with 'provided' scope won't be embedded in the .nbm -->
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<version>1.3.7</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>it.tidalwave.imageio</groupId>
<artifactId>codec</artifactId>
<version>1.6.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>it.tidalwave.imageio</groupId>
<artifactId>processor</artifactId>
<version>1.6.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>nbm-maven-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<publicPackages>
<publicPackage>it.tidalwave.imageio</publicPackage>
<publicPackage>it.tidalwave.imageio.arw</publicPackage>
<publicPackage>it.tidalwave.imageio.cr2</publicPackage>
<publicPackage>it.tidalwave.imageio.crw</publicPackage>
<publicPackage>it.tidalwave.imageio.dcr</publicPackage>
<publicPackage>it.tidalwave.imageio.decoder</publicPackage>
<publicPackage>it.tidalwave.imageio.dng</publicPackage>
<publicPackage>it.tidalwave.imageio.io</publicPackage>
<publicPackage>it.tidalwave.imageio.makernote</publicPackage>
<publicPackage>it.tidalwave.imageio.minolta</publicPackage>
<publicPackage>it.tidalwave.imageio.mrw</publicPackage>
<publicPackage>it.tidalwave.imageio.nef</publicPackage>
<publicPackage>it.tidalwave.imageio.orf</publicPackage>
<publicPackage>it.tidalwave.imageio.pef</publicPackage>
<publicPackage>it.tidalwave.imageio.raf</publicPackage>
<publicPackage>it.tidalwave.imageio.raw</publicPackage>
<publicPackage>it.tidalwave.imageio.rawprocessor</publicPackage>
<publicPackage>it.tidalwave.imageio.rawprocessor.arw</publicPackage>
<publicPackage>it.tidalwave.imageio.rawprocessor.cr2</publicPackage>
<publicPackage>it.tidalwave.imageio.rawprocessor.crw</publicPackage>
<publicPackage>it.tidalwave.imageio.rawprocessor.demosaic</publicPackage>
<publicPackage>it.tidalwave.imageio.rawprocessor.dng</publicPackage>
<publicPackage>it.tidalwave.imageio.rawprocessor.mrw</publicPackage>
<publicPackage>it.tidalwave.imageio.rawprocessor.nef</publicPackage>
<publicPackage>it.tidalwave.imageio.rawprocessor.orf</publicPackage>
<publicPackage>it.tidalwave.imageio.rawprocessor.pef</publicPackage>
<publicPackage>it.tidalwave.imageio.rawprocessor.raf</publicPackage>
<publicPackage>it.tidalwave.imageio.rawprocessor.raw</publicPackage>
<publicPackage>it.tidalwave.imageio.rawprocessor.srf</publicPackage>
<publicPackage>it.tidalwave.imageio.srf</publicPackage>
<publicPackage>it.tidalwave.imageio.tiff</publicPackage>
<publicPackage>it.tidalwave.imageio.util</publicPackage>
</publicPackages>
</configuration>
</plugin>
</plugins>
</build>
</project>

This looks a bit more complex, but you'll find out that it's just more verbose.

First, note that we are depending on it.tidalwave.imageio.raw, since the job of merging all the classes in a single jar has been performed by that module. After all we're creating a wrapper module around the JSE artifact.

Now, you should notice all these dependencies with scope=provided. Why are they here? The problem is that the nbm-maven-plugin, by default, puts into the artifact all the transitive dependencies; so it would put not only it.tidalwave.imageio.raw, but also codec, processor (and the annotations for jsr305, that are used in the code); scope=provided tells the plugin to not embed them.

Now we have to declare the public packages that will be exported by the nbm artifact. Instead of the OSGi bundler, that accepts wildcards to specify subpackages, we have to enumerate them one by one.

Other parameters are not specified in the pom, but in a specific configuration file, which is placed under src/main/nbm/module.xml:

<nbm>
<moduleType>normal</moduleType>
<codeNameBase>it.tidalwave.imageio.raw/1</codeNameBase>
<cluster>libraries</cluster>
<manifest>src/main/nbm/manifest.mf</manifest>
<homepageUrl>http://jrawio.rawdarkroom.org</homepageUrl>
<author>Fabrizio Giudici</author>
<licenseName>Apache License, Version 2.0</licenseName>
<licenseFile>../../LICENSE.txt</licenseFile>
</nbm>
  • codeNameBase. The fully qualified, unique id of the produced artifact.
  • homePageURL. The URL of the project.
Some more options are specific to the NBM protocol, which is richer than OSGi:
  • moduleType. This is related to the life-cycle of the NetBeans Platform modules.
  • cluster. Clusters are a way that the NetBeans project uses to pack together similar modules. It could be unneeded for single libraries, anyway I've got the habit of putting libraries in a cluster with the same name.
  • author. The author of the code.
  • licenseName. The name of the license.
  • licenseFile. The full text of the license.

I'm not an expert of OSGi yet. I can't exclude that similar attributes are also available in the OSGi world.

In addition to those items of information, I also need to put or override some attributes in the NBM manifest; on that purpose, I can just specify them in a fragment of MANIFEST.MF that is pointed by the manifest entry:

OpenIDE-Module-Java-Dependencies: Java > 1.5
OpenIDE-Module-Display-Category: Libraries
OpenIDE-Module-Name: Libraries - jrawio
AutoUpdate-Show-In-Client: true

Everything that is in this fragment won't be overridden by the plugin. The produced manifest is:

Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Created-By: Apache Maven
Built-By: fritz
Build-Jdk: 1.5.0_20
Implementation-Build: 86e32fde717a+
OpenIDE-Module-Java-Dependencies: Java > 1.5
OpenIDE-Module-Display-Category: Libraries
OpenIDE-Module-Name: Libraries - jrawio
AutoUpdate-Show-In-Client: true
OpenIDE-Module-Specification-Version: 1.6.0
OpenIDE-Module-Implementation-Version: 1.6.0-20090916
OpenIDE-Module-Build-Version: 200909161113
OpenIDE-Module: it.tidalwave.imageio.raw/1
OpenIDE-Module-Public-Packages: it.tidalwave.imageio.*, it.tidalwave.i
mageio.arw.*, it.tidalwave.imageio.cr2.*, it.tidalwave.imageio.crw.*,
it.tidalwave.imageio.dcr.*, ...
OpenIDE-Module-Requires: org.openide.modules.ModuleFormat1
OpenIDE-Module-Short-Description: Camera raw support in Java
OpenIDE-Module-Long-Description: Camera raw support in Java
Class-Path: ext/it.tidalwave.imageio.raw-1.6.0-SNAPSHOT.jar
As you can see by looking at the Class-Path attribute, we have generated a wrapper module - that is, it just embeds the original jar, adding another manifest. There are different solutions, such as creating a regular nbm, or even re-using the existing jar, where the OpenIDE-Module-* attributes have been added to the manifest. This sounds as extremely interesting, as it would allow me to release a single jar artifact for all the three environments; but I've still to learn a few details (in particular, the co-existence with OSGi stuff), and furthermore I preferred this intermediate step for testing (I just replaced the Maven-generated jrawio nbm in blueMarine and checked that everything still worked). Any improvement will go in future versions of jrawio.

Build vs Buy a Data Quality Solution: Which is Best for You? Maintaining high quality data is essential for operational efficiency, meaningful analytics and good long-term customer relationships. But, when dealing with multiple sources of data, data quality becomes complex, so you need to know when you should build a custom data quality tools effort over canned solutions. Download our whitepaper for more insights into a hybrid approach.

Topics:

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}