Technical Blog

2 Posts tagged with the enhance tag

Ever wondered why you need to enhance an OpenJPA persistent class, or what exactly does the 'enhancer' do? This post explains why you need to enhance Elastic Path's persistent classes and talks a bit about what OpenJPA enhancer does to our code.

 

An OpenJPA enhancer post-processes the bytecode of the persistent classes that are generated by the Java compiler. During post-processing, the enhancer inserts necessary code into the generated bytecode so that it can include persistence features in classes.

 

For example, after the enhancer runs, persistent classes will implement the org.apache.renamed.openjpa.enhance.PersistenceCapable and the java.io.Externalizable interfaces. Some of the fields and methods generated include the ones that are required for implementing the PersistenceCapable interface, as well as the ones from Externalizable interface, which helps with the process of reading and writing data to disk. Other features that gets implemented into an enhanced class are the ability to lazy load collection proxies and a method of doing dirty-field checking, which is used to determine what needs to be persisted.

Important: Always use the accessor methods to access the fields of persistent objects. Accessing fields directly will bypass the dirty-field checking and lazy-loading features, which will cause persisted fields to be out of date.

 

Sample methods inserted by enhancer:
public void readExternal(ObjectInput objectinput)
    throws IOException, ClassNotFoundException {
    ...
}
 
public void writeExternal(ObjectOutput objectoutput)
    throws IOException {
    ...
}
 
public boolean pcIsDirty() {
    ...
}
 
public boolean pcIsPersistent() {
    ...
}
 
public boolean pcIsTransactional() {
    ...
}

 

In addition to adding methods from the PersistenceCapable and Externalizable interfaces, the enhancer adds a method for each setter and getter method in the pre-enhanced bytecode. For example, if there was a getUid() method for a persistent field uid, then there would be a method generated by the enhancer called pcgetUid(), and the original getUid() method would call the generated pcgetUid(). Inside each of the original setter and getter methods, there is a call to a global StateManager (also created by the enhancer), which is responsible for fetching and storing the persistent data.

 

The following code samples show the differences between the original Java code, the pre-enhanced bytecode, and the post-enhanced bytecode (decompiled). Notice how all enhancer-generated fields and methods that are not part of PersistenceCapable or Externalizable interface have the letters pc prepended to their names. The pc prefix stands for PersistenceCapable.

 

 

Original .java file:
@Basic
@Column(name = "IMAGE_URL")
public String getImageUrl() {
    return imageUrl;
}
 
public void setImageUrl(final String imageUrl) {
    this.imageUrl = imageUrl;
}

 

Pre-enhanced .class file:
public String getImageUrl() {
    return imageUrl;
}
 
public void setImageUrl(String imageUrl) {
    this.imageUrl = imageUrl;
}

 

Post-enhanced .class file:
public String getImageUrl() {
    if(pcStateManager == null) {
        return pcgetImageUrl();
    } else {
        int i = pcInheritedFieldCount + 2;
        pcStateManager.accessingField(i);
        return pcgetImageUrl();
    }
}
 
public void setImageUrl(String s) {
    if(pcStateManager == null) {
        pcsetImageUrl(s);
        return;
    } else {
        pcStateManager.settingStringField(this, pcInheritedFieldCount + 2, pcgetImageUrl(), s, 0);
        return;
    }
}
 
protected String pcgetImageUrl() {
    return imageUrl;
}
 
protected void pcsetImageUrl(String s) {
    this.imageUrl = s;
}

 

The bytecode modification is done in such a way that it allows the modified class to keep its compatibility with Java debuggers. This means that even with extra lines of code inserted within various areas of a class, the modifcations can still keep the line numbers the same in stack traces.

 

Now whenever you enhance a class, you'll know why you even bother doing it...but don't just take my word for it, wrap your de-compiler around an enhanced class and see for yourself!


0 Comments Permalink

So, you've imported all your Elastic Path projects into Eclipse. You've set up Tomcat to run your Elastic Path web apps from within Eclipse and you've configured the log4j.properties for each web app. You launch the server. Your web apps don't start. What did you do wrong?

 

You check your web app logs. Buried deep in the stack trace, you see this:

 

Caused by: <openjpa-1.0.1-ep-6.1-1-rsvnversion: invalid option:  nonfatal user error> org.apache.renamed.openjpa.persistence.ArgumentException: [Error while processing persistent field com.elasticpath.domain.order.impl.OrderImpl.status, declared in null. Error details: The accessor for field setStatus in type com.elasticpath.domain.order.impl.OrderImpl is private or package-visible. OpenJPA requires accessors in unenhanced instances to be public or protected. If you do not want to add such an accessor, you must run the OpenJPA enhancer after compilation, or deploy to an environment that supports deploy-time enhancement, such as a Java EE 5 application server.]

 

The clue here is this:

 

you must run the OpenJPA enhancer after compilation

 

OpenJPA does bytecode enhancement on Elastic Path's persistent classes. You can learn the details of why and how in this article on OpenJPA bytecode enhancement, but if you just want to start your web apps, here's what you need to do:

  1. In Eclipse, expand the com.elasticpath.core project.
  2. Select coreAntJpaEnhance.launch.
  3. Right-click and choose Run As -> coreAntJpaEnhance.

 

You usually need to do this when you first set up your Elastic Path dev environment. After that, you need to do it whenever you modify persistent classes (basically the com.elasticpath.domain.**/*Impl classes in the com.elasticpath.core project). The jar target in the com.elasticpath.core project will do the enhancement and install it in your Maven repository, so make sure you use it before rebuilding your web apps.

 

But sometimes, it doesn't work...

There is a workaround:

  1. In the project explorer in Eclipse, select the web app project's root node.
  2. In the Project menu, choose Properties.
  3. Select Maven, and uncheck Resolve dependencies from Workspace projects.

The projects will now use the core jar that was built last time the com.elasticpath.core/build.xml jar target was run.

 

It's still not working!

If you have persistent classes that are not in the com.elasticpath package structure, you need to modify the openjpa enhance task call in the enhance target of openjpa.xml to include your packages:

 

<openjpac>
      <config propertiesFile="${src.main.java.dir}/META-INF/persistence-renamed.xml"/>
      <classpath refid="classpath"/>
      <fileset dir="${target.classes.main.dir}">
   <include name="com/elasticpath/domain/**/impl/*.class"/>
   <include name="com/my/package/**/impl/*.class"/>
      </fileset>
</openjpac>

 

Hopefully, that will get you through most of your OpenJPA related enhance problems :-)

0 Comments Permalink