Monday, March 28, 2011

Lightweight JPA testing in a JEE environment using the WebSphere Embeddable EJB Container

Recognizing the importance of accelerated development and simplified testing, Enterprise JavaBeans took a page out of the JPA book by including JSE-friendly embeddable container within the EJB 3.1 specification. While the embeddable container doesn't support all the bells and whistles of a full blown container, it gives you plenty of features that can drastically speed up development and more importantly, provide a test environment that more closely resembles a JEE environment - in both configuration and behavior.

The EJB 3.1 specification outlines these features for the embeddable container:
  • Synchronous calling of local stateless, stateful, and singleton beans (singletons are new in EJB 3.1)
  • Declarative and programmatic security
  • Interceptors
  • Use of annotations or XML deployment descriptors
  • Support for JPA 2.0
In addition to these features, the embeddable EJB container provided with WebSphere Application Server V8 provides the ability to easily configure JDBC data sources. This is especially important when using JPA in container managed mode. WebSphere's embeddable container even supports the use of JTA component data sources within your persistence.xml. This allows you to use the same persistence.xml with the embeddable container as you use in your enterprise application.

With the exception of an optional (but recommended) IDE such as Eclipse or RAD 8.0.3, WebSphere Application Server V8 contains everything you need to get started - the Java SDK (depending on platform), the embeddable container, JPA client package, and the Derby database. When you install the beta be sure to install Stand-alone thin clients, resource adapters and embeddable containers.



For the purposes of an example, let's say we have a simple EJB module that is used to manage personal contacts. The ContactModule contains a simple annotated session bean named
ContactBean which is primarly a transactional facade over JPA operations. The operations provided by the EJB allow you to find a contact by name, save a contact, and clear all contacts. The JPA operations operate on a persistent entity, Contact. The ContactBean is a session bean that implements a local interface ContactLocal. While EJB 3.1 will now provide access to an bean without a Local interface, using interfaces is still a good practice. Interfaces provide a layer of abstraction from the base bean. They allow the bean to implement multiple specialized interfaces or allow the client to call a different EJB which implements the interface without modifying Java code. Here are our EJB and JPA classes and persistence.xml:

ejbs/ContactBean.java
package ejbs;

import static javax.ejb.TransactionAttributeType.REQUIRED;
import static javax.ejb.TransactionAttributeType.SUPPORTS;

import java.util.List;

import javax.annotation.Resource;
import javax.annotation.Resources;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import javax.sql.DataSource;

import entities.Contact;

// Data source references for JPA
@Resources({
@Resource(name="jdbc/ContactDS", type=DataSource.class),
@Resource(name="jdbc/ContactNoTxDS", type=DataSource.class)
})
@Stateless
public class ContactBean implements ContactLocal {

@PersistenceContext(unitName="ContactPU")
private EntityManager em;

@TransactionAttribute(SUPPORTS)
public List<Contact> findContactsByLastName(String lastName) {
TypedQuery<Contact> q = em.createNamedQuery("ByLastName", Contact.class);
q.setParameter("lastName", lastName);
return q.getResultList();
}

@TransactionAttribute(REQUIRED)
public Contact saveContact(Contact c) {
return em.merge(c);
}

@TransactionAttribute(REQUIRED)
public void clearContacts() {
Query q = em.createNamedQuery("DeleteAll");
q.executeUpdate();
}
}

ejbs/ContactLocal.java
package ejbs;

import java.util.List;
import javax.ejb.Local;
import entities.Contact;

@Local
public interface ContactLocal {
public List<Contact> findContactsByLastName(String lastName);
public Contact saveContact(Contact c);
public void clearContacts();
}

entities/Contact.java
package entities;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;

@Entity
@NamedQueries( {
@NamedQuery(name="ByLastName",
query="SELECT c FROM Contact c WHERE c.lastName = :lastName"),
@NamedQuery(name="DeleteAll", query="DELETE FROM Contact c")
})
public class Contact {

@Id
@GeneratedValue
private Long id;

private String firstName;
private String lastName;
private String phoneNumber;
private String emailAddress;

public void setId(Long id) {
this.id = id;
}

public Long getId() {
return id;
}

public void setFirstName(String firstName) {
this.firstName = firstName;
}

public String getFirstName() {
return firstName;
}

public void setLastName(String lastName) {
this.lastName = lastName;
}

public String getLastName() {
return lastName;
}

public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}

public String getPhoneNumber() {
return phoneNumber;
}

public void setEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
}

public String getEmailAddress() {
return emailAddress;
}
}

META-INF/persistence.xml
 <?xml version="1.0"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="ContactPU" transaction-type="JTA">
