Saturday, March 21, 2009

Testing EJB out of application server - Part 1

Introduction



Earlier I had a couple of projects that used Hibernate as a persistence framework of choice. Hibernate provides rich API, that is actually a superset of what EJB3 provides, and at the same time it gives an opportunity to test core application modules and services without application server running with the application deployed on it. But using Hibernate for persistence we at the same time stay far from technologies that are standards de jure nowadays. The question is whether it is possible to follow industrial standards and still have things implemented as simple as possible. In this post I'll try to show on example the way how EJB can be used as the primary technology for persistence, bean management and Web services implementation. The most important thing it covers is testing EJBs without deploying developing application onto application server.

EJB spec offers standard way to develop enterprise level application that can be deployed on any application server, that follows the standard, but does not say how we can easily test EJBs. And usually the first what developers decide is to test them remotely on application server. The approach perhaps is the most significant time consumer and requires great experience in EJB development - each redeployment may cost too much in comparison to how much time spent on writing production code that is tested.

Fortunately there is a solution (of course there is always something we can do) - "OpenEJB offers an "embeddable and lightweight EJB 3.0 implementation that can be used as a standalone server or embedded into Tomcat, JUnit, TestNG, Eclipse, IntelliJ, Maven, Ant, and any IDE or application. OpenEJB is included in Apache Geronimo, IBM WebSphere Application Server CE, and Apple's WebObjects." - the title page states.
So, in this post we will focus on using EJB - entity and session beans, and Web services - and testing the application without application server. Since JBoss is a widely used application server, it is evident why it was chosen for development, also evident that there is Hibernate behind the scene. Next time I'm going to focus on integration with Spring framework, so stay tuned..

The price



First of all, you should consider whether functionality Hibernate provides, that is not accessible in EJB, is critical or not for your application. For example, Hibernate Criteria API, perhaps the most significant feature, can be helpful in organizing quick and efficient search on multiple criterions. Without this feature StringBuilder with multiple if/else constructs become our best friends and the worst enemies. Though EntityManager can be cast to HibernateEntityManager to access Hibernate internals, the code will not look so perfect and clear anymore as it was when the Earth had been created. Also EJB needs another approaches to solve familiar problems, like re-implementing OpenSessionInView (Hibernate solution can be found here.

Example application



Domain tier


Let's focus on a simple Internet shop application that sells products and tracks each product purchase in database. For mapping entities to database tables JPA annotations are used. Sure, Hibernate specific annotations, like @Index, still can be used, especially if you need to generate database schema script. Let's just keep things simpler.

@Entity
@Table(name = "product")
@SequenceGenerator(name = "product_generator", sequenceName = "product_seq")
public class Product {
@Id
@Column(name = "id")
@GeneratedValue(generator = "product_generator")
private Long id;

@Column(name = "code", nullable = false)
private String code;

@OneToMany(mappedBy = "product", fetch = FetchType.LAZY)
private Set<Purchase> purchases = new HashSet<Purchase>();

// ...
}

@Entity
@Table(name = "purchase")
@SequenceGenerator(name = "purchase_generator", sequenceName = "purchase_seq")
public class Purchase {
@Id
@Column(name = "id")
@GeneratedValue(generator = "purchase_generator")
private Long id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "product_id", nullable = false)
private Product product;

@Column(name = "payment_date", nullable = false)
private Date paymentDate;

// ...
}


No magic here - things look really familiar.

Data access tier



DAO beans are simple as well, and rely on EJB3 interfaces:

public interface DAO<T> {
void save(T obj);

T get(Long id);
}

@Local
public interface ProductDAO extends DAO<Product> {
}

public abstract class AbstractDAO<T> implements DAO<T> {
@PersistenceContext(unitName = "ishopPU")
private EntityManager entityManager;

public void save(T obj) {
entityManager.persist(obj);
}

// other methods
}

@Stateless(name = "productDAO")
public class ProductDAOImpl extends AbstractDAO<Product> implements ProductDAO {
// ProductDAO interface methods
}


