Thursday, February 15, 2007

Unit Testing JPA (EJB3) Code With With an EntityListener.

My goal was to verify that some objects were pulled from the EJB3 cache (ie, that I was actually getting cache hits) using unit tests. I did not see any way to get statistical information or cache information using the EJB3 API. However, I did read the about EntityListeners annotation.

@Entity
@EntityListeners( { DomainObjectListener.class })
public class DomainObject  {

The EntityListeners annotation allows you to specify a class which can track EJB3 events such as PrePersist and PostPersist. I developed some simple ObjectEvent classes to track, via a list, which EJB3 events happen. The following code shows my base class and one subclass. There is one subclass for each EJB3 event.

package org.domain;

abstract public class ObjectEvent {
   private IDomainObject domainObject = null;
   public ObjectEvent(final IDomainObject _domainObject) {
       super();
       this.domainObject = _domainObject;
   }
   public IDomainObject getDomainObject() {
       return this.domainObject;
   }
}

package org.domain;

public class PrePersistObjectEvent extends ObjectEvent {
   public PrePersistObjectEvent(final IDomainObject _domainObject) {
       super(_domainObject);
   }
}

I'm sure that you can create the rest of the ObjectEvent subclasses. The DomainObjectListener class holds a list of events and watched classes. The list of watched classes is needed because all of my persisted objects are subclasses of DomainObject. Here is the listener class:

package org.domain;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.PostLoad;
import javax.persistence.PostPersist;
import javax.persistence.PostRemove;
import javax.persistence.PostUpdate;
import javax.persistence.PrePersist;
import javax.persistence.PreRemove;
import javax.persistence.PreUpdate;

public class DomainObjectListener {

   // private static final Logger logger = Logger.getLogger(DomainObjectListener.class.getSimpleName());

   final static private List watchedClasses = new ArrayList();

   @SuppressWarnings("unchecked")
   public static void addWatchedClass(Class clazz) {
       watchedClasses.add(clazz);
   }

   public static void clear() {
       watchedClasses.clear();
       events.clear();
   }

   final static private List events = new ArrayList();

   private static void addEvent(final ObjectEvent objectEvent) {
       events.add(objectEvent);
   }

   public static List getEvents() {
       return events;
   }
 
   public static int getNumberOfEvents() {
       return events.size();
   }
 
   public static boolean wasEventGenerated(final Class objectEventClazz) {
       boolean rv = false;
       for (ObjectEvent objectEvent : events) {
           if (objectEvent.getClass() == objectEventClazz) {
               rv = true;
               break;
           }
       }
       return rv;
   }

   public DomainObjectListener() {
       super();
   }

   @PrePersist
   public void prePersist(final IDomainObject item) {
       if (watchedClasses.contains(item.getClass())) {
           addEvent(new PrePersistObjectEvent(item));
       }
   }

   @PreRemove
   public void preRemove(final IDomainObject item) {
       if (watchedClasses.contains(item.getClass())) {
           addEvent(new PreRemoveObjectEvent(item));
       }
   }

   @PostPersist
   public void postPersist(final IDomainObject item) {
       if (watchedClasses.contains(item.getClass())) {
           addEvent(new PostPersistObjectEvent(item));
       }
   }

   @PostRemove
   public void postRemove(final IDomainObject item) {
       if (watchedClasses.contains(item.getClass())) {
           addEvent(new PostRemoveObjectEvent(item));
       }
   }

   @PreUpdate
   public void preUpdate(final IDomainObject item) {
       if (watchedClasses.contains(item.getClass())) {
           addEvent(new PreUpdateObjectEvent(item));
       }
   }

   @PostUpdate
   public void postUpdate(final IDomainObject item) {
       if (watchedClasses.contains(item.getClass())) {
           addEvent(new PostUpdateObjectEvent(item));
       }
   }

   @PostLoad
   public void postLoad(final IDomainObject item) {
       if (watchedClasses.contains(item.getClass())) {
           addEvent(new PostLoadObjectEvent(item));
       }
   }

}

The unit tests to use the entity listener might look like this:

    public void test_new_object_pre_and_post_events_only() {
       DomainObjectListener.addWatchedClass(ContactCountry.class);
       addContactCountry("US", "United States of America");
       assertEquals(2, DomainObjectListener.getNumberOfEvents());
       assertTrue(DomainObjectListener.wasEventGenerated(PrePersistObjectEvent.class));
       assertTrue(DomainObjectListener.wasEventGenerated(PostPersistObjectEvent.class));
       DomainObjectListener.clear();
   }

I'm not happy with my solution because the entity listener class is assigned via an annotation which means that it is present in all enviroments - dev, test, and production - unless the DomainObject class is changed and recompiled during deployment. I'll keep looking for a better solution.

Wednesday, February 07, 2007

portalDatasource Used By JBoss Seam Portal Example

While the JBoss Portal uses a datasource called PortalDS then JBoss Seam Portal example uses a datasource called portalDatasource which causes an datasource not found exception when deployed. You can update the hibernate configuration file to resolve the issue.

Tuesday, February 06, 2007

Java Jar Files Used for JPA, PojoCache, and SEAM

These are the Jar files that I am using for my JPA, PojoCache, and SEAM development. I'm listing them here because it took me a bit of experimentation to find them. You can use Eclipse's User Library import feature. <?xml version="1.0" encoding="UTF-8" standalone="no"?> <eclipse-userlibraries version="2"> <library name="JPA" systemlibrary="false"> <archive path="C:/data/support/jboss-4.0.5.GA/server/default/lib/cglib.jar"/> <archive path="C:/data/support/jboss-4.0.5.GA/server/default/deploy/jbossweb-tomcat55.sar/jsf-libs/commons-collections.jar"/> <archive path="C:/data/support/jboss-4.0.5.GA/server/default/lib/dom4j.jar"/> <archive path="C:/data/support/jboss-4.0.5.GA/server/default/lib/ejb3-persistence.jar"/> <archive path="C:/data/support/jboss-4.0.5.GA/server/default/deploy/jboss-portal.sar/lib/ehcache.jar"/> <archive path="C:/data/support/jboss-4.0.5.GA/server/default/lib/hibernate-annotations.jar"/> <archive path="C:/data/support/jboss-4.0.5.GA/server/default/lib/hibernate-entitymanager.jar"/> <archive path="C:/data/support/jboss-4.0.5.GA/server/default/lib/hibernate3.jar"/> <archive path="C:/data/support/jboss-4.0.5.GA/lib/jboss-common.jar"/> <archive path="C:/data/support/jboss-4.0.5.GA/server/default/lib/javassist.jar"/> <archive path="C:/data/support/jboss-4.0.5.GA/server/default/lib/jboss-j2ee.jar"/> <archive path="C:/data/support/jboss-4.0.5.GA/server/default/lib/antlr-2.7.6.jar"/> </library> <library name="JPA_TESTING" systemlibrary="false"> <archive path="C:/data/support/spring-framework-2.0.1/dist/spring-mock.jar"/> </library> <library name="JUNIT" systemlibrary="false"> <archive path="C:/data/support/junit4.1/junit-4.1.jar"/> </library> <library name="LOG4J" systemlibrary="false"> <archive path="C:/data/support/jboss-4.0.5.GA/server/default/lib/log4j.jar"/> <archive path="C:/data/support/jboss-4.0.5.GA/server/default/lib/commons-logging.jar"/> </library> <library name="ORACLE" systemlibrary="false"> <archive path="C:/oracle/product/10.2.0/db_1/jdbc/lib/classes12.jar"/> </library> <library name="POJO_CACHE" systemlibrary="false"> <archive path="C:/data/support/jboss-4.0.5.GA/server/default/lib/jboss-cache.jar"/> <archive path="C:/data/support/jboss-4.0.5.GA/lib/jboss-system.jar"/> <archive path="C:/data/support/jboss-4.0.5.GA/lib/jboss-jmx.jar"/> <archive path="C:/data/support/JBossCache-1.4.1.GA/lib-50/jboss-cache-jdk50.jar"/> </library> <library name="SEAM" systemlibrary="false"> <archive path="C:/data/support/jboss-seam-1.1.5.GA/jboss-seam.jar"/> <archive path="C:/data/support/jboss-seam-1.1.5.GA/lib/myfaces-api-1.1.4.jar"/> </library> <library name="SPRING_FRAMEWORK" systemlibrary="false"> <archive path="C:/data/support/spring-framework-2.0.1/dist/spring.jar"/> </library> <library name="TESTNG" systemlibrary="false"> <archive path="C:/data/support/jboss-seam-1.1.5.GA/lib/testng-4.5.1-jdk15.jar"/> </library> </eclipse-userlibraries>

Monday, February 05, 2007

Database Sequences Needed for JBoss Portal & Oracle

When I just installed JBoss it was configured to use Oracle. However, JBoss Portal did not work immediately. I needed to perform the following steps: 1. Copy the Oracle JDBC jar file to [JBOSS.HOME]/server/default/lib. 2. Create database sequences: create sequence hibernate_sequence; create sequence portal_seq; create sequence sec_seq; create sequence instance_seq; create sequence portlet_seq; create sequence user_seq; I'm not sure when Hibernate didn't create these sequences automatically. I may be an issue specific to Oracle since I don't recall the same problem when using other databases. UPDATE: JBoss Portal did not automatically create the sequences because I did not copy the Oracle JDBC jar file into the JBOSS.HOME/lib directory immediately after the installation process completed.