Fault Loading Example for OpenEJB3
I finally got the fault loading example to work. I'll post the code here in the list so that other users can look at it in the future.
First, all of this code uses entity beans from the alpha-05 release of CORM (my Open Source commercial O/R model project for Java), which can be downloaded from http://java.eremite.org/bin/view/CORM/CORM_Project_Downloads?rev=25
The example below shows how to use fault loading in an extended session context in order to simplify testing the cascade relationships for multiple collection-valued references inside of a single persistent entity bean. My entity bean is an instance of org.eremite.corm.party.Party (sorry, no public SVN yet, but I'll paste some of the code below).
The major pieces I needed to create for this example were #1: The Context factory I use to make sure all of my test cases don't create a new database (this saves time), #2: The test case, #3: A Stateful Session bean "bean manager" that contained an entity manager inside of an EXTENDED persistence context, and its local interface (not shown), #4: the peristence.xml file, which I place in my src/test/resources directory for mvn, #5: nachos, to keep things calm down below while I worked. I'll show the code for Party and first four pieces below. You will have to find your own nachos. Also, all of the code listed below is AFL 3.0 licensed, so have at it.
Exhibit #0: The Party class:
package org.eremite.corm.party;
import org.eremite.corm.BaseArchetype;
import org.eremite.corm.party.address.AssociatedAddress;
import org.eremite.corm.party.relationship.Capability;
import org.eremite.corm.party.relationship.PartyRole;
import javax.persistence.*;
import java.util.*;
@Entity
public class Party extends BaseArchetype {
@OneToMany(cascade={
CascadeType.PERSIST,
CascadeType.MERGE,
CascadeType.REMOVE}, mappedBy="party")
private Set <AssociatedAddress> addresses =
new HashSet <AssociatedAddress> ();
@OneToMany(cascade={
CascadeType.PERSIST,
CascadeType.MERGE,
CascadeType.REMOVE})
private Set <Binding> bindings = new HashSet <Binding> ();
@OneToMany(cascade={
CascadeType.PERSIST,
CascadeType.MERGE,
CascadeType.REMOVE})
private Set <RegisteredIdentifier> registeredIds =
new HashSet <RegisteredIdentifier> ();
@OneToMany(cascade={
CascadeType.PERSIST,
CascadeType.MERGE,
CascadeType.REMOVE})
private Set <Capability> capabilities = new HashSet<Capability>();
@OneToMany(cascade={
CascadeType.PERSIST,
CascadeType.MERGE,
CascadeType.REMOVE}, mappedBy="party")
private Set <PartyRole> roles = new HashSet <PartyRole> ();
/* Default Zero-arg Constructor */
public Party() {}
public Set<AssociatedAddress> getAddresses() {
return addresses;
}
public void setAddresses(Set<AssociatedAddress> addresses) {
this.addresses = addresses;
}
public Set<Binding> getPreferences() {
return bindings;
}
public void setPreferences(Set<Binding> preferences) {
this.bindings = bindings;
}
public Set<RegisteredIdentifier> getRegisteredIdentifiers() {
return registeredIds;
}
public void setRegisteredIdentifiers(
Set<RegisteredIdentifier> registeredIds) {
this.registeredIds = registeredIds;
}
public Set<PartyRole> getPartyRoles() {
return roles;
}
public void setPartyRoles(Set<PartyRole> roles) {
this.roles = roles;
}
public Set<Capability> getCapabilities() {
return this.capabilities;
}
public void setCapabilities(Set<Capability> capabilities) {
this.capabilities = capabilities;
}
public void addPartyRole(PartyRole ... roles) {
getPartyRoles().addAll(Arrays.asList(roles));
}
public void addPartyRole(PartyRole role) {
getPartyRoles().add(role);
}
public void addRegisteredIdentifier(RegisteredIdentifier rid) {
getRegisteredIdentifiers().add(rid);
}
public void addRegisteredIdentifier(
RegisteredIdentifier ... rids) {
getRegisteredIdentifiers().addAll(
java.util.Arrays.asList(rids));
}
public void addPreference(Binding ... bindings) {
getPreferences().addAll(Arrays.asList(bindings));
}
public void addCapability(Capability ... capabilities) {
getCapabilities().addAll(Arrays.asList(capabilities));
}
public void addCapability(Capability capability) {
getCapabilities().add(capability);
}
public void addAddress(AssociatedAddress ... addresses) {
getAddresses().addAll(Arrays.asList(addresses));
}
public void addAddress(AssociatedAddress address) {
getAddresses().add(address);
}
}
Note that the Party class has 5 collection-valued references to other entities. For brevity I'll not show the definitions of those entities here. Also note that Party and all other entities in my project descend from the same MappedSuperclass, "Archetype". This helps me considerably during testing and other areas. Archetype has a name, description and ID field, and is not shown here.
Exhibit #1: The Context Factory
package org.eremite.corm.party.testutil;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.util.Properties;
public class ContextFactory {
private Context context;
private static ContextFactory instance;
public static Context reference() {
if(instance == null) instance = new ContextFactory();
return instance.getContext();
}
private ContextFactory() {
Properties p = new Properties();
p.put(Context.INITIAL_CONTEXT_FACTORY,
"org.apache.openejb.client.LocalInitialContextFactory");
p.put("partyDB", "new://Resource?type=DataSource");
p.put("partyDB.JdbcDriver", "org.hsqldb.jdbcDriver");
p.put("partyDB.JdbcUrl", "jdbc:hsqldb:mem:partydb");
p.put("partyDBUnmanaged", "new://Resource?type=DataSource");
p.put("partyDBUnmanaged.JdbcDriver", "org.hsqldb.jdbcDriver");
p.put("partyDBUnmanaged.JdbcUrl", "jdbc:hsqldb:mem:partydb");
p.put("partyDBUnmanaged.JtaManaged", "false");
try {
context = new InitialContext(p);
} catch (NamingException e) {
e.printStackTrace();
}
}
private Context getContext() {
return this.context;
}
}
The ContextFactory is something I whipped up to make sure that my different TestCases didn't all recreate the InitialContext. The addition of this class helped me shave considerable time off of my maven build, and therefore has been useful for speeding up development.
Exhibit #2: The Test Case
package org.eremite.corm.party;
import junit.framework.TestCase;
import org.eremite.corm.Archetype;
import org.eremite.corm.party.address.Address;
import org.eremite.corm.party.address.AssociatedAddress;
import org.eremite.corm.party.relationship.Capability;
import org.eremite.corm.party.relationship.PartyRole;
import org.eremite.corm.party.relationship.PartyRoleType;
import org.eremite.corm.party.testutil.BeanManager;
import org.eremite.corm.party.testutil.ComponentFactory;
import org.eremite.corm.party.testutil.ExtendedBeanManager;
import javax.naming.Context;
import java.util.Iterator;
import java.util.Set;
public class PartyCascade02Test extends TestCase {
protected ExtendedBeanManager mgr;
protected BeanManager db;
protected ComponentFactory one;
protected Context context;
public void setUp() throws Exception {
super.setUp();
context = org.eremite.corm.party.testutil.
ContextFactory.reference();
mgr = (ExtendedBeanManager) context.lookup(
"ExtendedBeanManagerLocal");
db = (BeanManager) context.lookup("BeanManagerLocal");
one = new ComponentFactory();
}
public void tearDown() throws Exception {
super.tearDown();
}
public void testFaultLoading() {
Party p1 = new Party();
PartyRole<Party> initialRole =
new PartyRole<Party>(new PartyRoleType(), p1);
Capability initialCapability = new Capability();
Binding initialPreference = new Binding();
RegisteredIdentifier initialRegID = new RegisteredIdentifier();
AssociatedAddress initialAA =
new AssociatedAddress(p1, new Address());
p1.addCapability(initialCapability);
p1.addPreference(initialPreference);
p1.addRegisteredIdentifier(initialRegID);
assertNotNull(p1.getAddresses());
assertNotNull(p1.getCapabilities());
assertNotNull(p1.getPartyRoles());
assertNotNull(p1.getPreferences());
assertNotNull(p1.getRegisteredIdentifiers());
assertTrue(p1.getAddresses().contains(initialAA));
assertTrue(p1.getCapabilities().contains(initialCapability));
assertTrue(p1.getPartyRoles().contains(initialRole));
assertTrue(p1.getPreferences().contains(initialPreference));
assertTrue(p1.getRegisteredIdentifiers().contains(initialRegID));
// First unit of work -- cascading persist
mgr.persist(p1);
long id = p1.getID();
assertTrue(id != 0);
// Second unit of work -- get party by ID
Party p2 = mgr.getPartyByID(id);
assertNotNull(p2);
assertNotNull(p2.getAddresses());
assertNotNull(p2.getCapabilities());
assertNotNull(p2.getPreferences());
assertNotNull(p2.getPartyRoles());
assertNotNull(p2.getRegisteredIdentifiers());
assertTrue(p2.getAddresses().size() > 0);
assertTrue(p2.getCapabilities().size() > 0);
assertTrue(p2.getPreferences().size() > 0);
assertTrue(p2.getPartyRoles().size() > 0);
assertTrue(p2.getRegisteredIdentifiers().size() > 0);
Set<AssociatedAddress> addresses = p2.getAddresses();
Set<Capability> capabilities = p2.getCapabilities();
Set<Binding> preferences = p2.getPreferences();
Set<PartyRole> partyRoles = p2.getPartyRoles();
Set<RegisteredIdentifier> regIDs = p2.getRegisteredIdentifiers();
assertNotNull(addresses);
assertNotNull(capabilities);
assertNotNull(preferences);
assertNotNull(partyRoles);
assertNotNull(regIDs);
assertTrue(hasArchetypeWithID(addresses, initialAA.getID()));
assertTrue(hasArchetypeWithID(capabilities, initialCapability.getID()));
assertTrue(hasArchetypeWithID(preferences, initialPreference.getID()));
assertTrue(hasArchetypeWithID(partyRoles, initialRole.getID()));
assertTrue(hasArchetypeWithID(regIDs, initialRegID.getID()));
p2.setName("preMerge");
Archetype address2, cap2, pref2, role2, regID2,
address3, cap3, pref3, role3, regID3;
address2 = getByID(addresses, initialAA.getID());
cap2 = getByID(capabilities, initialCapability.getID());
pref2 = getByID(preferences, initialPreference.getID());
role2 = getByID(partyRoles, initialRole.getID());
regID2 = getByID(regIDs, initialRegID.getID());
address2.setName("preMerge address");
cap2.setName("preMerge capability");
pref2.setName("preMerge preference");
role2.setName("preMerge partyRole");
regID2.setName("preMerge regID");
// Third unit of work -- cascading merges
mgr.merge(p2);
// Fourth unit of work -- get party by ID
Party p3 = mgr.getPartyByID(id);
assertNotNull(p3);
assertNotNull(p3.getAddresses());
assertNotNull(p3.getCapabilities());
assertNotNull(p3.getPreferences());
assertNotNull(p3.getPartyRoles());
assertNotNull(p3.getRegisteredIdentifiers());
assertTrue(p3.getAddresses().size() > 0);
assertTrue(p3.getCapabilities().size() > 0);
assertTrue(p3.getPreferences().size() > 0);
assertTrue(p3.getPartyRoles().size() > 0);
assertTrue(p3.getRegisteredIdentifiers().size() > 0);
Set<AssociatedAddress> addresses2 = p3.getAddresses();
Set<Capability> capabilities2 = p3.getCapabilities();
Set<Binding> preferences2 = p3.getPreferences();
Set<PartyRole> partyRoles2 = p3.getPartyRoles();
Set<RegisteredIdentifier> regIDs2 = p3.getRegisteredIdentifiers();
assertNotNull(addresses2);
assertNotNull(capabilities2);
assertNotNull(preferences2);
assertNotNull(partyRoles2);
assertNotNull(regIDs2);
assertTrue(hasArchetypeWithID(addresses2, initialAA.getID()));
assertTrue(hasArchetypeWithID(capabilities2, initialCapability.getID()));
assertTrue(hasArchetypeWithID(preferences2, initialPreference.getID()));
assertTrue(hasArchetypeWithID(partyRoles2, initialRole.getID()));
assertTrue(hasArchetypeWithID(regIDs2, initialRegID.getID()));
address3 = getByID(addresses2, initialAA.getID());
cap3 = getByID(capabilities2, initialCapability.getID());
pref3 = getByID(preferences2, initialPreference.getID());
role3 = getByID(partyRoles2, initialRole.getID());
regID3 = getByID(regIDs2, initialRegID.getID());
assertEquals(p2.getName(), p3.getName());
assertEquals(address2.getName(), address3.getName());
assertEquals(cap2.getName(), cap3.getName());
assertEquals(pref2.getName(), pref3.getName());
assertEquals(role2.getName(), role3.getName());
assertEquals(regID2.getName(), regID3.getName());
// 5th unit of work -- cascading remove
mgr.remove(p3);
// 6th - 13th units of work -- verification
assertEquals(0, mgr.sizeOf("Party"));
assertEquals(0, mgr.sizeOf("AssociatedAddress"));
assertEquals(0, mgr.sizeOf("Capability"));
assertEquals(0, mgr.sizeOf("Binding"));
assertEquals(0, mgr.sizeOf("PartyRole"));
assertEquals(0, mgr.sizeOf("RegisteredIdentifier"));
assertEquals(1, mgr.sizeOf("PartyRoleType"));
assertEquals(1, mgr.sizeOf("Address"));
}
private static void traceln(Object ... o) {
for(Object item : o) System.out.println(item);
}
private boolean hasArchetypeWithID(Set s, long ID) {
Iterator<Archetype> iter = (Iterator<Archetype>)s.iterator();
while(iter.hasNext()) {
Archetype a = iter.next();
if(a.getID() == ID) return true;
}
return false;
}
private Archetype getByID(Set s, long ID) {
Iterator<Archetype> iter = (Iterator<Archetype>)s.iterator();
while(iter.hasNext()) {
Archetype a = iter.next();
if(a.getID() == ID) return a;
}
fail("There was no archetype with ID==" + ID + " in the set.");
return null;
}
}
Note that this was the test case for a class with 5 collection valued references. My goal was to reduce the number of times I needed to go to the database while performing tests, so as to speed up the testing phase of my maven build and thereby speed up development. Major gains were between the 2nd and 6th units of work, as in previous versions I was unable to use fault loading (for reasons which will be discussed below).
Exhibit #3: The Stateful Session Bean
package org.eremite.corm.party.testutil;
import org.eremite.corm.Archetype;
import org.eremite.corm.party.Party;
import javax.ejb.Stateful;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import javax.persistence.Query;
import java.util.List;
@Stateful(name="ExtendedBeanManager")
public class ExtendedBeanManagerImpl implements ExtendedBeanManager {
@PersistenceContext(
unitName = "party-test-unit",
type = PersistenceContextType.EXTENDED)
private EntityManager em;
public Party getPartyByID(long ID) {
Query q = em.createQuery("SELECT a FROM Party as a WHERE a.ID=" + ID);
return (Party) q.getSingleResult();
}
public long persist(Archetype a) {
em.persist(a);
return a.getID();
}
public void merge(Archetype a) {
em.merge(a);
}
public void remove(Archetype a) {
em.remove(a);
em.flush();
}
public int sizeOf(String table) {
Query q = em.createQuery("SELECT a FROM " + table + " as a");
return q.getResultList().size();
}
}
Exhibit #4: The persistence.xml file
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">
<persistence-unit name="party-test-unit">
<jta-data-source>java:openejb/Resource/partyDB</jta-data-source>
<non-jta-data-source>java:openejb/Resource/partyDBUnmanaged</non-jta-data-source>
<class>org.eremite.corm.BaseArchetype</class>
<class>org.eremite.corm.party.address.Address</class>
<class>org.eremite.corm.party.address.AssociatedAddress</class>
<class>org.eremite.corm.party.address.EmailAddress</class>
<class>org.eremite.corm.party.address.GeographicAddress</class>
<class>org.eremite.corm.party.address.ISOCountryCode</class>
<class>org.eremite.corm.party.address.Locale</class>
<class>org.eremite.corm.party.address.TelecomAddress</class>
<class>org.eremite.corm.party.address.WebPageAddress</class>
<class>org.eremite.corm.party.relationship.AssignedResponsibility</class>
<class>org.eremite.corm.party.relationship.Capability</class>
<class>org.eremite.corm.party.relationship.PartyRelationship</class>
<class>org.eremite.corm.party.relationship.PartyRelationshipType</class>
<class>org.eremite.corm.party.relationship.PartyRole</class>
<class>org.eremite.corm.party.relationship.PartyRoleType</class>
<class>org.eremite.corm.party.relationship.Responsibility</class>
<class>org.eremite.corm.party.Organization</class>
<class>org.eremite.corm.party.OrganizationName</class>
<class>org.eremite.corm.party.Party</class>
<class>org.eremite.corm.party.PartySignature</class>
<class>org.eremite.corm.party.Person</class>
<class>org.eremite.corm.party.PersonName</class>
<class>org.eremite.corm.party.Binding</class>
<class>org.eremite.corm.party.BindingValue</class>
<class>org.eremite.corm.party.BindingType</class>
<class>org.eremite.corm.party.RegisteredIdentifier</class>
<properties>
<property name="openjpa.jdbc.SynchronizeMappings" value="buildSchema(ForeignKeys=true, SchemaAction='add,deleteTableContents')"/>
<!--<property name="openjpa.Log" value="SQL=TRACE, Query=TRACE"/>-->
</properties>
</persistence-unit>
</persistence>
I did not change my persistence.xml file at all from the one I normally use for testing.
My bean manager for this example was a complete rewrite of my former one. The former bean manager was a Stateless Session Bean with an EntityManager that operated in a TRANSACTION typed session context. I do not know whether it is possible to use fault loading from a Stateless Session Bean and from an EntityManager that uses a TRANSACTION typed session context. However, I can confirm that the technique DOES work if one uses a Stateful Session Bean with an EM that operates in an EXTENDED typed session context. I will now go through my cascade tests and update them to use this new mechanism, and then will rinse and repeat for the rest of the modules in my project.
The overall result includes fewer trips to the database, simpler test cases, forward progress on my project, and a much happier developer.
