Thursday, February 12, 2009

OpenJPA Enhancement

What is Enhancement Anyway?

I have cruised around the OpenJPA forums enough to notice that numerous new developers have posted questions regarding enhancement. Since I'm new to OpenJPA myself, I figured I'd dive in to enhancement and see what all the fuss is about. This post is directed mainly at developers that are taking a first look at OpenJPA and having problems getting started. No worries for those of you deploying an application to run in a Java EE 5 compliant application server such as WebSphere Application Server because your Entities will be automatically enhanced at runtime.

As I was exploring how to enhance, I started to wonder why do I need to enhance at all? As it turns out the JPA spec requires some type of monitoring of Entity objects, but the spec does not define how to implement this monitoring. Some JPA providers auto-generate new subclasses or proxy objects that front the user's Entity objects, while others like OpenJPA use byte-code weaving technologies to enhance the actual Entity class objects. Now that we've got that out of the way, lets get back to how to do the enhancement. I'm going to give runtime examples first, as I feel those are the easiest from a developers point of view.

Runtime enhancement-When running in a JSE environment or in a non EE-5 compliant container, OpenJPA defaults to using subclassing enhancement. The subclassing enhancement support was added originally as a convenience to new developers to reduce the amount of work to get a 'HelloWorld-ish' OpenJPA application working out of the box. It was never meant to run in production. So you're probably thinking that this sounds great! OpenJPA handles enhancement automatically for me and I can stop reading this post. Wrong! Subclassing has two major drawbacks. First off, it isn't nearly as fast as byte-code enhancement and the second drawback is that there are some documented functional problems when using the subclassing support. The moral of the story is, don't use this method of enhancement. I'm not the only one making this recommendation as I've come across form posts about removing subclassing enhancement as the default OpenJPA behavior. Additional information regarding the subclassing enhancement can be found here.

The second and recommended way get runtime enhancement is to provide a javaagent when launching the JVM that OpenJPA is going run in. This is the method that I use in most of my test environments because it is very painless. All that is required to get runtime enhancement in Eclipse is to specify the -javaagent:[open_jpa_jar_location] on the Run Configuration page. That is it.

Another simple way to get runtime enhancement is to specify the the -javaagent when launching an application from ant. Below is a snippet from my build.xml that shows how to pass the -javaagent when launching a JSE application that uses OpenJPA. 
    <path id="jpa.enhancement.classpath">
        <pathelement location="bin"/>
        <!-- lib contains all of the jars that came with the OpenJPA binary download -->
        <fileset dir="lib">
            <include name="**/*.jar"/>
        </fileset>
    </path>
...
<target name="drive" depends="clean, build">
        <echo message="Running test with run time enhancement."/>
        <java classname="main.Driver" failonerror="true" fork="yes">
            <jvmarg value="-javaagent:${openJPA-jar}"/>
            <classpath refid="jpa.enhancement.classpath"/>
        </java>
    </target>

Buildtime enhancement -
In this section I'm going to cover how to invoke the build time enhancer ant task that is packaged with OpenJPA through the use of ant and maven. Yes there are many different ways to buildtime enhancement bliss, but I'm only going to talk about using the ant task. Note that this entire section assumes you're running the ant/maven scripts from a command line. I can't say for certian how this works inside Eclipse. Warning: I'm not a maven wizard, so please be nice if I've commited some cardinal maven sin. :-)

I'll start first by showing how to define a OpenJPA enhancer task and how to invoke the task in ant. I had to fumble a little to get it working, but overall it was pretty straight forward. First you'll need to compile the Entites. (Note: as a prereq to running the enhance task, I copied my persistence.xml file to my /build directory. You might not need to do this, but the persistence.xml has to be in the classpath.) Next you'll need to configure the enhancer task and a classpath where the task can be found.(Adding the classpath is the part that the OpenJPA manual missed.) The final step is to call the enhance task. A snippet is provided below.
    <path id="jpa.enhancement.classpath">
        <pathelement location="bin"/>

        <!-- lib contains all of the jars that came with the OpenJPA binary download -->
        <fileset dir="lib">
            <include name="**/*.jar"/>
        </fileset>
    </path>


    <target name="enhance" depends="build">
    <!-- This is a bit of a hack, but I needed to copy the persistence.xml file from my src dir
        to the build dir when we run enhancement -->
    <copy includeemptydirs="false" todir="bin">
        <fileset dir="src" excludes="**/*.launch, **/*.java"/>
    </copy>


    <!-- define the openjpac task -->
    <taskdef name="openjpac" classname="org.apache.openjpa.ant.PCEnhancerTask">
        <classpath refid="jpa.enhancement.classpath"/>
    </taskdef>
      
    <!-- invoke enhancer the enhancer -->
    <openjpac>
        <classpath refid="jpa.enhancement.classpath"/>
    </openjpac>
    <echo message="Enhancing complete."/>
</target>
The second(different) path to build time enhancement is to use the maven antrun plug-in to launch the OpenJPA enhancer task. Since I'm new to maven this road was pretty clunky, but the steps were nearly identical to running directly in ant. I'll include the parts from my pom.xml that took the most time to figure out. Again, I'm not sure if you will need to move the persistence.xml file to the build directory, but I did(I assume I'm not doing something right).
  <build>
  <!-- Copy the persistence.xml file to the build dir -->
  <!-- You can skip this step if you put the persistence.xml in src/main/resources/META-INF instead of src/main/java/META-INF -->
  <resources>
      <resource>
        <directory> src/main/java </directory>
        <includes>
          <include> **/*.xml </include>
          </includes>
      </resource>
    </resources>
    <plugins>
.....           
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-antrun-plugin</artifactId>
        <version>1.2</version>
        <executions>
          <execution>
              <phase>process-classes</phase>
            <configuration>
              <tasks>
                  <taskdef name="openjpac" classname="org.apache.openjpa.ant.PCEnhancerTask" classpathref="maven.compile.classpath"/>
                  <openjpac>
                      <classpath refid="maven.compile.classpath"/>
                  </openjpac>
              </tasks>
            </configuration>
            <goals>
              <goal>run</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
....
  </build>
Hopefully this helps someone out there! Feel free to contact me if you have any questions/comments/suggestions.

-Rick