Persistence in OSGi with OpenJPA — part 2


In the previous blog entry on openJPA in OSGi, we showed that it is fairly easy to use
openJPA within an OSGi context. This was demonstrated by creating a bundle that
provided a persistence service, that was able to persist and list sample domain
objects. As the goal was to get it working, we didn’t bother about separating stuff
over several bundles and put all in one bundle. In this blog, we’ll show how to make it
work with separate bundles, and how this simplifies using JPA within an OSGi context.

There are two disadvantages with respect to the single bundle approach (apart from
that it’s just ugly). Suppose you would develop deploy several bundles that provide
persistence services, and that these would all contain the OpenJPA jar (2.8 M) and all
of its supporting libraries (another megabyte). Having copies of these jars in each
bundle whould be a waste of space (disk space and memory!) and if you would
like to update one library, you’d have to update all bundles. Of course, it would be
much better if each “component” (whatever that me be) is in its own bundle.

Another issue is that the domain objects (in the code sample the Person
class) are bundled together with the persistence service. This is not ideal, as you can
image that domain objects can be used without persistence. Bundling them together would
require each application that uses domain objects, to have the persistence service
bundle to be installed as well. Having the domain objects in a separate,
independant bundle, results in a more modular configuration.

A domain bundle

We’ll start with the last issue, as this is the simplest one to fix. Starting point is
the sample code we developed last time (download the zip for all sample code).
The test client, a bundle that extends the
Felix command shell with a simple command to call our persistence service, is exactly
the same as last time. However, if you compare the first build file
(build1.xml) of the jpa-sample bundle with the ones from the previous blog
entry, you’ll notice differences with respect to imports of the sample bundle. This is
the result of upgrading to OpenJPA 1.1.0. With this new OpenJPA version, a number of
(obscure) dependencies (oracle, apache.avalon, weblogic) seem to be vanished, and also
commons-logging doesn’t seem to be required anymore, so we could clean up the
<bundle&gt task a bit.

Separating the domain classes is easy: create a bundle that only contains the
net.luminis.sample.jpainosgi.domain package. At the same time, we’ll
remove this package from the existing jpasample bundle (that is now
renamed to jpa-sample-service). This is not
required (OSGi can handle several bundles providing the same package), but we want to
ensure that our new setup works when the domain classes are in the domain bundle only.
However, as the domain classes use the javax.persistence package, this is
not enough: someone must export this package in order to make the domain bundle
resolvable. As this package is in the jpa-sample-service bundle, we’ll add an extra
export there (build2.xml).

If you’d deploy these two bundles in Felix and test it, you’d notice this works. But
it’s not yet perfect: due to the javax.persistence package that is
provided by the jpa-sample-service bundle, our newly created domain bundle still needs
the service bundle. That’s not we wanted, the domain bundle should be completely
independent from the service bundle! Hence, we must move out the
javax.persistence from the service bundle and make it a separate bundle.

Supporting libraries

The OpenJPA distribution comes with an implementation of the
javax.persistence package
(geronimo-jta_1.1_spec-1.1.jar). Turning this library into an OSGi bundle
is not difficult, and can be easily achieved with the Bnd tool (*).
But creating these bundles by hand is not necessary anymore, because the
javax.persistence and javax.transaction libraries that are
shipped with Apache Geronimo since version 2.1, are already OSGi bundles.
If you download the 2.1.2 Geronimo release, you’ll find the libraries in the
geronimo-jetty6-javaee5-2.1.2/repository/org/apache/geronimo/specs
subdirectory.

There are more common libraries that are used by OpenJPA and that can be easily
stripped from the bundle: the most used Apache Commons libraries are nowadays released as OSGi
bundles too. Unfortunately, the ones shipped with OpenJPA are not OSGi ready, but the
newest releases of Commons Collections, Commons Lang and Commons Pool are (see
overview). So you can simply
download these new versions and install them right away in your OSGi framework.

The Postgres JDBC driver is not yet available as OSGi bundle, so i fixed this by hand
(with a little help from Bnd). You can download this bundle, as well as the Bnd
configuration file used to create this bundle, from the
luminis open source server.
This results in a much smaller jpa-sample-service
bundle (build4.xml).

OpenJPA OSGi bundle

The final step, removing the openjar itself from our bundle, is a little bit more
tricky. We start by creating a bundle from the OpenJPA jar file. This time the Bnd
configuration file is less trivial; not only we must provide the (do-not)import
specifications that were previously in the build file (!org.apache.tools.ant;
!org.apache.tools.ant.types;
, etc), but also we need to explicitely export the
packages that will be needed by other bundles. This leads to an OSGi enabled version of
the OpenJPA jar (download it here).
Next we remove the openjpa stuff from our jpa-sample-service bundle. Trying to run this
version results in an “Creating EntityManagerFactory failed!” error. What’s going on here?