<jta-data-source>java:comp/env/jdbc/ContactDS</jta-data-source>
<non-jta-data-source>java:comp/env/jdbc/ContactNoTxDS</non-jta-data-source>
<class>entities.Contact</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
<properties>
<!-- Create the database tables on startup -->
<property name="openjpa.jdbc.SynchronizeMappings" value="buildSchema"/>
</properties>
</persistence-unit>
</persistence>

The simple EJB + JPA module looks like this when loaded as an Eclipse project:


Since ContactModule uses only Java EE APIs, it only requires WAS_HOME\dev\JavaEE\j2ee.jar to compile. If you use WebSphere, OpenJPA, or other extensions you'll need to include additional jars in your build path. Remember, this is only to compile the EJB + JPA module. We'll need different jars to actually run the module in the Embeddable EJB Container.

Now that the module is defined, we need to create an EJB jar file. As of EJB 3.0, a deployment descriptor (ejb-jar.xml) is not required. You can either use the "jar" command to create the jar or use the Eclipse Export... -> Java -> JAR file wizard. Export the jar to a location such as C:\ContactModule.jar. The EJB jar file should contain:

  • META-INF/MANIFEST.MF
  • entities/Contact.class
  • ejbs/ContactBean.class
  • ejbs/ContactLocal.class
  • META-INF/persistence.xml
We now have an EJB module. Traditionally, to test this module you'd write a web client, deploy it in the app server, and drive some http operations against it. Another option is to create a JEE client container app and then drive the EJB using launchClient. Or you may have used RAD's Universal Test Client to execute EJB operations. All of these options work, but they can be difficult to automate and some of them may require code modifications in order to work properly.

Let's take a look at how the Embeddable EJB Container can be used to simplify this testing using the popular JUnit 4 test framework. Again, I used an Eclipse project, but provided that you have the necessary JUnit 4 libraries, you can also run the same tests from the command line. To test the basic functions of our ContactBean session bean, we'll create a simple JUnit 4 annotated test class, TestContactBean.

test/TestContactBean.java
package test;

import java.util.List;

import javax.ejb.embeddable.EJBContainer;

import org.junit.Assert;
import org.junit.Test;

import ejbs.ContactLocal;
import entities.Contact;

public class TestContactBean {

@Test
public void testContactBean() {

// Create test data
Contact c1 = new Contact();
c1.setFirstName("John");
c1.setLastName("Smith");
c1.setPhoneNumber("555-555-5555");
c1.setEmailAddress("john@someplace.org");

Contact c2 = new Contact();
c2.setFirstName("Jane");
c2.setLastName("Smith");
c2.setPhoneNumber("555-555-5555");
c2.setEmailAddress("jane@someplace.org");

// Create the embeddable container
EJBContainer ec = EJBContainer.createEJBContainer();

try {
// Use the embeddable container context to look up a bean
ContactLocal contactBean = (ContactLocal) ec.getContext().lookup(
"java:global/ContactModule/ContactBean!ejbs.ContactLocal");

// Clear existing contacts
contactBean.clearContacts();

// Save new contacts
contactBean.saveContact(c1);
contactBean.saveContact(c2);

// Query contacts and verify results
List contacts = contactBean.findContactsByLastName("Smith");
Assert.assertEquals(2, contacts.size());
for (Contact c : contacts) {
Assert.assertTrue(c.getLastName().equals("Smith"));
}
} catch (Throwable t) {
Assert.fail("Caught unexpected exception: " + t.getMessage());
} finally {
ec.close();
}
}
}

If you've used JPA in JSE-mode, the pattern looks pretty familiar, right? We use a static EJBContainer.createEJBContainer() method to create the embeddable container and then use a lookup to get an instance of the bean. Knowing what JNDI name to use can be a little tricky, but the new java:global namespace simplifies that as well. In general, you can use java:global/module-name/bean-name[/!interface] to look up the bean. After you have a reference to the bean, you can call its EJB methods. Piece of cake. Well there is a little more to do. In order to get the code to compile and run you need to include ContactModule.jar, WAS_HOME\runtimes\com.ibm.ws.ejb.embeddableContainer_8.0.0.jar, WAS_HOME\runtimes\com.ibm.ws.jpa.thinclient_8.0.0.jar, and the jUnit jars in your classpath.

