Monday, February 15, 2010

Persisting Qi4j Transient Composites using JPA

I've been trying to use Qi4j in a "lightweight mode", while still retaining the JPA (Java Persistence API) programming model. I haven't been successful yet.

Qi4j is a framework for domain driven development aka composite-oriented programming in Java. If that's still not enough buzzwords for you, Qi4j allows you to implement Data-Context-Interaction (DCI) architecture the same way Spring Web MVC allows you to implement MVC.

Fast forward to the current results, what I get is this error:
Testcase: createOne(user.UserTest):        Caused an ERROR
Object: org.qi4j.runtime.composite.TransientInstance@8fa0f0 is not a known entity type.
java.lang.IllegalArgumentException: Object: org.qi4j.runtime.composite.TransientInstance@8fa0f0 is not a known entity type.
        at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.registerNewObjectForPersist(UnitOfWorkImpl.java:4147)
        at org.eclipse.persistence.internal.jpa.EntityManagerImpl.persist(EntityManagerImpl.java:368)
        at user.UserTest.createOne(UserTest.java:57)
In short, I was trying to persist a Qi4j Transient Composite object using JPA. Transient Composites are the simplest composite objects in Qi4j, I thought it may work.

But it won't, because JPA expects an entity class (the closest to that would be the a UserMixin class), not a TransientInstance class (which is Qi4j implementation detail).

Here's the test code:

// test/user/UserTest.java
package user;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import org.junit.Test;
import org.qi4j.bootstrap.AssemblyException;
import org.qi4j.bootstrap.ModuleAssembly;
import org.qi4j.test.AbstractQi4jTest;
import static org.junit.Assert.*;

public class UserTest extends AbstractQi4jTest {

    EntityManager em;

    public void assemble(ModuleAssembly module) throws AssemblyException {
        module.addTransients(UserEntity.class);
    }

    @Override
    public void setUp() throws Exception {
        super.setUp();
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("PragmaticDciPU");
        assertNotNull(emf);
        em = emf.createEntityManager();
        assertNotNull(em);
    }

    @Override
    public void tearDown() throws Exception {
        if (em != null) {
            em.close();
        }
        super.tearDown();
    }

    @Test
    public void createOne() {
        UserEntity user = transientBuilderFactory.newTransient(UserEntity.class);
        user.setFirstName("Hendy");
        em.getTransaction().begin();
        em.persist(user);
        em.getTransaction().commit();
        UserEntity user2 = em.find(UserEntity.class, user.getId());
        assertEquals(user.getFirstName(), user2.getFirstName());
    }
} 
Here's the UserEntity composite interface:
// main/user/UserEntity.java
package user;

import org.qi4j.api.composite.TransientComposite;

public interface UserEntity
  extends User, TransientComposite { }
Here's the User mixin interface:
// main/user/User.java
package user;

import org.qi4j.api.mixin.Mixins;
import user.internal.UserMixin;

@Mixins(UserMixin.class)
public interface User {

    public Long getId();
    void setId(Long id);
    String getFirstName();
    void setFirstName(String firstName);
    String getLastName();
    void setLastName(String lastName);
}
And finally, the User mixin implementation:

// main/user/internal/UserMixin.java
package user.internal;

import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import user.User;

@Entity
public class UserMixin implements User, Serializable {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;

    private String firstName;

    private String lastName;

    public String getFirstName() {
        return firstName;
    }

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

    public Long getId() {
        return id;
    }

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

    public String getLastName() {
        return lastName;
    }

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

}
If you're curious about the persistence unit, here is it:
<!-- WEB-INF/persistence.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" 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_2_0.xsd">
  <persistence-unit name="PragmaticDciPU" transaction-type="RESOURCE_LOCAL">
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <class>user.internal.UserMixin</class>
    <exclude-unlisted-classes>true</exclude-unlisted-classes>
    <properties>
      <property name="eclipselink.ddl-generation" value="create-tables"/>
      <property name="javax.persistence.jdbc.driver" value="org.apache.derby.jdbc.ClientDriver"/>
      <property name="javax.persistence.jdbc.url" value="jdbc:derby://localhost/pragmaticdci"/>
      <property name="javax.persistence.jdbc.user" value="app"/>
      <property name="javax.persistence.jdbc.password" value="app"/>
    </properties>
  </persistence-unit>
</persistence>