@Local marks business interface for DAO and @Stateless controls DAO implementation beans management by EJB container. Also AbstractDAO takes reference to the entity manager for the ishopPU persistence unit, configured in persistence.xml configuration file. Persistence unit name is required when we have more than one unit listed in this file.

Service tier



Stateless session beans that provide business methods to clients, like Web services and UI, may look as follows:

@Local
public interface ProductService {
// business methods
}

@TransactionManagement
@Stateless(name = "productService", mappedName = "productService")
public class ProductServiceImpl implements ProductService {
@EJB
private ProductDAO productDAO;

@TransactionAttribute
public void save(Product product) {
productDAO.save(product);
}

@TransactionAttribute
public Product getProduct(Long id) {
return productDAO.get(id);
}

@TransactionAttribute
public Purchase purchase(Long productId, Date paymentDate) throws BusinessException {
// implementation
}
}


Business method purchase() throws BusinessException that is annotated with @ApplicationException on class level. I have to say that Spring transaction management API is much cleaner and allows to control more aspects, like isolation level, read-only option and commit/rollback behavior depending on thrown exception type, on method and not on exception class level. That's one more reason you may consider it's use in the project.

Web service



Web service looks the same way as business service except implementation class is annotated with JBoss specific annotation to specify concrete URL and JNDI context name we want to bind the service to:

@WebService(name = "ishop",
targetNamespace = "http://ishop.org",
serviceName = "productWebService")
@WebContext(contextRoot = "/ishop-ws", urlPattern = "/productWebService")
@Remote(ProductWebService.class)
@RemoteBinding(jndiBinding = "productWebServiceRemote")
@Stateless
public class ProductWebServiceImpl implements ProductWebService {
@WebMethod
public ProductBean getProduct(@WebParam(name = "id") long id) {
// implementation
}
}


Production code configuration



Persistence unit configuration is also plain, but you should notice that ishopPU unit is configured to be used with Hibernate as persistence provider (org.hibernate.ejb.HibernatePersistence) and properties listed in properties XML element go directly to the provider. The most important is that we specify dialect to use - Oracle (org.hibernate.dialect.OracleDialect) and other options like log SQL statements executed (set to true) and automatic database schema generation (set to none).

<persistence 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_1_0.xsd"
version="1.0">

<persistence-unit name="ishopPU" transaction-type="JTA">
<provider>org.hibernate.ejb.HibernatePersistence</provider>

<!-- Data source name -->
<jta-data-source>java:/ishopDS</jta-data-source>

<!-- Persistent classes -->
<class>org.ishop.domain.Product</class>
<class>org.ishop.domain.Purchase</class>

<properties>
<!-- Hibernate specific properties -->
<property name="hibernate.dialect" value="org.hibernate.dialect.OracleDialect"/>
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.hbm2ddl.auto" value="none"/>
</properties>
</persistence-unit>
</persistence>


Properties specified for Hibernate will be used in production. For testing we will use HSQLDB as we do not need to access any pre-existing data, and will generate it for each test. Below is discussed how to change these properties (at least database dialect), for testing environment.

Testing code configuration



Let's consider what we do to test all the stuff out of application server. Assume we want to test DAO, business logic and call Web service remotely to check that application was deployed successfully in production environment, and works well.

  1. At first, to keep session beans JNDI names consistent, we configure OpenEJB descriptor file openejb-jar.xml:

    <openejb-jar xmlns="http://www.openejb.org/openejb-jar/1.1">
    <properties>
    openejb.deploymentId.format = {ejbName}
    openejb.jndiname.format = {deploymentId}{interfaceType.annotationName}
    </properties>
    </openejb-jar>


    so each session bean JNDI name consists of two components - bean name and interface type name (either Local or Remote).

  2. JNDI Configuration file for testing environment - jndi.properties - initializes JNDI context factory for OpenEJB:

    java.naming.factory.initial=org.apache.openejb.client.LocalInitialContextFactory


  3. The last what we do is >re-configure persistence context, in persistence.xml file to switch to testing data source:

    java.naming.factory.initial=org.apache.openejb.client.LocalInitialContextFactory

    # Data source configuration
    ishopPU=new://Resource?type=DataSource
    ishopPU.JdbcDriver=org.hsqldb.jdbcDriver
    ishopPU.JdbcUrl=jdbc:hsqldb:localdb/localdb

    # These properties override specified in persistence.xml
    ishopPU.hibernate.dialect=org.hibernate.dialect.HSQLDialect
    ishopPU.hibernate.hbm2ddl.auto=create-drop


    Now in unit tests OpenEJB uses HSQLDB for persistence, with correct dialect and other Hibernate properties.
    The syntax for property is following:
    <PERSISTENCE UNIT NAME>.<HIBERNATE PROPERTY NAME>=<PROPERTY VALUE>



