OPENJPA-506

git-svn-id: https://svn.apache.org/repos/asf/openjpa/branches/1.0.x@617334 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Patrick Linskey 2008-02-01 02:41:00 +00:00
parent dc1088934b
commit f12c0926de
12 changed files with 614 additions and 39 deletions

View File

@ -41,6 +41,12 @@ public class LifecycleEvent
*/ */
public static final int AFTER_PERSIST = 1; public static final int AFTER_PERSIST = 1;
/**
* Event type when an instance is made persistent, after the record has
* been written to the store
*/
public static final int AFTER_PERSIST_PERFORMED = 18;
/** /**
* Event type when an instance is loaded. * Event type when an instance is loaded.
*/ */
@ -76,6 +82,12 @@ public class LifecycleEvent
*/ */
public static final int AFTER_DELETE = 8; public static final int AFTER_DELETE = 8;
/**
* Event type when an instance is deleted, after the record has been
* deleted from the store.
*/
public static final int AFTER_DELETE_PERFORMED = 19;
/** /**
* Event type when an instance is dirtied for the first time. * Event type when an instance is dirtied for the first time.
*/ */
@ -121,12 +133,26 @@ public class LifecycleEvent
*/ */
public static final int AFTER_REFRESH = 17; public static final int AFTER_REFRESH = 17;
/**
* Event type when an instance is modified. This is not invoked for
* PNEW records, but is invoked for PNEWFLUSHED.
*/
public static final int BEFORE_UPDATE = 20;
/**
* Event type when an instance is modified, after the change has been
* sent to the store. This is not invoked for PNEW records, but is
* invoked for PNEWFLUSHED records.
*/
public static final int AFTER_UPDATE_PERFORMED = 21;
/** /**
* Convenience array of all event types. * Convenience array of all event types.
*/ */
public static final int[] ALL_EVENTS = new int[]{ public static final int[] ALL_EVENTS = new int[]{
BEFORE_PERSIST, BEFORE_PERSIST,
AFTER_PERSIST, AFTER_PERSIST,
AFTER_PERSIST_PERFORMED,
AFTER_LOAD, AFTER_LOAD,
BEFORE_STORE, BEFORE_STORE,
AFTER_STORE, AFTER_STORE,
@ -134,6 +160,7 @@ public class LifecycleEvent
AFTER_CLEAR, AFTER_CLEAR,
BEFORE_DELETE, BEFORE_DELETE,
AFTER_DELETE, AFTER_DELETE,
AFTER_DELETE_PERFORMED,
BEFORE_DIRTY, BEFORE_DIRTY,
AFTER_DIRTY, AFTER_DIRTY,
BEFORE_DIRTY_FLUSHED, BEFORE_DIRTY_FLUSHED,
@ -143,6 +170,8 @@ public class LifecycleEvent
BEFORE_ATTACH, BEFORE_ATTACH,
AFTER_ATTACH, AFTER_ATTACH,
AFTER_REFRESH, AFTER_REFRESH,
BEFORE_UPDATE,
AFTER_UPDATE_PERFORMED,
}; };
private final int _type; private final int _type;

View File