If we were simply testing EJBs we'd be all set. JPA has a few more requirements we need to take care of. First, since we'll be using Derby as our database, we need to add the Derby library to our classpath. It can be found at WAS_HOME\derby\lib\derby.jar. Second, in an EE environment, JPA uses data sources to connect to a relational database. JPA typically requires both a transactional and non-transactional data source. WebSphere's embeddable container allows us to very easily configure data sources one of two ways. The most flexible of the two approaches is to configure them within a properties file. If a file named embeddable.properties is found on the classpath the properties in this file will automatically get loaded by the embeddable container . The WebSphere Embeddable EJB Container also allows you to pass properties in via Map on the createEJBContainer method. As a third option you can specify a system property to inform the embeddable container where its properties file resides. More information on the properties supported by the embeddable container can be found in the Information Center article Embeddable EJB container configuration properties. You'll find that the embeddable container is extremely configurable. You can even configure settings such as cache size, security options, and logging.

embeddable.properties
# JPA Transactional data source definition
DataSource.ds1.name=jdbc/ContactDS
DataSource.ds1.className=org.apache.derby.jdbc.EmbeddedXADataSource
DataSource.ds1.createDatabase=create
DataSource.ds1.databaseName=ContactDB
DataSource.ds1.transactional=true

# JPA non-Transactional data source definition
DataSource.ds2.name=jdbc/ContactNoTxDS
DataSource.ds2.className=org.apache.derby.jdbc.EmbeddedXADataSource
DataSource.ds2.createDatabase=create
DataSource.ds2.databaseName=ContactDB
DataSource.ds2.transactional=false

# Definitions for global to component namespace bindings - required for @Resource defs
Bean.#ContactModule#ContactBean.ResourceRef.BindingName.jdbc/ContactDS=jdbc/ContactDS
Bean.#ContactModule#ContactBean.ResourceRef.BindingName.jdbc/ContactNoTxDS=jdbc/ContactNoTxDS

Another approach is to define the data source using one or more @DataSourceDefinition(s) annotation(s) on the EJB. For example:

@DataSourceDefinitions({
@DataSourceDefinition(name="jdbc/ContactDS",
className="org.apache.derby.jdbc.EmbeddedXADataSource",
databaseName="JPAinEmbeddedContainer",
transactional=true,
properties={"createDatabase=create"}),
@DataSourceDefinition(name="jdbc/ContactNoTxDS",
className="org.apache.derby.jdbc.EmbeddedXADataSource",
databaseName="JPAinEmbeddedContainer",
transactional=false,
properties={"createDatabase=create"})
})
@Stateless
public class ContactBean implements ContactLocal {
...
}

As you can see, using @DataSourceDefinition is a very simple way to define data sources. There is no properties file or @Resource references to manage. The data sources automatically get registered into the java:comp/env namespace (take a look jta-data-source and non-jta-data-source definitions in the persistence.xml - as a best practice, component data sources are used). However, simplicity comes at the cost of flexibility. A change in the data source definition requires recompiling the application.



There's one more JPA-specific item we need to take care of before running the test. In the full application server, the container takes care of JPA entity enhancement. This does not occur in the embeddable container. Fortunately, you can either enhance the entities at build time or the JPA agent-based enhancer can be used to perform the enhancement at runtime. Enabling the enhancer is as simple as specifying the JVM option -javaagent:C:\/"Program Files/"\IBM\WebSphere\AppServer\runtimes\com.ibm.ws.jpa.thinclient_8.0.0.jar on the java or Eclipse jUnit invocation. If all goes well, you should see a result similar to this...



More information about the Embeddable EJB Container is available in the WebSphere Application Server V8 Information Center. Happy testing!

-Jeremy

6 comments:

simon said...
This comment has been removed by a blog administrator.
OrDousxDous said...

Thanks, a very usefull post.

We have a WAS in a Power machine, but we work with the Rational Web Developer for Windows and I think that it hasn't the embedded container.

How can we develop with the embedded container? Only in remote or we can get the embedded libraries?

Thanks

Eric Wolf said...

Great article! I tried to use it, but getting the following error. It looks to be about the hpel logging Anybody else ran into this? -

java.lang.NoClassDefFoundError: com.ibm.ws.logging.object.hpel.WsLogRecord

Jeremy Bauer said...

Hi Eric,

Thanks for the feedback! Which level of WAS are you using?

huiuiui said...
This comment has been removed by the author.
huiuiui said...

Hey Jeremy

Thanks for your post it helped me a ton. Just to (perhaps) help others: I also had this two ClassDefNotFoundErrors (WsLogRecord / ClusterMemberService). Cause for this was the Eclipse / RSA library for WebSphere 8.5 on the classpath. So try a clean EJB project without server reference and it will work. Adding the missing classes just causes this exception: This JNDI client configuration does not support java:app lookups.

Greetings
Joe