The solution I'm going to outline is by no means final, and in the meantime, my first shot has been greatly simplified using some valuable input from the Apache Aries User mailing list.
In the past, brute force solutions like DynamicImport-Package were more or less the only way to let a JPA provider bundle access all the required classes for a persistence unit from application bundles. Since Release 4.2, the OSGi Enterprise Specification standardizes a whole lot of OSGi service wrappers for well-known Java EE APIs, including JPA, JDBC, JTA and JNDI, so this should be the preferred way of integrating JPA providers into an OSGi environment.
The Apache Aries project provides (partial) implementations for some of the OSGi enterprise services and a whole lot of other things beyond the scope of this article. Fortunately, all their service implementations are nicely modular, so it is quite easy to use just the JPA and JNDI Services from Aries to make OpenJPA work on OSGi, following the OSGi Enterprise spec rather closely (though not fully).
Here is an overview of the required steps:
- Get OSGi bundles for OpenJPA and all its dependencies, plus the required Aries bundles.
- Publish the datasource to be used by your persistence unit as an OSGi service.
- Make sure that your persistence.xml satisfies a number of restrictions.
- Run the OpenJPA enhancer and package all classes and metadata of your persistence unit into a bundle with a special manifest header.
- Start your OSGi framework. The JPA Service will publish an EntityManagerFactory service for each persistence unit.
- In your client bundles, import your persistence bundle, OpenJPA and javax.persistence, get the appropriate EntityManagerFactory from the service registry and use it just like in an unmanaged Java SE environment.
Get the dependencies
The main JAR in the OpenJPA binary distribution is an OSGi bundle, but most of the included dependencies are plain old JARs, so you need to go shopping for osgified versions of all required libraries. All the required bundles for this example can be found in the SpringSource Enterprise Bundle Repository.
The following is the best part of a Maven POM for copying all the dependencies to a local directory target/platform/plugins.
You need to add the OSGi framework, javax.persistence (I copied both of them from Eclipse 3.5.2) and a JDBC driver bundle for your database. Then copy the whole lot to a new directory to be used as target platform for your Eclipse workspace.
<project> <properties> <aries.version>0.2-incubating</aries.version> <output.dir>${project.build.directory}/platform/plugins</output.dir> </properties> <dependencies> <dependency> <groupId>javax.transaction</groupId> <artifactId>com.springsource.javax.transaction</artifactId> <version>1.1.0</version> </dependency> <dependency> <groupId>net.sourceforge.serp</groupId> <artifactId>com.springsource.serp</artifactId> <version>1.13.1</version> </dependency> <dependency> <groupId>org.apache.aries</groupId> <artifactId>org.apache.aries.util</artifactId> <version>${aries.version}</version> </dependency> <dependency> <groupId>org.apache.aries.jndi</groupId> <artifactId>org.apache.aries.jndi.api</artifactId> <version>${aries.version}</version> </dependency> <dependency> <groupId>org.apache.aries.jndi</groupId> <artifactId>org.apache.aries.jndi.core</artifactId> <version>${aries.version}</version> </dependency> <dependency> <groupId>org.apache.aries.jndi</groupId> <artifactId>org.apache.aries.jndi.url</artifactId> <version>${aries.version}</version> </dependency> <dependency> <groupId>org.apache.aries.jpa</groupId> <artifactId>org.apache.aries.jpa.api</artifactId> <version>${aries.version}</version> </dependency> <dependency> <groupId>org.apache.aries.jpa</groupId> <artifactId>org.apache.aries.jpa.container</artifactId> <version>${aries.version}</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>com.springsource.org.apache.commons.collections</artifactId> <version>3.2.1</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>com.springsource.org.apache.commons.lang</artifactId> <version>2.2.0</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>com.springsource.org.apache.commons.pool</artifactId> <version>1.5.3</version> </dependency> <dependency> <groupId>org.apache.openjpa</groupId> <artifactId>openjpa-all</artifactId> <version>2.0.1</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>com.springsource.slf4j.api</artifactId> <version>1.5.0</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>com.springsource.slf4j.log4j</artifactId> <version>1.5.0</version> </dependency> <dependency> <groupId>org.apache.log4j</groupId> <artifactId>com.springsource.org.apache.log4j</artifactId> <version>1.2.15</version> </dependency> </dependencies> <build> <plugins> <plugin> <artifactId>maven-dependency-plugin</artifactId> <executions> <execution> <id>copy-bundles</id> <phase>package</phase> <goals> <goal>copy-dependencies</goal> </goals> <configuration> <outputDirectory>${output.dir}</outputDirectory> </configuration> </execution> </executions> </plugin> </plugins> </build> <repositories> <repository> <id>com.springsource.repository.bundles.external</id> <name>SpringSource Enterprise Bundle Repository - External Bundle Releases</name> <url>http://repository.springsource.com/maven/bundles/external</url> </repository> </repositories> </project>
Register a data source service
A persistence unit requires a data source, either from a javax.sql.DataSource or from a JDBC driver URL. This may be a matter of taste - in this article, I'm focusing on the DataSource.
Create a DataSource for your database using any suitable driver and register it as an OSGi service with the javax.sql.DataSource interface and optionally some additional identifying properties of your choice, e.g. name=myds, in case you have more than one DataSource in your system.
It is useful to do this from a separate bundle, so that this bundle will be the only one in your system with a direct dependency on the JDBC driver.
Adapt your persistence.xml
If you have a persistence.xml which works on Java SE oder Java EE, you will probably have to modify it to make it work with OSGi and Aries. Here is an example:
<?xml version="1.0" encoding="UTF-8"?> <persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"> <persistence-unit name="myapp" transaction-type="RESOURCE_LOCAL"> <non-jta-data-source>osgi:service/javax.sql.DataSource</non-jta-data-source> <mapping-file>META-INF/orm.xml</mapping-file> <exclude-unlisted-classes>true</exclude-unlisted-classes> </persistence-unit> </persistence>
First, you need to specify the RESOURCE_LOCAL transaction type explicitly, or else OpenJPA will complain about a missing transaction manager. This is because Aries creates a container EntityManagerFactory and not a stand-alone one.
Second, your data source has to be a non-jta-data-source. Note the special syntax for accessing your data source as an OSGi service via JNDI. To make this work, you need to include the Aries JNDI Service bundles. To select a specific data source by properties, you can append an LDAP filter to the JNDI URL, e.g.
osgi:service/javax.sql.DataSource/(name=myds)
.Third, you need to prevent the persistence provider from scanning your persistence unit for entity classes, since this may lead to classloader problems. To do so, either list all your classes explicitly if you use JPA annotations, or include all mapping files if you use XML metadata and set exclude-unlisted-classes to true to prevent the provider from scanning your bundle.
If you get funny exceptions like
org.apache.openjpa.persistence.PersistenceException: Error extracting class information from "bundleresource://23.fwk1421571929/". Caused by: java.io.FileNotFoundException: /myapp/plugins/test.openjpa/bin (Is a directory)
then check if you have set this option. (This problem may be specific to Eclipse, but I'm not sure about that.)
Package your persistence unit bundle
To turn your persistence unit into a persistence bundle, make sure you have all mapping data resources and all entity classes included. In addition to the usual OSGi headers, insert the following one into your manifest:
Meta-Persistence: META-INF/persistence.xml
This is the hook for Aries to discover your persistence bundle using the extender pattern. If all goes well, an EntityManagerFactory will be registered as an OSGi service.
In theory, your persistence bundle should only depend on javax.persistence and not on org.apache.openjpa. In practice, you currently need to include this unwanted import, or else you will get exceptions of the following kind:
java.lang.NoClassDefFoundError: org/apache/openjpa/enhance/PersistenceCapable
Apparently, OpenJPA tries to load one of its own classes via your persistence bundle class loader, which is incorrect.
Use the EntityManagerFactory service in your clients
Finally, to work with your persistence unit from client bundles, just get hold of the appropriate EntityManagerFactory service any way you like, using the filter
(osgi.unit.name=myapp)
.Update 26 Sep 2010: I have submitted some sample code for inclusion into the OpenJPA test suite. The sample is attached to OPENJPA-1815.