To understand what’s going on, we must understand how the generic
javax.persistence.Persistence class, finds a specific
EntityManagerFactory (since that class is part of the JPA implementation
you use, in constract to the Persistence class that bootstraps it). The find out which
JPA implementation you’d like the use, the Persistence class searches the classpath for
a provider configuration file in the META-INF/services resource
directory, see href="http://java.sun.com/j2se/1.5.0/docs/guide/jar/jar.html#Service%20Provider">Jar
file specification (actually, it asks the thread-context-classloader to list all
these files). In our old scenario, with the openjpa jar inside the bundle, this worked
well because the openjpa.jar was on the bundle-classpath (and hence, could be loaded by
the bundle classloader, that was set as context-classloader). But now, these service
definitions are in another bundle and it is not possible to load these resources from
outside (it is possible to load resources from another bundle, but not in this way).

We can solve this problem by explicitly telling the Persistence class which persistence
provider we want. This is possible by passing a property:

props.put("javax.persistence.provider", "org.apache.openjpa.persistence.PersistenceProviderImpl");

Hard-coding this is of course bad style, as we would tie our persistence service to one
specific JPA implementation. In production ready software, one would make this configurable.

Now, the Persistence class knows which persistence provider (class) to create. We must
only ensure that this class is visible for the jpa-sample-service bundle: it must
import org.apache.openjpa.persistence and org.apache.openjpa.jdbc.kernel. With this
setup (build5.xml) the bundle can start and create an EntityManagerFactory.

Unfortunately, this does not mean that calling the persistence service succeeds. When
you try, you’ll get a ClassNotFoundException for the Person class. This is caused by
the fact that OpenJPA tries to load the Person class from the same classloader that has loaded
the OpenJPA classes. In this case, that is the bundle classloader of the openjpa.jar
bundle, which of course, cannot find the Person class (it does not import it). In the
previous setup, both the openjpa.jar and the Person class were in the same bundle and
thus loaded via the same (bundle) classloader.

We can solve this by setting the thread context classloader, as OpenJPA tries that
classloader too. The disadvantage of this approach is that we have to add
setContextClassLoader() calls to each and every method in our service
implementation that uses JPA, e.g.

public void list() {
    ClassLoader oldCL = Thread.currentThread().getContextClassLoader();
    Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());

    EntityManager entityManager = entityManagerFactory.createEntityManager();

    EntityTransaction transaction = entityManager.getTransaction();
    try {
        transaction.begin();
        List resultList = entityManager.createQuery("select p from Person p").getResultList();
        System.out.println("List of all persons in db: ");
        for (Object p: resultList)
            System.out.println(p);
    }
    finally {
        transaction.commit();
        entityManager.close();
    }
    Thread.currentThread().setContextClassLoader(oldCL);
}

Of course, this is not what we want. We want to be able to use a “normal” piece of
(JPA) persistence code, without change, in a service implementation. The classloader
fiddling is an infrastructure aspect, that should be separated from our business
code. A simple solution is to use a proxy object that takes care of the context
classloader, see the sample source code for details.

Setting the context classloader solves the class not found issue, but reveals another
one: OpenJPA now complains that the Person class is not enhanced. This is strange,
because OpenJPA enhance persistent classes on the fly – after all, that is how it
worked in all the previous samples. I tried to track down this issue; it has to do with
a difference between the classloader that is used to enhance the classes and the one
that is used for accessing these classes. I’m not sure yet whether this is an OpenJPA
or an OSGi (or Felix) issue. Anyway, it’s easy to solve by enhancing the classes
beforehand. If you run the enhance task in build6.xml before the
bundle task, it creates a domain bundle with pre-enhanced domain
classes. Deploy this bundle in Felix, and see how it all works fine now!

We finally arrived at the situation with two very small sample bundles, that contain no
infrastructural libraries anymore. All libraries used are now bundles on their own, and
deploying these in an OSGi framework is all what it takes to make it work.

Conclusion

We showed that using OpenJPA in an OSGi context is very easy. An OSGi service
implementation that uses OpenJPA, can be implemented in the same way you would do when
used outside OSGi. Although there is one small difference, the need for setting the thread context
classloader upon each service invocation, this can be easily hidden by using a
proxy. If you want to maximize ease of development, and have no worries about the
overhead caused by using dynamic proxies, you could use a dynamic proxy that
automatically delegates all methods in the interface.

The bottom line is that using OpenJPA within an OSGi context is painless. That makes
OpenJPA a perfect solution for introducing persistency in an OSGi based application.

, ,

  1. Nog geen reacties.
(wordt niet gepubliceerd)
  1. Nog geen trackbacks.