Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Implemented JPA lifecycle callbacks.

This involved updating the PrivateAccessUtil to be able to create non-static method accessors
like the previous change that allows non-static field accessors.

This also involved a refactoring of the EntityManager to centralize most of the entity state
transition logic.
  • Loading branch information...
commit 656845ec9d884c1fd4256fd70aa8fe5dd9b321ae 1 parent fefbc18
@jfuerth jfuerth authored
View
22 errai-codegen/src/main/java/org/jboss/errai/codegen/meta/MetaClass.java
@@ -16,6 +16,9 @@
package org.jboss.errai.codegen.meta;
+import java.lang.annotation.Annotation;
+import java.util.List;
+
/**
* @author Mike Brock <cbrock@redhat.com>
*/
@@ -34,6 +37,21 @@
public abstract MetaMethod[] getMethods();
+ /**
+ * Returns all declared and inherited methods on this class that have the
+ * given annotation targeting them.
+ * <p>
+ * TODO: the returned collection should not include overridden methods from superclasses.
+ *
+ * @param annotation
+ * The annotation to scan this class's methods for. Must not be null.
+ * @return An unmodifiable list of all declared and inherited methods of this
+ * class that are annotated with the given annotation.
+ * @throws NullPointerException
+ * if {@code} annotation is null.
+ */
+ public abstract List<MetaMethod> getMethodsAnnotatedWith(Class<? extends Annotation> annotation);
+
public abstract MetaMethod[] getDeclaredMethods();
public abstract MetaMethod getMethod(String name, Class... parameters);
@@ -124,10 +142,6 @@
public abstract boolean isAssignableFrom(Class clazz);
public abstract boolean isAssignableTo(Class clazz);
-//
-// public abstract boolean isAssignableFrom(JClassType clazz);
-//
-// public abstract boolean isAssignableTo(JClassType clazz);
public abstract boolean isPrimitive();
View
21 errai-codegen/src/main/java/org/jboss/errai/codegen/meta/impl/AbstractMetaClass.java
@@ -16,15 +16,13 @@
package org.jboss.errai.codegen.meta.impl;
-import static org.jboss.errai.codegen.meta.MetaClassFactory.asClassArray;
import static org.jboss.errai.codegen.util.GenUtil.classToMeta;
import static org.jboss.errai.codegen.util.GenUtil.getArrayDimensions;
-import static org.jboss.errai.codegen.util.GenUtil.getBestConstructorCandidate;
import java.lang.annotation.Annotation;
-import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -38,7 +36,6 @@
import org.jboss.errai.codegen.meta.MetaType;
import org.jboss.errai.codegen.util.GenUtil;
import org.mvel2.util.NullType;
-import org.mvel2.util.ParseTools;
/**
* @author Mike Brock <cbrock@redhat.com>
@@ -374,6 +371,22 @@ public final boolean isAnnotationPresent(Class<? extends Annotation> annotation)
return getAnnotation(annotation) != null;
}
+ // docs inherited from superclass
+ @Override
+ public final List<MetaMethod> getMethodsAnnotatedWith(Class<? extends Annotation> annotation) {
+ List<MetaMethod> methods = new ArrayList<MetaMethod>();
+ MetaClass scanTarget = this;
+ while (scanTarget != null) {
+ for (MetaMethod m : scanTarget.getDeclaredMethods()) {
+ if (m.isAnnotationPresent(annotation)) {
+ methods.add(m);
+ }
+ }
+ scanTarget = scanTarget.getSuperClass();
+ }
+ return Collections.unmodifiableList(methods); // in case we want to cache this in the future
+ }
+
public T getEnclosedMetaObject() {
return enclosedMetaObject;
}
View
74 errai-codegen/src/main/java/org/jboss/errai/codegen/util/PrivateAccessUtil.java
@@ -23,7 +23,13 @@
import org.jboss.errai.codegen.meta.MetaParameter;
/**
+ * Utility class with methods that generate code to access private, default
+ * access ("package private"), and protected methods and fields in arbirtary
+ * classes. Each generator allows the choice of generating Java Reflection code
+ * (for use on the server side) or JSNI code (for use on the client side).
+ *
* @author Mike Brock
+ * @author Jonathan Fuerth
*/
public class PrivateAccessUtil {
private static final String JAVA_REFL_FLD_UTIL_METH = "_getAccessibleField";
@@ -175,11 +181,7 @@ public static void addPrivateAccessStubs(PrivateAccessType accessType,
if (useJSNIStubs) {
- // append JSNI modifier to the given modifier array
- Modifier[] origModifiers = modifiers;
- modifiers = new Modifier[origModifiers.length + 1];
- System.arraycopy(origModifiers, 0, modifiers, 0, origModifiers.length);
- modifiers[modifiers.length - 1] = Modifier.JSNI;
+ modifiers = appendJsni(modifiers);
if (write) {
final MethodCommentBuilder<? extends ClassStructureBuilder<?>> methodBuilder
@@ -269,7 +271,47 @@ public static void addPrivateAccessStubs(PrivateAccessType accessType,
}
}
+ /**
+ * Generates methods for accessing a nonpublic method using either JSNI or
+ * Java Reflection. The generated method will be private and static. The name
+ * of the generated method can be discovered by calling
+ * {@link #getPrivateMethodName(MetaMethod)}.
+ *
+ * @param useJSNIStubs
+ * If true, the generated method will use JSNI to access the field.
+ * Otherwise, Java reflection will be used (in this case, the
+ * generated code will not be GWT translatable).
+ * @param classBuilder
+ * The class builder to add the generated method to.
+ * @param m
+ * The method the generated accessors read and write.
+ */
public static void addPrivateAccessStubs(boolean useJSNIStubs, ClassStructureBuilder<?> classBuilder, MetaMethod m) {
+ addPrivateAccessStubs(useJSNIStubs, classBuilder, m, new Modifier[] { Modifier.Static });
+ }
+
+ /**
+ * Generates methods for accessing a nonpublic method using either JSNI or Java
+ * Reflection. The generated method will be private and static.
+ *
+ * @param useJSNIStubs
+ * If true, the generated method will use JSNI to access the field.
+ * Otherwise, Java reflection will be used (in this case, the
+ * generated code will not be GWT translatable).
+ * @param classBuilder
+ * The class builder to add the generated method to.
+ * @param m
+ * The method the generated accessors read and write.
+ * @param modifiers
+ * The modifiers on the generated method, for example
+ * {@link Modifier#Final} or {@link Modifier#Synchronized}. <i>Never
+ * specify {@code Modifier.JSNI}</i>; it is added automatically when
+ * needed.
+ */
+ public static void addPrivateAccessStubs(
+ boolean useJSNIStubs, ClassStructureBuilder<?> classBuilder,
+ MetaMethod m, Modifier[] modifiers) {
+
List<Parameter> wrapperDefParms = new ArrayList<Parameter>();
if (!m.isStatic()) {
@@ -281,9 +323,10 @@ public static void addPrivateAccessStubs(boolean useJSNIStubs, ClassStructureBui
wrapperDefParms.addAll(methodDefParms);
if (useJSNIStubs) {
+ modifiers = appendJsni(modifiers);
classBuilder.publicMethod(m.getReturnType(), getPrivateMethodName(m))
.parameters(new DefParameters(wrapperDefParms))
- .modifiers(Modifier.Static, Modifier.JSNI)
+ .modifiers(modifiers)
.body()
._(new StringStatement(JSNIUtil.methodAccess(m)))
.finish();
@@ -301,7 +344,7 @@ public static void addPrivateAccessStubs(boolean useJSNIStubs, ClassStructureBui
BlockBuilder<? extends ClassStructureBuilder> body = classBuilder.publicMethod(m.getReturnType(),
getPrivateMethodName(m))
.parameters(new DefParameters(wrapperDefParms))
- .modifiers(Modifier.Static)
+ .modifiers(modifiers)
.body();
BlockBuilder<CatchBlockBuilder> tryBuilder = Stmt.try_();
@@ -342,6 +385,23 @@ public static String getPrivateMethodName(MetaMethod method) {
return buf.toString();
}
+ /**
+ * Returns a new array consisting of a copy of the given array, plus
+ * Modifiers.JSNI as the last element.
+ *
+ * @param modifiers The array to copy. May be empty, but must not be null.
+ * @return An array of length {@code n + 1}, where {@code n} is the length of
+ * the given array. Positions 0..n-1 correspond with the respective
+ * entries in the given array, and position n contains Modifiers.JSNI.
+ */
+ private static Modifier[] appendJsni(Modifier[] modifiers) {
+ Modifier[] origModifiers = modifiers;
+ modifiers = new Modifier[origModifiers.length + 1];
+ System.arraycopy(origModifiers, 0, modifiers, 0, origModifiers.length);
+ modifiers[modifiers.length - 1] = Modifier.JSNI;
+ return modifiers;
+ }
+
private static String _getReflectionFieldMethGetName(MetaField f) {
MetaClass t = f.getType();
View
14 errai-jpa/errai-jpa-client/src/main/java/org/jboss/errai/jpa/client/local/EntityState.java
@@ -0,0 +1,14 @@
+package org.jboss.errai.jpa.client.local;
+
+/**
+ * Represents the states an entity instance can be in, according to the
+ * ErraiEntityManager.
+ *
+ * @author Jonathan Fuerth <jfuerth@gmail.com>
+ */
+public enum EntityState {
+ NEW,
+ MANAGED,
+ DETACHED,
+ REMOVED
+}
View
151 ...a/errai-jpa-client/src/main/java/org/jboss/errai/jpa/client/local/ErraiEntityManager.java
@@ -1,9 +1,12 @@
package org.jboss.errai.jpa.client.local;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import javax.persistence.EntityExistsException;
import javax.persistence.EntityManager;
import org.jboss.errai.jpa.client.local.backend.StorageBackend;
@@ -91,29 +94,6 @@ protected ErraiEntityManager() {
return (T) value;
}
- private <T> void persistImpl(T entity) {
- ErraiEntityType<T> entityType = getMetamodel().entity(getNarrowedClass(entity));
-
- ErraiSingularAttribute<? super T,?> idAttr;
- switch (entityType.getIdType().getPersistenceType()) {
- case BASIC:
- idAttr = (ErraiSingularAttribute<? super T, ?>) entityType.getId(entityType.getIdType().getJavaType());
- break;
- default:
- throw new RuntimeException(entityType.getIdType().getPersistenceType() + " ids are not yet supported");
- }
-
- Object id = idAttr.get(entity);
- if (id == null) {
- id = generateAndSetLocalId(entity, idAttr);
- // TODO track this generated ID for later reconciliation with the server
- }
-
- Key<T, ?> key = new Key<T, Object>(entityType, id);
- persistenceContext.put(key, entity);
- backend.put(key, entity);
- }
-
/**
* Generates a new ID value for the given entity instance that is guaranteed
* to be unique <i>on this client</i>. If the entity instance with this ID is
@@ -137,21 +117,6 @@ protected ErraiEntityManager() {
return nextId;
}
- /**
- * Internal routine that completes the detach process for a managed entity.
- * This method does not alter the {@link #persistenceContext}, so it is safe
- * to use while iterating over that collection.
- *
- * @param entity
- * the entity whose state is transitioning from <i>managed</i> to
- * <i>detached</i>.
- */
- private void finishDetach(Object entity) {
- // in fact, it's probably better to create a single method that transitions
- // the state of an entity, checking for illegal transitions and firing the
- // appropriate events along the way.
- }
-
private <X, T> Key<X, T> lookupManagedEntity(X entity, boolean throwIfAbsent) {
// this implementation becomes poor when the persistence context is large.
// Turning the persistenceContext into a bimap where the value set is done by object identity would be better.
@@ -168,6 +133,93 @@ private void finishDetach(Object entity) {
return null;
}
+ /**
+ * As they say in television, "this is where the magic happens." This method
+ * attempts to resolve the given object as an entity and put that entity into
+ * the given state, taking into account its existing state and performing the
+ * required side effects during the state transition.
+ */
+ private <T> void changeEntityState(T entity, EntityState newState) {
+ ErraiEntityType<T> entityType = getMetamodel().entity(getNarrowedClass(entity));
+
+ ErraiSingularAttribute<? super T, ?> idAttr;
+ switch (entityType.getIdType().getPersistenceType()) {
+ case BASIC:
+ idAttr = (ErraiSingularAttribute<? super T, ?>) entityType.getId(entityType.getIdType().getJavaType());
+ break;
+ default:
+ throw new RuntimeException(entityType.getIdType().getPersistenceType() + " ids are not yet supported");
+ }
+ Object id = idAttr.get(entity);
+ if (id == null) {
+ id = generateAndSetLocalId(entity, idAttr);
+ // TODO track this generated ID for later reconciliation with the server
+ }
+
+ Key<T, ?> key = new Key<T, Object>(entityType, id);
+
+ final EntityState oldState;
+ if (persistenceContext.get(key) != null) {
+ oldState = EntityState.MANAGED;
+ }
+ else if (backend.get(key) != null) {
+ oldState = EntityState.DETACHED;
+ }
+ else {
+ oldState = EntityState.NEW;
+ }
+ // TODO handle REMOVED state
+
+ switch (newState) {
+ case MANAGED:
+ switch (oldState) {
+ case NEW:
+ case REMOVED:
+ entityType.deliverPrePersist(entity);
+ persistenceContext.put(key, entity);
+ backend.put(key, entity);
+ entityType.deliverPostPersist(entity);
+ // FALLTHROUGH
+ case MANAGED:
+ // no-op, but cascade to relatives
+ break;
+ case DETACHED:
+ throw new EntityExistsException();
+ }
+ break;
+ case DETACHED:
+ switch (oldState) {
+ case NEW:
+ case DETACHED:
+ // ignore
+ break;
+ case MANAGED:
+ case REMOVED:
+ persistenceContext.remove(key);
+ break;
+ }
+ break;
+ case REMOVED:
+ switch (oldState) {
+ case NEW:
+ case MANAGED:
+ entityType.deliverPreRemove(entity);
+ persistenceContext.remove(key);
+ backend.remove(key);
+ entityType.deliverPostRemove(entity);
+ break;
+ case DETACHED:
+ throw new IllegalArgumentException("Entities can't transition from " + oldState + " to " + newState);
+ case REMOVED:
+ // ignore
+ break;
+ }
+ break;
+ case NEW:
+ throw new IllegalArgumentException("Entities can't transition from " + oldState + " to " + newState);
+ }
+ }
+
// -------------- Actual JPA API below this line -------------------
@Override
@@ -183,7 +235,7 @@ public ErraiMetamodel getMetamodel() {
@Override
public void persist(Object entity) {
- persistImpl(entity);
+ changeEntityState(entity, EntityState.MANAGED);
}
@Override
@@ -193,17 +245,15 @@ public void flush() {
@Override
public void detach(Object entity) {
- Key<Object, Object> key = lookupManagedEntity(entity, true);
- finishDetach(entity);
- persistenceContext.remove(key);
+ changeEntityState(entity, EntityState.DETACHED);
}
@Override
public void clear() {
- for (Object entity : persistenceContext.values()) {
- finishDetach(entity);
+ List<?> entities = new ArrayList<Object>(persistenceContext.values());
+ for (Object entity : entities) {
+ detach(entity);
}
- persistenceContext.clear();
}
@Override
@@ -212,7 +262,12 @@ public void clear() {
X entity = cast(entityClass, persistenceContext.get(key));
if (entity == null) {
entity = backend.get(key);
- persistenceContext.put(key, entity);
+ if (entity != null) {
+ persistenceContext.put(key, entity);
+
+ // XXX when persistenceContext gets its own class, this should go on the ultimate ingress point
+ getMetamodel().entity(entityClass).deliverPostLoad(entity);
+ }
}
return entity;
}
@@ -224,8 +279,6 @@ public void clear() {
@Override
public void remove(Object entity) {
- Key<?, ?> key = lookupManagedEntity(entity, true);
- persistenceContext.remove(key);
- backend.remove(key);
+ changeEntityState(entity, EntityState.REMOVED);
}
}
View
68 ...-jpa/errai-jpa-client/src/main/java/org/jboss/errai/jpa/client/local/ErraiEntityType.java
@@ -3,6 +3,7 @@
import java.util.HashSet;
import java.util.Set;
+import javax.persistence.PrePersist;
import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.CollectionAttribute;
import javax.persistence.metamodel.EntityType;
@@ -14,7 +15,7 @@
import javax.persistence.metamodel.SingularAttribute;
import javax.persistence.metamodel.Type;
-public class ErraiEntityType<X> implements EntityType<X> {
+public abstract class ErraiEntityType<X> implements EntityType<X> {
private final Set<SingularAttribute<? super X, ?>> singularAttributes = new HashSet<SingularAttribute<? super X,?>>();
@@ -35,6 +36,71 @@ public ErraiEntityType(String name, Class<X> javaType) {
if (attribute.isVersion()) version = attribute;
}
+ /**
+ * Delivers the {@link PrePersist} event to the pre-persist listeners on the given
+ * instance of this entity.
+ *
+ * @param targetEntity
+ * The entity instance to deliver the PrePersist event to.
+ */
+ public abstract void deliverPrePersist(X targetEntity);
+
+ /**
+ * Delivers the {@link PostPersist} event to the post-persist listeners on the given
+ * instance of this entity.
+ *
+ * @param targetEntity
+ * The entity instance to deliver the PostPersist event to.
+ */
+ public abstract void deliverPostPersist(X targetEntity);
+
+ /**
+ * Delivers the {@link PreUpdate} event to the pre-Update listeners on the given
+ * instance of this entity.
+ *
+ * @param targetEntity
+ * The entity instance to deliver the PreUpdate event to.
+ */
+ public abstract void deliverPreUpdate(X targetEntity);
+
+ /**
+ * Delivers the {@link PostUpdate} event to the post-Update listeners on the given
+ * instance of this entity.
+ *
+ * @param targetEntity
+ * The entity instance to deliver the PostUpdate event to.
+ */
+ public abstract void deliverPostUpdate(X targetEntity);
+
+ /**
+ * Delivers the {@link PreRemove} event to the pre-Remove listeners on the given
+ * instance of this entity.
+ *
+ * @param targetEntity
+ * The entity instance to deliver the PreRemove event to.
+ */
+ public abstract void deliverPreRemove(X targetEntity);
+
+ /**
+ * Delivers the {@link PostRemove} event to the post-Remove listeners on the given
+ * instance of this entity.
+ *
+ * @param targetEntity
+ * The entity instance to deliver the PostRemove event to.
+ */
+ public abstract void deliverPostRemove(X targetEntity);
+
+ /**
+ * Delivers the {@link PostLoad} event to the post-load listeners on the given
+ * instance of this entity.
+ *
+ * @param targetEntity
+ * The entity instance to deliver the PostLoad event to.
+ */
+ public abstract void deliverPostLoad(X targetEntity);
+
+ // ---------- JPA API Below This Line -------------
+
@SuppressWarnings("unchecked")
@Override
public <Y> SingularAttribute<? super X, Y> getId(Class<Y> type) {
View
131 ...rrai-jpa-client/src/main/java/org/jboss/errai/jpa/rebind/ErraiEntityManagerGenerator.java
@@ -1,10 +1,13 @@
package org.jboss.errai.jpa.rebind;
import java.io.PrintWriter;
+import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -15,6 +18,13 @@
import javax.persistence.EntityManagerFactory;
import javax.persistence.GeneratedValue;
import javax.persistence.Persistence;
+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;
import javax.persistence.metamodel.EntityType;
import javax.persistence.metamodel.ManagedType;
import javax.persistence.metamodel.Metamodel;
@@ -22,13 +32,17 @@
import javax.persistence.metamodel.Type;
import org.jboss.errai.codegen.Modifier;
+import org.jboss.errai.codegen.Parameter;
import org.jboss.errai.codegen.SnapshotMaker;
import org.jboss.errai.codegen.SnapshotMaker.MethodBodyCallback;
import org.jboss.errai.codegen.Statement;
import org.jboss.errai.codegen.StringStatement;
import org.jboss.errai.codegen.Variable;
+import org.jboss.errai.codegen.builder.AnonymousClassStructureBuilder;
+import org.jboss.errai.codegen.builder.BlockBuilder;
import org.jboss.errai.codegen.builder.ClassStructureBuilder;
import org.jboss.errai.codegen.builder.MethodBlockBuilder;
+import org.jboss.errai.codegen.meta.MetaClass;
import org.jboss.errai.codegen.meta.MetaClassFactory;
import org.jboss.errai.codegen.meta.MetaField;
import org.jboss.errai.codegen.meta.MetaMethod;
@@ -37,6 +51,7 @@
import org.jboss.errai.codegen.util.PrivateAccessType;
import org.jboss.errai.codegen.util.PrivateAccessUtil;
import org.jboss.errai.codegen.util.Stmt;
+import org.jboss.errai.common.client.framework.Assert;
import org.jboss.errai.jpa.client.local.ErraiEntityManager;
import org.jboss.errai.jpa.client.local.ErraiEntityType;
import org.jboss.errai.jpa.client.local.ErraiSingularAttribute;
@@ -49,6 +64,19 @@
public class ErraiEntityManagerGenerator extends Generator {
+ private static final List<Class<? extends Annotation>> LIFECYCLE_EVENT_TYPES;
+ static {
+ List<Class<? extends Annotation>> l = new ArrayList<Class<? extends Annotation>>();
+ l.add(PrePersist.class);
+ l.add(PostPersist.class);
+ l.add(PreUpdate.class);
+ l.add(PostUpdate.class);
+ l.add(PreRemove.class);
+ l.add(PostRemove.class);
+ l.add(PostLoad.class);
+ LIFECYCLE_EVENT_TYPES = Collections.unmodifiableList(l);
+ }
+
@Override
public String generate(TreeLogger logger, GeneratorContext context,
String typeName) throws UnableToCompleteException {
@@ -68,6 +96,7 @@ public String generate(TreeLogger logger, GeneratorContext context,
MethodBlockBuilder<?> pmm = classBuilder.protectedMethod(void.class, "populateMetamodel");
for (final EntityType<?> et : mm.getEntities()) {
+ MetaClass met = MetaClassFactory.get(et.getJavaType());
// first, create a variable for the EntityType
pmm.append(Stmt.codeComment(
@@ -75,9 +104,16 @@ public String generate(TreeLogger logger, GeneratorContext context,
"** EntityType for " + et.getJavaType().getName() + "\n" +
"**"));
String entityTypeVarName = entitySnapshotVarName(et.getJavaType());
+
+ AnonymousClassStructureBuilder entityTypeSubclass =
+ Stmt.newObject(MetaClassFactory.get(ErraiEntityType.class, new ParameterizedEntityType(et.getJavaType())))
+ .extend();
+
+ generateLifecycleEventDeliveryMethods(met, entityTypeSubclass);
+
pmm.append(Stmt.declareVariable(ErraiEntityType.class).asFinal()
.named(entityTypeVarName)
- .initializeWith(Stmt.newObject(ErraiEntityType.class).withParameters(et.getName(), et.getJavaType())));
+ .initializeWith(entityTypeSubclass.finish().withParameters(et.getName(), et.getJavaType())));
MethodBodyCallback methodBodyCallback = new MethodBodyCallback() {
@@ -231,6 +267,44 @@ else if (attr.getJavaMember() instanceof Method) {
}
/**
+ * Generates the event delivery methods for the given JPA Entity type.
+ *
+ * @param entityType
+ * The metaclass representing the entity type.
+ * @param classBuilder
+ * The target builder to receive the generated methods. For the
+ * generated code to be valid, this should be a builder of a subclass
+ * of {@link ErraiEntityType}.
+ */
+ protected void generateLifecycleEventDeliveryMethods(
+ MetaClass entityType,
+ AnonymousClassStructureBuilder classBuilder) {
+
+ for (Class<? extends Annotation> eventType : LIFECYCLE_EVENT_TYPES) {
+ BlockBuilder<AnonymousClassStructureBuilder> methodBuilder =
+ classBuilder.publicMethod(
+ Void.TYPE,
+ "deliver" + eventType.getSimpleName(),
+ Parameter.of(entityType, "targetEntity"));
+
+ // TODO also scan standalone listener types mentioned in class-level annotation
+
+ for (MetaMethod callback : entityType.getMethodsAnnotatedWith(eventType)) {
+ if (!callback.isPublic()) {
+ PrivateAccessUtil.addPrivateAccessStubs(true, classBuilder, callback, new Modifier[] {});
+ methodBuilder.append(
+ Stmt.loadVariable("this")
+ .invoke(PrivateAccessUtil.getPrivateMethodName(callback), Stmt.loadVariable("targetEntity")));
+ }
+ else {
+ methodBuilder.append(Stmt.loadVariable("targetEntity").invoke(callback));
+ }
+ }
+ methodBuilder.finish();
+ }
+ }
+
+ /**
* Returns true if the given Java member is annotated as a JPA generated value.
* <p>
* TODO: support this determination for XML-configured entities.
@@ -256,5 +330,60 @@ static String entitySnapshotVarName(Class<?> forType) {
return "et_" + forType.getCanonicalName().replace('.', '_');
}
+ /**
+ * Represents the parameterized Java reflection type for
+ * {@code ErraiEntityType<X>}, where {@code X} can be provided at runtime.
+ *
+ * @author Jonathan Fuerth <jfuerth@gmail.com>
+ */
+ static final class ParameterizedEntityType implements ParameterizedType {
+
+ private final java.lang.reflect.Type entityType;
+
+ public ParameterizedEntityType(java.lang.reflect.Type entityType) {
+ this.entityType = Assert.notNull(entityType);
+ }
+
+ @Override
+ public java.lang.reflect.Type[] getActualTypeArguments() {
+ return new java.lang.reflect.Type[] { entityType };
+ }
+
+ @Override
+ public java.lang.reflect.Type getRawType() {
+ return ErraiEntityType.class;
+ }
+ @Override
+ public java.lang.reflect.Type getOwnerType() {
+ return null;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result
+ + ((entityType == null) ? 0 : entityType.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ ParameterizedEntityType other = (ParameterizedEntityType) obj;
+ if (entityType == null) {
+ if (other.entityType != null)
+ return false;
+ }
+ else if (!entityType.equals(other.entityType))
+ return false;
+ return true;
+ }
+ }
}
View
135 errai-jpa/errai-jpa-client/src/test/java/org/jboss/errai/jpa/test/client/ErraiJpaTest.java
@@ -2,10 +2,20 @@
import java.sql.Date;
+import java.util.ArrayList;
+import java.util.List;
import javax.persistence.EntityManager;
+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;
import org.jboss.errai.ioc.client.Container;
+import org.jboss.errai.jpa.rebind.ErraiEntityManagerGenerator;
import org.jboss.errai.jpa.test.entity.Album;
import org.jboss.errai.jpa.test.entity.Artist;
@@ -43,6 +53,15 @@ protected void gwtSetUp() throws Exception {
new Container().boostrapContainer();
}
+ /**
+ * Tests that the entity manager was injected into the testing class. If this
+ * test fails, the likely cause is that the
+ * {@link ErraiEntityManagerGenerator} failed to output a compilable class. In
+ * that case, try re-running this test with
+ * {@code -Derrai.codegen.permissive=true} and
+ * {@code -Derrai.codegen.printOut=true}. This should allow you to inspect the
+ * generated source code and to see the Java compiler errors.
+ */
public void testEntityManagerInjection() throws Exception {
getEntityManager(); // has its own assertions
}
@@ -245,4 +264,120 @@ public void testRemoveOneEntity() {
assertNull(em.find(Album.class, album.getId()));
}
+ public void testPersistNewEntityLifecycle() throws Exception {
+
+ List<Class<?>> expectedLifecycle = new ArrayList<Class<?>>();
+
+ // make it
+ Album album = new Album();
+ album.setArtist(null);
+ album.setName("Abbey Road");
+ album.setReleaseDate(new Date(-8366400000L));
+
+ assertEquals(expectedLifecycle, album.getCallbackLog());
+
+ // store it
+ EntityManager em = getEntityManager();
+ em.persist(album);
+
+ expectedLifecycle.add(PrePersist.class);
+ expectedLifecycle.add(PostPersist.class);
+ assertEquals(expectedLifecycle, album.getCallbackLog());
+
+ em.flush();
+ assertEquals(expectedLifecycle, album.getCallbackLog());
+
+ // verify that detach causes no lifecycle updates
+ em.detach(album);
+ assertEquals(expectedLifecycle, album.getCallbackLog());
+ }
+
+ public void testFetchEntityLifecycle() throws Exception {
+
+ // make it
+ Album album = new Album();
+ album.setArtist(null);
+ album.setName("Abbey Road");
+ album.setReleaseDate(new Date(-8366400000L));
+
+ // store it
+ EntityManager em = getEntityManager();
+ em.persist(album);
+ List<Class<?>> expectedLifecycle = new ArrayList<Class<?>>();
+ em.flush();
+ em.detach(album);
+
+ // fetch a fresh copy
+ Album fetchedAlbum = em.find(Album.class, album.getId());
+ expectedLifecycle.add(PostLoad.class);
+ assertEquals(expectedLifecycle, fetchedAlbum.getCallbackLog());
+
+ // fetch again; expect no more PostLoad notifications
+ Album fetchedAlbum2 = em.find(Album.class, album.getId());
+ assertSame(fetchedAlbum, fetchedAlbum2);
+ assertEquals(expectedLifecycle, fetchedAlbum2.getCallbackLog());
+ }
+
+ public void testRemoveEntityLifecycle() throws Exception {
+
+ // make it
+ Album album = new Album();
+ album.setArtist(null);
+ album.setName("Abbey Road");
+ album.setReleaseDate(new Date(-8366400000L));
+
+ // store it
+ EntityManager em = getEntityManager();
+ em.persist(album);
+ List<Class<?>> expectedLifecycle = new ArrayList<Class<?>>();
+ em.flush();
+ em.detach(album);
+
+ // fetch a fresh copy
+ Album fetchedAlbum = em.find(Album.class, album.getId());
+ expectedLifecycle.add(PostLoad.class);
+ assertEquals(expectedLifecycle, fetchedAlbum.getCallbackLog());
+
+ // delete it
+ em.remove(fetchedAlbum);
+ em.flush();
+ expectedLifecycle.add(PreRemove.class);
+ expectedLifecycle.add(PostRemove.class);
+ assertEquals(expectedLifecycle, fetchedAlbum.getCallbackLog());
+
+ // verify that detached entity received no further lifecycle updates
+ expectedLifecycle.clear();
+ expectedLifecycle.add(PrePersist.class);
+ expectedLifecycle.add(PostPersist.class);
+ assertEquals(expectedLifecycle, album.getCallbackLog());
+ }
+
+ // disabled until we support updates to existing entities
+ public void IGNOREtestUpdateEntityLifecycle() throws Exception {
+
+ // make it
+ Album album = new Album();
+ album.setArtist(null);
+ album.setName("Abbey Road");
+ album.setReleaseDate(new Date(-8366400000L));
+
+ // store it
+ EntityManager em = getEntityManager();
+ em.persist(album);
+ List<Class<?>> expectedLifecycle = new ArrayList<Class<?>>();
+ em.flush();
+
+ expectedLifecycle.add(PrePersist.class);
+ expectedLifecycle.add(PostPersist.class);
+ assertEquals(expectedLifecycle, album.getCallbackLog());
+
+ // modify it
+ album.setName("Cowabunga");
+ em.flush();
+
+ expectedLifecycle.add(PreUpdate.class);
+ expectedLifecycle.add(PostUpdate.class);
+ assertEquals(expectedLifecycle, album.getCallbackLog());
+ }
+
}
View
31 errai-jpa/errai-jpa-client/src/test/java/org/jboss/errai/jpa/test/entity/Album.java
@@ -1,11 +1,21 @@
package org.jboss.errai.jpa.test.entity;
import java.sql.Date;
+import java.util.ArrayList;
+import java.util.List;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
+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;
+import javax.persistence.Transient;
import org.jboss.errai.common.client.api.annotations.Portable;
import org.jboss.errai.ioc.client.api.TestOnly;
@@ -64,4 +74,25 @@ public String toString() {
+ (artist == null ? "null" : artist.getName())
+ ", releaseDate=" + releaseDate + "]";
}
+
+ // ------ Lifecycle callbacks (assorted access levels to test that they all work) ------
+
+ @Transient
+ private transient final List<Class<?>> callbackLog = new ArrayList<Class<?>>();
+
+ public List<Class<?>> getCallbackLog() {
+ return callbackLog;
+ }
+
+ @SuppressWarnings("unused")
+ @PrePersist private void prePersist() { callbackLog.add(PrePersist.class); };
+
+ @SuppressWarnings("unused")
+ @PostPersist private void postPersist() { callbackLog.add(PostPersist.class); };
+
+ @PreRemove void preRemove() { callbackLog.add(PreRemove.class); };
+ @PostRemove void postRemove() { callbackLog.add(PostRemove.class); };
+ @PreUpdate protected void preUpdate() { callbackLog.add(PreUpdate.class); };
+ @PostUpdate protected void postUpdate() { callbackLog.add(PostUpdate.class); };
+ @PostLoad public void postLoad() { callbackLog.add(PostLoad.class); };
}
View
1  errai-jpa/errai-jpa-client/src/test/resources/log4j.properties
@@ -22,3 +22,4 @@ log4j.appender.A1.layout.ConversionPattern=%d [%t] %-5p %c{1} - %m%n
log4j.logger=INFO
log4j.logger.org.jboss.errai.reflections=DEBUG
log4j.logger.org.jboss.errai.ioc.rebind.ioc.bootstrapper=DEBUG
+log4j.logger.ErraiMarshalling=DEBUG
Please sign in to comment.
Something went wrong with that request. Please try again.