Writing testing code



I won't be explaining here why I chose TestNG - you may easily switch to jUnit if you want to. Let's create abstract base class for tests that need access to database, and add setUpDatabase() method that initializes local database before class test methods run, loading configuration from persistence.properties file. Since all session beans are published by the container in JNDI, to access them we create an utility method lookup() that returns any bean from JNDI contaxt, by bean JNDI name:

public abstract class AbstractPersistenceTest {
private Context context;

@BeforeClass(groups = "database")
public final void setUpDatabase() throws NamingException, IOException {
Properties props = new Properties();
props.load(AbstractPersistenceTest.class.getResourceAsStream("/persistence.properties"));
context = new InitialContext(props);
}

@SuppressWarnings("unchecked")
protected final <T> T lookup(String name) throws NamingException {
return (T) context.lookup(name);
}
}


Below is a simple business service test class:

public class ProductServiceTest extends AbstractPersistenceTest {
private ProductService productService;

private Product product;

@BeforeClass(groups = "database")
public void setUpService() throws NamingException {
productService = lookup("productServiceLocal");
}

@BeforeMethod(groups = "database")
public void createProduct() {
product = new Product();
product.setCode("ABCDEF");
}

@Test(groups = "database")
public void testSave() {
productService.save(product);

Assert.assertNotNull(product.getId());
}

// other tests
}


Before class test methods run, setUpService() and createProduct() methods are invoked to get reference to ProductService bean and create test Product.

Lazy initialization problem