@ -28,6 +28,8 @@ import java.util.Map;
import org.apache.openjpa.meta.ClassMetaData; import org.apache.openjpa.meta.ClassMetaData;
import org.apache.openjpa.meta.MetaDataDefaults; import org.apache.openjpa.meta.MetaDataDefaults;
import org.apache.openjpa.lib.log.Log;
import org.apache.openjpa.lib.util.Localizer;
/** /**
* Manager that can be used to track and notify listeners on lifecycle events. * Manager that can be used to track and notify listeners on lifecycle events.
@ -48,6 +50,9 @@ public class LifecycleEventManager
private static final Exception[] EMPTY_EXCEPTIONS = new Exception[0]; private static final Exception[] EMPTY_EXCEPTIONS = new Exception[0];
private static final Localizer _loc = Localizer.forPackage(
LifecycleEventManager.class);
private Map _classListeners = null; // class -> listener list private Map _classListeners = null; // class -> listener list
private ListenerList _listeners = null; private ListenerList _listeners = null;
private List _addListeners = new LinkedList(); private List _addListeners = new LinkedList();
@ -56,6 +61,11 @@ public class LifecycleEventManager
private boolean _firing = false; private boolean _firing = false;
private boolean _fail = false; private boolean _fail = false;
private boolean _failFast = false; private boolean _failFast = false;
private final Log _log;
public LifecycleEventManager(Log log) {
_log = log;
}
/** /**
* Whether to fail after first exception when firing events to listeners. * Whether to fail after first exception when firing events to listeners.
@ -132,7 +142,9 @@ public class LifecycleEventManager
*/ */
public boolean hasPersistListeners(Object source, ClassMetaData meta) { public boolean hasPersistListeners(Object source, ClassMetaData meta) {
return hasHandlers(source, meta, LifecycleEvent.BEFORE_PERSIST) return hasHandlers(source, meta, LifecycleEvent.BEFORE_PERSIST)
|| hasHandlers(source, meta, LifecycleEvent.AFTER_PERSIST); || hasHandlers(source, meta, LifecycleEvent.AFTER_PERSIST)
|| hasHandlers(source, meta,
LifecycleEvent.AFTER_PERSIST_PERFORMED);
} }
/** /**
@ -140,7 +152,8 @@ public class LifecycleEventManager
*/ */
public boolean hasDeleteListeners(Object source, ClassMetaData meta) { public boolean hasDeleteListeners(Object source, ClassMetaData meta) {
return hasHandlers(source, meta, LifecycleEvent.BEFORE_DELETE) return hasHandlers(source, meta, LifecycleEvent.BEFORE_DELETE)
|| hasHandlers(source, meta, LifecycleEvent.AFTER_DELETE); || hasHandlers(source, meta, LifecycleEvent.AFTER_DELETE)
|| hasHandlers(source, meta, LifecycleEvent.AFTER_DELETE_PERFORMED);
} }
/** /**
@ -166,6 +179,14 @@ public class LifecycleEventManager
|| hasHandlers(source, meta, LifecycleEvent.AFTER_STORE); || hasHandlers(source, meta, LifecycleEvent.AFTER_STORE);
} }
/**
* Return whether there are listeners or callbacks for the given source.
*/
public boolean hasUpdateListeners(Object source, ClassMetaData meta) {
return hasHandlers(source, meta, LifecycleEvent.BEFORE_UPDATE)
|| hasHandlers(source, meta, LifecycleEvent.AFTER_UPDATE_PERFORMED);
}
/** /**
* Return whether there are listeners or callbacks for the given source. * Return whether there are listeners or callbacks for the given source.
*/ */
@ -470,6 +491,11 @@ public class LifecycleEventManager
((AttachListener) listener).afterAttach(ev); ((AttachListener) listener).afterAttach(ev);
} }
break; break;
default:
if (_log.isWarnEnabled())
_log.warn(_loc.get("unknown-lifecycle-event",
Integer.toString(type)));
break;
} }
} }
catch (Exception e) { catch (Exception e) {

View File

@ -264,7 +264,8 @@ public class BrokerImpl
else else
_runtime = new LocalManagedRuntime(this); _runtime = new LocalManagedRuntime(this);
_lifeEventManager = new LifecycleEventManager(); _lifeEventManager = new LifecycleEventManager(
_conf.getLog(OpenJPAConfiguration.LOG_RUNTIME));
_transEventManager = new TransactionEventManager(); _transEventManager = new TransactionEventManager();
int cmode = _conf.getMetaDataRepositoryInstance(). int cmode = _conf.getMetaDataRepositoryInstance().
getMetaDataFactory().getDefaults().getCallbackMode(); getMetaDataFactory().getDefaults().getCallbackMode();

View File

@ -952,6 +952,11 @@ public class StateManagerImpl
if (reason != BrokerImpl.FLUSH_ROLLBACK if (reason != BrokerImpl.FLUSH_ROLLBACK
&& reason != BrokerImpl.FLUSH_LOGICAL) { && reason != BrokerImpl.FLUSH_LOGICAL) {
// analyze previous state for later
boolean wasNew = isNew();
boolean wasFlushed = isFlushed();
boolean wasDeleted = isDeleted();
// all dirty fields were flushed // all dirty fields were flushed
_flush.or(_dirty); _flush.or(_dirty);
@ -972,6 +977,15 @@ public class StateManagerImpl
// if this object was stored with preFlush, do post-store callback // if this object was stored with preFlush, do post-store callback
if ((_flags & FLAG_PRE_FLUSHED) > 0) if ((_flags & FLAG_PRE_FLUSHED) > 0)
fireLifecycleEvent(LifecycleEvent.AFTER_STORE); fireLifecycleEvent(LifecycleEvent.AFTER_STORE);
// do post-update as needed
if (wasNew && !wasFlushed)
fireLifecycleEvent(LifecycleEvent.AFTER_PERSIST_PERFORMED);
else if (wasDeleted)
fireLifecycleEvent(LifecycleEvent.AFTER_DELETE_PERFORMED);
else
// updates and new-flushed with changes
fireLifecycleEvent(LifecycleEvent.AFTER_UPDATE_PERFORMED);
} else if (reason == BrokerImpl.FLUSH_ROLLBACK) { } else if (reason == BrokerImpl.FLUSH_ROLLBACK) {
// revert to last loaded version and original oid // revert to last loaded version and original oid
assignVersionField(_loadVersion); assignVersionField(_loadVersion);
@ -2761,6 +2775,11 @@ public class StateManagerImpl
if (isPersistent()) { if (isPersistent()) {
fireLifecycleEvent(LifecycleEvent.BEFORE_STORE); fireLifecycleEvent(LifecycleEvent.BEFORE_STORE);
// BEFORE_PERSIST is handled during Broker.persist and Broker.attach
if (isDeleted())
fireLifecycleEvent(LifecycleEvent.BEFORE_DELETE);
else
fireLifecycleEvent(LifecycleEvent.BEFORE_UPDATE);
_flags |= FLAG_PRE_FLUSHED; _flags |= FLAG_PRE_FLUSHED;
} }

View File

@ -33,6 +33,7 @@ import org.apache.openjpa.util.ApplicationIds;
import org.apache.openjpa.util.ObjectNotFoundException; import org.apache.openjpa.util.ObjectNotFoundException;
import org.apache.openjpa.util.OptimisticException; import org.apache.openjpa.util.OptimisticException;
import org.apache.openjpa.util.ImplHelper; import org.apache.openjpa.util.ImplHelper;
import org.apache.openjpa.event.LifecycleEvent;
/** /**
* Handles attaching instances using version and primary key fields. * Handles attaching instances using version and primary key fields.
@ -133,8 +134,13 @@ class VersionAttachStrategy
return into; return into;
} }
// invoke any preAttach on the detached instance if (isNew) {
manager.fireBeforeAttach(toAttach, meta); broker.fireLifecycleEvent(toAttach, null, meta,
LifecycleEvent.BEFORE_PERSIST);
} else {
// invoke any preAttach on the detached instance
manager.fireBeforeAttach(toAttach, meta);
}
// assign the detached pc the same state manager as the object we're // assign the detached pc the same state manager as the object we're
// copying into during the attach process // copying into during the attach process

View File

@ -168,9 +168,9 @@ public class ImplHelper {
} }
} }
/** /**
* Returns the fields of the state that require an update. * Returns the fields of the state that require an update.
* *
* @param sm the state to check * @param sm the state to check
* @return the BitSet of fields that need update, or null if none * @return the BitSet of fields that need update, or null if none
*/ */

View File

@ -96,4 +96,6 @@ bean-constructor: Could not instantiate class {0}. Make sure it has an \
method-notfound: Method "{1}" with arguments of type: {2} \ method-notfound: Method "{1}" with arguments of type: {2} \
not found in class "{0}". not found in class "{0}".
broker-factory-listener-exception: Exception thrown while calling a \ broker-factory-listener-exception: Exception thrown while calling a \
BrokerFactoryListener. This exception will be ignored. BrokerFactoryListener. This exception will be ignored.
unknown-lifecycle-event: An unknown lifecycle event was encountered. Please \
report this to dev@openjpa.apache.org. Event type: {0}.

View File

@ -24,27 +24,52 @@ import javax.persistence.PostLoad;
import javax.persistence.PrePersist; import javax.persistence.PrePersist;
import javax.persistence.PreUpdate; import javax.persistence.PreUpdate;
import javax.persistence.Version; import javax.persistence.Version;
import javax.persistence.PreRemove;
import javax.persistence.PostRemove;
import javax.persistence.PostUpdate;
import javax.persistence.PostPersist;
import javax.persistence.GeneratedValue;
import javax.persistence.Transient;
@Entity @Entity
public class ExceptionsFromCallbacksEntity { public class ExceptionsFromCallbacksEntity {
@Id private long id; @Id @GeneratedValue private long id;
@Version private int version; @Version private int version;
private boolean throwOnPrePersist; @Transient private boolean throwOnPrePersist;
private boolean throwOnPreUpdate; @Transient private boolean throwOnPostPersist;
@Transient private boolean throwOnPreUpdate;
@Transient private boolean throwOnPostUpdate;
private boolean throwOnPostLoad; private boolean throwOnPostLoad;
@Transient private boolean throwOnPreRemove;
@Transient private boolean throwOnPostRemove;
private String stringField; private String stringField;
public void setThrowOnPrePersist(boolean b) { public void setThrowOnPrePersist(boolean b) {
throwOnPrePersist = b; throwOnPrePersist = b;
} }
public void setThrowOnPostPersist(boolean b) {
throwOnPostPersist = b;
}
public void setThrowOnPreUpdate(boolean b) {
throwOnPreUpdate = b;
}
public void setThrowOnPostUpdate(boolean b) {
throwOnPostUpdate = b;
}
public void setThrowOnPostLoad(boolean b) { public void setThrowOnPostLoad(boolean b) {
throwOnPostLoad = b; throwOnPostLoad = b;
} }
public void setThrowOnPreUpdate(boolean b) { public void setThrowOnPreRemove(boolean b) {
throwOnPreUpdate = b; throwOnPreRemove = b;
}
public void setThrowOnPostRemove(boolean b) {
throwOnPostRemove = b;
} }
public void setStringField(String s) { public void setStringField(String s) {
@ -57,18 +82,50 @@ public class ExceptionsFromCallbacksEntity {
throw new CallbackTestException(); throw new CallbackTestException();
} }
@PostPersist
public void postPersist() {
if (throwOnPostPersist)
throw new CallbackTestException();
}
@PostLoad
public void postLoad() {
if (throwOnPostLoad && isInvokedFromTestMethod())
throw new CallbackTestException();
}
private boolean isInvokedFromTestMethod() {
return TestExceptionsFromCallbacks.testRunning;
}
@PreUpdate @PreUpdate
public void preUpdate() { public void preUpdate() {
if (throwOnPreUpdate) if (throwOnPreUpdate)
throw new CallbackTestException(); throw new CallbackTestException();
} }
@PostLoad @PostUpdate
public void postLoad() { public void postUpdate() {
if (throwOnPostLoad) if (throwOnPostUpdate)
throw new CallbackTestException(); throw new CallbackTestException();
} }
@PreRemove
public void preRemove() {
if (throwOnPreRemove && isInvokedFromTestMethod())
throw new CallbackTestException();
}
@PostRemove
public void postRemove() {
if (throwOnPostRemove && isInvokedFromTestMethod())
throw new CallbackTestException();
}
public Object getId() {
return id;
}
public class CallbackTestException public class CallbackTestException
extends RuntimeException { extends RuntimeException {
} }

View File

@ -18,18 +18,15 @@
*/ */
package org.apache.openjpa.persistence.callbacks; package org.apache.openjpa.persistence.callbacks;
import java.util.HashMap; import java.util.Set;
import java.util.Map; import java.util.HashSet;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import javax.persistence.Persistence;
import javax.persistence.RollbackException; import javax.persistence.RollbackException;
import junit.framework.TestCase;
import org.apache.openjpa.persistence.OpenJPAEntityManagerFactory;
import org.apache.openjpa.persistence.OpenJPAPersistence; import org.apache.openjpa.persistence.OpenJPAPersistence;
import org.apache.openjpa.persistence.callbacks.ExceptionsFromCallbacksEntity.CallbackTestException; import org.apache.openjpa.persistence.callbacks.ExceptionsFromCallbacksEntity.CallbackTestException;
import org.apache.openjpa.persistence.test.SingleEMFTestCase; import org.apache.openjpa.persistence.test.SingleEMFTestCase;
import org.apache.openjpa.enhance.PersistenceCapable;
/** /**
* Tests against JPA section 3.5's description of callback exception handling. * Tests against JPA section 3.5's description of callback exception handling.
@ -37,8 +34,35 @@ import org.apache.openjpa.persistence.test.SingleEMFTestCase;
public class TestExceptionsFromCallbacks public class TestExceptionsFromCallbacks
extends SingleEMFTestCase { extends SingleEMFTestCase {
public static boolean testRunning = false;
@Override
public void setUp() { public void setUp() {
setUp(ExceptionsFromCallbacksEntity.class); Set needEnhancement = new HashSet();
needEnhancement.add(
"testPostUpdateExceptionDuringFlushWithNewInstance");
needEnhancement.add(
"testPreUpdateExceptionDuringFlushWithExistingFlushedInstance");
needEnhancement.add(
"testPreUpdateExceptionDuringCommitWithExistingFlushedInstance");
needEnhancement.add(
"testPostUpdateExceptionDuringFlushWithExistingFlushedInstance");
needEnhancement.add(
"testPostUpdateExceptionDuringCommitWithExistingFlushedInstance");
if (!PersistenceCapable.class.isAssignableFrom(
ExceptionsFromCallbacksEntity.class)
&& needEnhancement.contains(getName()))
// actually, we really only need redef
fail("this test method does not work without enhancement");
setUp(ExceptionsFromCallbacksEntity.class, CLEAR_TABLES, "openjpa.Log", "SQL=TRACE");
testRunning = true;
}
@Override
public void tearDown() throws Exception {
testRunning = false;
super.tearDown();
} }
public void testPrePersistException() { public void testPrePersistException() {
@ -60,13 +84,41 @@ public class TestExceptionsFromCallbacks
} }
} }
public void testPreUpdateExceptionDuringFlush() { public void testPrePersistExceptionOnMerge() {
EntityManager em = emf.createEntityManager(); EntityManager em = emf.createEntityManager();
em.getTransaction().begin(); em.getTransaction().begin();
ExceptionsFromCallbacksEntity o = new ExceptionsFromCallbacksEntity(); ExceptionsFromCallbacksEntity o = new ExceptionsFromCallbacksEntity();
o.setThrowOnPreUpdate(true); o.setThrowOnPrePersist(true);
try {
em.merge(o);
fail("merge should have failed");
} catch (CallbackTestException cte) {
// transaction should be still active, but marked for rollback
assertTrue(em.getTransaction().isActive());
assertTrue(em.getTransaction().getRollbackOnly());
} finally {
if (em.getTransaction().isActive())
em.getTransaction().rollback();
em.close();
}
}
public void testPostPersistExceptionDuringFlush() {
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
ExceptionsFromCallbacksEntity o = new ExceptionsFromCallbacksEntity();
o.setThrowOnPostPersist(true);
em.persist(o); em.persist(o);
mutateAndFlush(em, o);
}
private void mutateAndFlush(EntityManager em,
ExceptionsFromCallbacksEntity o) {
o.setStringField("foo"); o.setStringField("foo");
flush(em);
}
private void flush(EntityManager em) {
try { try {
em.flush(); em.flush();
fail("flush should have failed"); fail("flush should have failed");
@ -81,20 +133,29 @@ public class TestExceptionsFromCallbacks
} }
} }
public void testPreUpdateExceptionDuringCommit() { public void testPostPersistExceptionDuringCommit() {
EntityManager em = emf.createEntityManager(); EntityManager em = emf.createEntityManager();
em.getTransaction().begin(); em.getTransaction().begin();
ExceptionsFromCallbacksEntity o = new ExceptionsFromCallbacksEntity(); ExceptionsFromCallbacksEntity o = new ExceptionsFromCallbacksEntity();
o.setThrowOnPreUpdate(true); o.setThrowOnPostPersist(true);
em.persist(o); em.persist(o);
mutateAndCommit(em, o);
}
private void mutateAndCommit(EntityManager em,
ExceptionsFromCallbacksEntity o) {
o.setStringField("foo"); o.setStringField("foo");
commit(em);
}
private void commit(EntityManager em) {
try { try {
em.getTransaction().commit(); em.getTransaction().commit();
fail("commit should have failed"); fail("commit should have failed");
} catch (RollbackException re) { } catch (RollbackException re) {
assertEquals(CallbackTestException.class, assertEquals(CallbackTestException.class,
re.getCause().getClass()); re.getCause().getClass());
// transaction should be rolled back at this point // transaction should be rolled back at this point
assertFalse(em.getTransaction().isActive()); assertFalse(em.getTransaction().isActive());
} finally { } finally {
@ -103,7 +164,253 @@ public class TestExceptionsFromCallbacks
em.close(); em.close();
} }
} }
public void testPrePersistExceptionDuringFlushWithNewFlushedInstance() {
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
ExceptionsFromCallbacksEntity o = new ExceptionsFromCallbacksEntity();
em.persist(o);
em.flush();
o.setThrowOnPrePersist(true);
// should pass; pre-persist should not be triggered
o.setStringField("foo");
em.flush();
em.getTransaction().commit();
em.close();
}
public void testPrePersistExceptionDuringCommitWithNewFlushedInstance() {
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
ExceptionsFromCallbacksEntity o = new ExceptionsFromCallbacksEntity();
em.persist(o);
em.flush();
o.setThrowOnPrePersist(true);
// should pass; pre-persist should not be triggered
o.setStringField("foo");
em.getTransaction().commit();
em.close();
}
public void testPostPersistExceptionDuringFlushWithNewFlushedInstance() {
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
ExceptionsFromCallbacksEntity o = new ExceptionsFromCallbacksEntity();
em.persist(o);
em.flush();
o.setThrowOnPostPersist(true);
// should pass; post-persist should not be triggered
o.setStringField("foo");
em.flush();
em.getTransaction().commit();
em.close();
}
public void testPostPersistExceptionDuringCommitWithNewFlushedInstance() {
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
ExceptionsFromCallbacksEntity o = new ExceptionsFromCallbacksEntity();
em.persist(o);
em.flush();
o.setThrowOnPostPersist(true);
// should pass; post-persist should not be triggered
o.setStringField("foo");
em.getTransaction().commit();
em.close();
}
public void testPreUpdateExceptionWithNewInstance() {
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
ExceptionsFromCallbacksEntity o = new ExceptionsFromCallbacksEntity();
o.setThrowOnPreUpdate(true);
em.persist(o);
o.setStringField("foo");
em.getTransaction().commit();
em.close();
}
public void testPostUpdateExceptionDuringFlushWithNewInstance() {
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
ExceptionsFromCallbacksEntity o = new ExceptionsFromCallbacksEntity();
o.setThrowOnPostUpdate(true);
em.persist(o);
o.setStringField("foo");
em.flush();
em.getTransaction().commit();
em.close();
}
public void testPostUpdateExceptionDuringCommitWithNewInstance() {
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
ExceptionsFromCallbacksEntity o = new ExceptionsFromCallbacksEntity();
o.setThrowOnPostUpdate(true);
em.persist(o);
o.setStringField("foo");
em.getTransaction().commit();
em.close();
}
public void testPreUpdateExceptionDuringFlushWithNewFlushedInstance() {
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
ExceptionsFromCallbacksEntity o = new ExceptionsFromCallbacksEntity();
em.persist(o);
em.flush();
o.setThrowOnPreUpdate(true);
mutateAndFlush(em, o);
}
public void testPreUpdateExceptionDuringCommitWithNewFlushedInstance() {
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
ExceptionsFromCallbacksEntity o = new ExceptionsFromCallbacksEntity();
em.persist(o);
em.flush();
o.setThrowOnPreUpdate(true);
mutateAndCommit(em, o);
}
public void testPostUpdateExceptionDuringFlushWithNewFlushedInstance() {
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
ExceptionsFromCallbacksEntity o = new ExceptionsFromCallbacksEntity();
em.persist(o);
em.flush();
o.setThrowOnPostUpdate(true);
mutateAndFlush(em, o);
}
public void testPostUpdateExceptionDuringCommitWithNewFlushedInstance() {
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
ExceptionsFromCallbacksEntity o = new ExceptionsFromCallbacksEntity();
em.persist(o);
em.flush();
o.setThrowOnPostUpdate(true);
mutateAndCommit(em, o);
}
public void testPreUpdateExceptionDuringFlushWithExistingInstance() {
Object oid = insert("new instance");
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
ExceptionsFromCallbacksEntity o =
em.find(ExceptionsFromCallbacksEntity.class, oid);
o.setThrowOnPreUpdate(true);
mutateAndFlush(em, o);
}
private Object insert(String s) {
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
ExceptionsFromCallbacksEntity o = new ExceptionsFromCallbacksEntity();
o.setStringField(s);
em.persist(o);
em.getTransaction().commit();
em.close();
return o.getId();
}
public void testPreUpdateExceptionDuringCommitWithExistingInstance() {
Object oid = insert("new instance");
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
ExceptionsFromCallbacksEntity o =
em.find(ExceptionsFromCallbacksEntity.class, oid);
o.setThrowOnPreUpdate(true);
mutateAndCommit(em, o);
}
public void testPostUpdateExceptionDuringFlushWithExistingInstance() {
Object oid = insert("new instance");
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
ExceptionsFromCallbacksEntity o =
em.find(ExceptionsFromCallbacksEntity.class, oid);
o.setThrowOnPostUpdate(true);
mutateAndFlush(em, o);
}
public void testPostUpdateExceptionDuringCommitWithExistingInstance() {
Object oid = insert("new instance");
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
ExceptionsFromCallbacksEntity o =
em.find(ExceptionsFromCallbacksEntity.class, oid);
o.setThrowOnPostUpdate(true);
mutateAndCommit(em, o);
}
public void testPreUpdateExceptionDuringFlushWithExistingFlushedInstance() {
Object oid = insert("new instance");
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
ExceptionsFromCallbacksEntity o =
em.find(ExceptionsFromCallbacksEntity.class, oid);
o.setStringField("foo");
em.flush();
o.setThrowOnPreUpdate(true);
// there's no additional flush work; should not re-invoke the callback
em.flush();
em.getTransaction().commit();
em.close();
}
public void testPreUpdateExceptionDuringCommitWithExistingFlushedInstance(){
Object oid = insert("new instance");
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
ExceptionsFromCallbacksEntity o =
em.find(ExceptionsFromCallbacksEntity.class, oid);
o.setStringField("foo");
em.flush();
o.setThrowOnPreUpdate(true);
// there's no additional flush work; should not re-invoke the callback
em.getTransaction().commit();
em.close();
}
public void testPostUpdateExceptionDuringFlushWithExistingFlushedInstance(){
Object oid = insert("new instance");
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
ExceptionsFromCallbacksEntity o =
em.find(ExceptionsFromCallbacksEntity.class, oid);
o.setStringField("foo");
em.flush();
o.setThrowOnPostUpdate(true);
// no mutations; should not trigger a PostUpdate
em.flush();
em.getTransaction().commit();
em.close();
}
public void testPostUpdateExceptionDuringCommitWithExistingFlushedInstance()
{
Object oid = insert("new instance");
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
ExceptionsFromCallbacksEntity o =
em.find(ExceptionsFromCallbacksEntity.class, oid);
o.setStringField("foo");
em.flush();
// no mutations; should not trigger a PostUpdate
o.setThrowOnPostUpdate(true);
em.getTransaction().commit();
em.close();
}
public void testPostLoadException() { public void testPostLoadException() {
EntityManager em = emf.createEntityManager(); EntityManager em = emf.createEntityManager();
em.getTransaction().begin(); em.getTransaction().begin();
@ -117,7 +424,7 @@ public class TestExceptionsFromCallbacks
em = emf.createEntityManager(); em = emf.createEntityManager();
em.getTransaction().begin(); em.getTransaction().begin();
try { try {
o = em.find(ExceptionsFromCallbacksEntity.class, oid); em.find(ExceptionsFromCallbacksEntity.class, oid);
fail("find should have failed"); fail("find should have failed");
} catch (CallbackTestException cte) { } catch (CallbackTestException cte) {
// transaction should be active but marked for rollback // transaction should be active but marked for rollback
@ -129,4 +436,107 @@ public class TestExceptionsFromCallbacks
em.close(); em.close();
} }
} }
public void testPreDeleteException() {
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
ExceptionsFromCallbacksEntity o = new ExceptionsFromCallbacksEntity();
em.persist(o);
em.flush();
o.setThrowOnPreRemove(true);
try {
em.remove(o);
} catch (CallbackTestException cte) {
// transaction should be active but marked for rollback
assertTrue(em.getTransaction().isActive());
assertTrue(em.getTransaction().getRollbackOnly());
} finally {
if (em.getTransaction().isActive())
em.getTransaction().rollback();
em.close();
}
}
public void testPostDeleteExceptionDuringFlush() {
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
ExceptionsFromCallbacksEntity o = new ExceptionsFromCallbacksEntity();
em.persist(o);
em.flush();
o.setThrowOnPostRemove(true);
try {
em.remove(o);
} catch (CallbackTestException e) {
em.getTransaction().rollback();
em.close();
fail("PostRemove is being called too soon (before SQL is issued)");
}
flush(em);
}
public void testPostDeleteExceptionDuringCommit() {
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
ExceptionsFromCallbacksEntity o = new ExceptionsFromCallbacksEntity();
em.persist(o);
em.flush();
o.setThrowOnPostRemove(true);
try {
em.remove(o);
} catch (CallbackTestException e) {
em.getTransaction().rollback();
em.close();
fail("PostRemove is being called too soon (before SQL is issued)");
}
commit(em);
}
public void testPreDeleteExceptionDoubleDelete() {
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
ExceptionsFromCallbacksEntity o = new ExceptionsFromCallbacksEntity();
em.persist(o);
em.flush();
// this should pass
em.remove(o);
em.flush();
o.setThrowOnPreRemove(true);
// this shoud also pass; no work to do for delete
em.remove(o);
em.getTransaction().commit();
em.close();
}
public void testPostDeleteExceptionDuringFlushDoubleDelete() {
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
ExceptionsFromCallbacksEntity o = new ExceptionsFromCallbacksEntity();
em.persist(o);
em.flush();
// this should pass
em.remove(o);
em.flush();
o.setThrowOnPostRemove(true);
// this shoud also pass; no work to do for delete
em.remove(o);
em.flush();
em.getTransaction().commit();
em.close();
}
public void testPostDeleteExceptionDuringCommitDoubleDelete() {
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
ExceptionsFromCallbacksEntity o = new ExceptionsFromCallbacksEntity();
em.persist(o);
em.flush();
// this should pass
em.remove(o);
em.flush();
o.setThrowOnPostRemove(true);
// this shoud also pass; no work to do for delete
em.remove(o);
em.getTransaction().commit();
em.close();
}
} }

View File

@ -29,6 +29,7 @@ import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence; import javax.persistence.Persistence;
import junit.framework.TestCase; import junit.framework.TestCase;
import junit.framework.TestResult;
import org.apache.openjpa.kernel.AbstractBrokerFactory; import org.apache.openjpa.kernel.AbstractBrokerFactory;
import org.apache.openjpa.kernel.Broker; import org.apache.openjpa.kernel.Broker;
import org.apache.openjpa.meta.ClassMetaData; import org.apache.openjpa.meta.ClassMetaData;
@ -47,6 +48,11 @@ public abstract class PersistenceTestCase
*/ */
protected static final Object CLEAR_TABLES = new Object(); protected static final Object CLEAR_TABLES = new Object();
/**
* The {@link TestResult} instance for the current test run.
*/
protected TestResult testResult;
/** /**
* Create an entity manager factory. Put {@link #CLEAR_TABLES} in * Create an entity manager factory. Put {@link #CLEAR_TABLES} in
* this list to tell the test framework to delete all table contents * this list to tell the test framework to delete all table contents
@ -88,8 +94,22 @@ public abstract class PersistenceTestCase
createEntityManagerFactory("test", map); createEntityManagerFactory("test", map);
} }
@Override
public void run(TestResult testResult) {
this.testResult = testResult;
super.run(testResult);
}
@Override
public void tearDown() throws Exception { public void tearDown() throws Exception {
super.tearDown(); try {
super.tearDown();
} catch (Exception e) {
// if a test failed, swallow any exceptions that happen
// during tear-down, as these just mask the original problem.
if (testResult.wasSuccessful())
throw e;
}
} }
/** /**

View File

@ -58,6 +58,11 @@ public abstract class SingleEMFTestCase
try { try {
clear(emf); clear(emf);
} catch (Exception e) {
// if a test failed, swallow any exceptions that happen
// during tear-down, as these just mask the original problem.
if (testResult.wasSuccessful())
throw e;
} finally { } finally {
closeEMF(emf); closeEMF(emf);
} }

View File

@ -47,15 +47,15 @@ class MetaDataParsers {
case PRE_PERSIST: case PRE_PERSIST:
return new int[]{ LifecycleEvent.BEFORE_PERSIST }; return new int[]{ LifecycleEvent.BEFORE_PERSIST };
case POST_PERSIST: case POST_PERSIST:
return new int[]{ LifecycleEvent.AFTER_PERSIST }; return new int[]{ LifecycleEvent.AFTER_PERSIST_PERFORMED };
case PRE_REMOVE: case PRE_REMOVE:
return new int[]{ LifecycleEvent.BEFORE_DELETE }; return new int[]{ LifecycleEvent.BEFORE_DELETE };
case POST_REMOVE: case POST_REMOVE:
return new int[]{ LifecycleEvent.AFTER_DELETE }; return new int[]{ LifecycleEvent.AFTER_DELETE_PERFORMED };
case PRE_UPDATE: case PRE_UPDATE:
return new int[]{ LifecycleEvent.BEFORE_STORE }; return new int[]{ LifecycleEvent.BEFORE_UPDATE };
case POST_UPDATE: case POST_UPDATE:
return new int[]{ LifecycleEvent.AFTER_STORE }; return new int[]{ LifecycleEvent.AFTER_UPDATE_PERFORMED };
case POST_LOAD: case POST_LOAD:
return new int[]{ LifecycleEvent.AFTER_LOAD, return new int[]{ LifecycleEvent.AFTER_LOAD,
LifecycleEvent.AFTER_REFRESH }; LifecycleEvent.AFTER_REFRESH };