The problem is caused by dynamic proxies Hibernate generates instead of loading business entities or collections from database. Usually that is FetchType.LAZY specified for fetch attribute in @OneToMany and similar mapping configuration annotations. Solution existing for enterprise applications using Hibernate for persistence is opening Hibernate Session for HTTP request and keeping it open till request processing is complete. Entities loaded in any transaction stay attached to HTTP session so Web page may access lazily loaded collections and entities without LazyInitializationException being thrown.
Assume that in EJB we have no access to Hibernate internals, but there are choices how we may solve the problem.

  • Access lazily loaded objects in transaction.

    For this purpose we access all our business methods in a single transaction. For example, annotate Web service bean with @TransactionManagement (that is actually done by EJB container by default) configured to use container transaction management (TransactionManagementType.CONTAINER):

    @TransactionManagement
    // other annotations suppresses
    public class ProductWebServiceImpl implements ProductWebService {
    // implementation
    }

    This will grant that Web service and business methods are executed in the same transaction. Not the best solution, expecially because it damages business methods transaction boundaries, causing unexpected errors and probably missing results. It may not work for business methods with propagation level REQUIRES_NEW, and certainly will cause problems with NEVER.
  • Template method.

    The idea is to wrap multiple business methods and execute them in a single transaction boundary. Seems there is no difference with the previous approach, but we can use EXTENDED persistence context mode when persistent entities kept attached to stateful bean till the moment the bean is removed from context:

    @Local
    interface OpenSessionInViewCallback {
    void invoke(Runnable r);

    void remove();
    }

    @Stateful(name = "openSessionInViewCallback")
    public class OpenSessionInViewCallbackImpl implements OpenSessionInViewCallback {
    @PersistenceContext(type = PersistenceContextType.EXTENDED)
    private EntityManager entityManager;

    @Init
    public void invoke(Runnable r) {
    r.run();
    }

    @Remove
    public void remove() {
    }
    }


    Template class gets reference to the stateful bean from JNDI context and removes bean from context when callback method is executed:

    public final class OpenSessionInViewTemplate {
    public static void invoke(Runnable r) {
    OpenSessionInViewCallback callback = null;
    try {
    // make lookup for callback bean
    Context context = new InitialContext();
    callback = (OpenSessionInViewCallback) context.lookup("openSessionInViewCallbackLocal");

    // invoke runnable
    callback.invoke(r);
    } catch (NamingException e) {
    throw new RuntimeException(e);
    } finally {
    if (callback != null) {
    // remove bean
    callback.remove();
    }
    }
    }
    }


    Any business methods calls can be done in run() method of Runnable without
    LazyInitializationException thrown and affecting business methods transactions boundaries. Unit tests may need to use template method approach.
  • Interceptor.

    The solution is similar to the previous one but the calling code will look much simple now:

    @Remote(ProductWebService.class)
    @Stateless(name = "productWebService")
    @TransactionManagement(TransactionManagementType.BEAN)
    @Interceptors(OpenSessionInViewInterceptor.class)
    public class ProductWebServiceImpl implements ProductWebService {
    @EJB
    private ProductService productService;

    @WebMethod
    public ProductBean getProduct(@WebParam(name = "id") long id) {
    ProductBean bean = null;

    Product product = productService.getProduct(id);
    if (product != null) {
    bean = new ProductBean();
    bean.setId(product.getId());
    bean.setCode(product.getCode());
    Date lastPaid = null;
    for (Purchase purchase : product.getPurchases()) {
    if (lastPaid == null || lastPaid.after(purchase.getPaymentDate())) {
    lastPaid = purchase.getPaymentDate();
    }
    }
    bean.setLastPaid(lastPaid);
    }
    return bean;
    }
    }


    In this example, thankfully to OpenSessionInViewInterceptor interceptor, invocation of getPurchases() will not cause error, at the same time any transaction boundary is not affected as well.


Deploying application and running tests



After application has been built with Maven:
mvn package


and everything was fine (pray first), you need to copy ishop-ear/src/main/resources/ishop-ds.xml to JBoss server/<CONFIGURATION>/deploy directory and configure according to your environment. Ensure that database driver JAR file is in JBoss application server class path. Execute schema.sql and sample.sql scripts to populate database with required tables and add sample data (databases other than Oracle may need to have them tuned). Copy ishop-ear/target/ishop-ear-1.0.ear to the same application server deployment directory. Start JBoss for that configuration:
$JBOSS_HOME/bin/run.sh -c <CONFIGURATION>


To check Web services change to ishop-wsclient/target and launch application like follows:
java -jar ishop-wsclient-1.0.jar 1

where 1 is the first product ID in production database. To have Web service client working ensure that application server has been started and available on port 8080 on local host (default settings).

Conclusion


Though following enterprise standards sometimes is a difficult decision and has its consequences, in many cases switching to EJB may increase application portability making, in the future, migration to another platform more transparent, reducing costs spent on application development and further support. At the same time, it is not so hard, as we may imagine at first, to develop and especially test enterprise application, based on EJB, out of application server. And OpenEJB helps with this.

Source code


ishop.ejb-testng.zip

Thank you.

5 comments:

  1. Great Article! If you could add an 'OpenEJB' label that'd be fantastic. Will definitely post this to the users@openejb.a.o list.

    ReplyDelete
  2. I know its very old post. But just wanted to check where you got the jndi lookup from for "productserviceLocal"? I do not see any references to it.

    ReplyDelete
  3. Note that ProductService interface is annotated with @Local - this gives ProductServiceLocal name in the JNDI context. Similarly, it will be ProductServiceRemote for @Remote-s.

    ReplyDelete
  4. One more thing to clarify. Actual name is "productServiceLocal" which is formed from bean name (see ProductServiceImpl for @Stateless(name = "productService")) and interface name (@Local or @Remote). I.e. "productService" + "Local" in your case.

    ReplyDelete