diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/event/LifecycleEvent.java b/openjpa-kernel/src/main/java/org/apache/openjpa/event/LifecycleEvent.java index 2fe5fc0fb..bb8cf0127 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/event/LifecycleEvent.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/event/LifecycleEvent.java @@ -41,6 +41,12 @@ public class LifecycleEvent */ 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. */ @@ -76,6 +82,12 @@ public class LifecycleEvent */ 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. */ @@ -121,12 +133,26 @@ public class LifecycleEvent */ 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. */ public static final int[] ALL_EVENTS = new int[]{ BEFORE_PERSIST, AFTER_PERSIST, + AFTER_PERSIST_PERFORMED, AFTER_LOAD, BEFORE_STORE, AFTER_STORE, @@ -134,6 +160,7 @@ public class LifecycleEvent AFTER_CLEAR, BEFORE_DELETE, AFTER_DELETE, + AFTER_DELETE_PERFORMED, BEFORE_DIRTY, AFTER_DIRTY, BEFORE_DIRTY_FLUSHED, @@ -143,6 +170,8 @@ public class LifecycleEvent BEFORE_ATTACH, AFTER_ATTACH, AFTER_REFRESH, + BEFORE_UPDATE, + AFTER_UPDATE_PERFORMED, }; private final int _type; diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/event/LifecycleEventManager.java b/openjpa-kernel/src/main/java/org/apache/openjpa/event/LifecycleEventManager.java index 674ad7e40..f5d250b72 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/event/LifecycleEventManager.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/event/LifecycleEventManager.java @@ -28,6 +28,8 @@ import java.util.Map; import org.apache.openjpa.meta.ClassMetaData; 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. @@ -48,6 +50,9 @@ public class LifecycleEventManager 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 ListenerList _listeners = null; private List _addListeners = new LinkedList(); @@ -56,6 +61,11 @@ public class LifecycleEventManager private boolean _firing = false; private boolean _fail = 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. @@ -132,7 +142,9 @@ public class LifecycleEventManager */ public boolean hasPersistListeners(Object source, ClassMetaData meta) { 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) { 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); } + /** + * 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. */ @@ -470,6 +491,11 @@ public class LifecycleEventManager ((AttachListener) listener).afterAttach(ev); } break; + default: + if (_log.isWarnEnabled()) + _log.warn(_loc.get("unknown-lifecycle-event", + Integer.toString(type))); + break; } } catch (Exception e) { diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/BrokerImpl.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/BrokerImpl.java index 3a924b45b..a690b7a3e 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/BrokerImpl.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/BrokerImpl.java @@ -264,7 +264,8 @@ public class BrokerImpl else _runtime = new LocalManagedRuntime(this); - _lifeEventManager = new LifecycleEventManager(); + _lifeEventManager = new LifecycleEventManager( + _conf.getLog(OpenJPAConfiguration.LOG_RUNTIME)); _transEventManager = new TransactionEventManager(); int cmode = _conf.getMetaDataRepositoryInstance(). getMetaDataFactory().getDefaults().getCallbackMode(); diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/StateManagerImpl.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/StateManagerImpl.java index 6d7486bb2..5edeb8a82 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/StateManagerImpl.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/StateManagerImpl.java @@ -952,6 +952,11 @@ public class StateManagerImpl if (reason != BrokerImpl.FLUSH_ROLLBACK && reason != BrokerImpl.FLUSH_LOGICAL) { + // analyze previous state for later + boolean wasNew = isNew(); + boolean wasFlushed = isFlushed(); + boolean wasDeleted = isDeleted(); + // all dirty fields were flushed _flush.or(_dirty); @@ -972,6 +977,15 @@ public class StateManagerImpl // if this object was stored with preFlush, do post-store callback if ((_flags & FLAG_PRE_FLUSHED) > 0) 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) { // revert to last loaded version and original oid assignVersionField(_loadVersion); @@ -2761,6 +2775,11 @@ public class StateManagerImpl if (isPersistent()) { 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; } diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/VersionAttachStrategy.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/VersionAttachStrategy.java index b41f63c44..7fa741f7b 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/VersionAttachStrategy.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/VersionAttachStrategy.java @@ -33,6 +33,7 @@ import org.apache.openjpa.util.ApplicationIds; import org.apache.openjpa.util.ObjectNotFoundException; import org.apache.openjpa.util.OptimisticException; import org.apache.openjpa.util.ImplHelper; +import org.apache.openjpa.event.LifecycleEvent; /** * Handles attaching instances using version and primary key fields. @@ -133,8 +134,13 @@ class VersionAttachStrategy return into; } - // invoke any preAttach on the detached instance - manager.fireBeforeAttach(toAttach, meta); + if (isNew) { + 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 // copying into during the attach process diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/util/ImplHelper.java b/openjpa-kernel/src/main/java/org/apache/openjpa/util/ImplHelper.java index 3a416da3d..69e0d4d4c 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/util/ImplHelper.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/util/ImplHelper.java @@ -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 * @return the BitSet of fields that need update, or null if none */ diff --git a/openjpa-kernel/src/main/resources/org/apache/openjpa/event/localizer.properties b/openjpa-kernel/src/main/resources/org/apache/openjpa/event/localizer.properties index 7ad4522a7..d146f0848 100644 --- a/openjpa-kernel/src/main/resources/org/apache/openjpa/event/localizer.properties +++ b/openjpa-kernel/src/main/resources/org/apache/openjpa/event/localizer.properties @@ -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} \ not found in class "{0}". broker-factory-listener-exception: Exception thrown while calling a \ - BrokerFactoryListener. This exception will be ignored. \ No newline at end of file + 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}. \ No newline at end of file diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/callbacks/ExceptionsFromCallbacksEntity.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/callbacks/ExceptionsFromCallbacksEntity.java index fd01ebecd..57bc1bae4 100644 --- a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/callbacks/ExceptionsFromCallbacksEntity.java +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/callbacks/ExceptionsFromCallbacksEntity.java @@ -24,27 +24,52 @@ import javax.persistence.PostLoad; import javax.persistence.PrePersist; import javax.persistence.PreUpdate; 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 public class ExceptionsFromCallbacksEntity { - @Id private long id; + @Id @GeneratedValue private long id; @Version private int version; - private boolean throwOnPrePersist; - private boolean throwOnPreUpdate; + @Transient private boolean throwOnPrePersist; + @Transient private boolean throwOnPostPersist; + @Transient private boolean throwOnPreUpdate; + @Transient private boolean throwOnPostUpdate; private boolean throwOnPostLoad; + @Transient private boolean throwOnPreRemove; + @Transient private boolean throwOnPostRemove; private String stringField; - + public void setThrowOnPrePersist(boolean 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) { throwOnPostLoad = b; } - public void setThrowOnPreUpdate(boolean b) { - throwOnPreUpdate = b; + public void setThrowOnPreRemove(boolean b) { + throwOnPreRemove = b; + } + + public void setThrowOnPostRemove(boolean b) { + throwOnPostRemove = b; } public void setStringField(String s) { @@ -57,18 +82,50 @@ public class ExceptionsFromCallbacksEntity { 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 public void preUpdate() { if (throwOnPreUpdate) throw new CallbackTestException(); } - @PostLoad - public void postLoad() { - if (throwOnPostLoad) + @PostUpdate + public void postUpdate() { + if (throwOnPostUpdate) 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 extends RuntimeException { } diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/callbacks/TestExceptionsFromCallbacks.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/callbacks/TestExceptionsFromCallbacks.java index 878edbd63..33b40440c 100644 --- a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/callbacks/TestExceptionsFromCallbacks.java +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/callbacks/TestExceptionsFromCallbacks.java @@ -18,18 +18,15 @@ */ package org.apache.openjpa.persistence.callbacks; -import java.util.HashMap; -import java.util.Map; - +import java.util.Set; +import java.util.HashSet; import javax.persistence.EntityManager; -import javax.persistence.Persistence; 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.callbacks.ExceptionsFromCallbacksEntity.CallbackTestException; 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. @@ -37,8 +34,35 @@ import org.apache.openjpa.persistence.test.SingleEMFTestCase; public class TestExceptionsFromCallbacks extends SingleEMFTestCase { + public static boolean testRunning = false; + + @Override 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() { @@ -60,13 +84,41 @@ public class TestExceptionsFromCallbacks } } - public void testPreUpdateExceptionDuringFlush() { + public void testPrePersistExceptionOnMerge() { EntityManager em = emf.createEntityManager(); em.getTransaction().begin(); 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); + mutateAndFlush(em, o); + } + + private void mutateAndFlush(EntityManager em, + ExceptionsFromCallbacksEntity o) { o.setStringField("foo"); + flush(em); + } + + private void flush(EntityManager em) { try { em.flush(); fail("flush should have failed"); @@ -81,20 +133,29 @@ public class TestExceptionsFromCallbacks } } - public void testPreUpdateExceptionDuringCommit() { + public void testPostPersistExceptionDuringCommit() { EntityManager em = emf.createEntityManager(); em.getTransaction().begin(); ExceptionsFromCallbacksEntity o = new ExceptionsFromCallbacksEntity(); - o.setThrowOnPreUpdate(true); + o.setThrowOnPostPersist(true); em.persist(o); + mutateAndCommit(em, o); + } + + private void mutateAndCommit(EntityManager em, + ExceptionsFromCallbacksEntity o) { o.setStringField("foo"); + commit(em); + } + + private void commit(EntityManager em) { try { em.getTransaction().commit(); fail("commit should have failed"); } catch (RollbackException re) { assertEquals(CallbackTestException.class, re.getCause().getClass()); - + // transaction should be rolled back at this point assertFalse(em.getTransaction().isActive()); } finally { @@ -103,7 +164,253 @@ public class TestExceptionsFromCallbacks 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() { EntityManager em = emf.createEntityManager(); em.getTransaction().begin(); @@ -117,7 +424,7 @@ public class TestExceptionsFromCallbacks em = emf.createEntityManager(); em.getTransaction().begin(); try { - o = em.find(ExceptionsFromCallbacksEntity.class, oid); + em.find(ExceptionsFromCallbacksEntity.class, oid); fail("find should have failed"); } catch (CallbackTestException cte) { // transaction should be active but marked for rollback @@ -129,4 +436,107 @@ public class TestExceptionsFromCallbacks 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(); + } } diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/test/PersistenceTestCase.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/test/PersistenceTestCase.java index 4ef989f92..365e69a8d 100644 --- a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/test/PersistenceTestCase.java +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/test/PersistenceTestCase.java @@ -29,6 +29,7 @@ import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; import junit.framework.TestCase; +import junit.framework.TestResult; import org.apache.openjpa.kernel.AbstractBrokerFactory; import org.apache.openjpa.kernel.Broker; import org.apache.openjpa.meta.ClassMetaData; @@ -47,6 +48,11 @@ public abstract class PersistenceTestCase */ 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 * this list to tell the test framework to delete all table contents @@ -88,8 +94,22 @@ public abstract class PersistenceTestCase createEntityManagerFactory("test", map); } + @Override + public void run(TestResult testResult) { + this.testResult = testResult; + super.run(testResult); + } + + @Override 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; + } } /** diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/test/SingleEMFTestCase.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/test/SingleEMFTestCase.java index 7a4f33866..60c439490 100644 --- a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/test/SingleEMFTestCase.java +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/test/SingleEMFTestCase.java @@ -58,6 +58,11 @@ public abstract class SingleEMFTestCase try { 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 { closeEMF(emf); } diff --git a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/MetaDataParsers.java b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/MetaDataParsers.java index d63a3820f..6eb378d9f 100644 --- a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/MetaDataParsers.java +++ b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/MetaDataParsers.java @@ -47,15 +47,15 @@ class MetaDataParsers { case PRE_PERSIST: return new int[]{ LifecycleEvent.BEFORE_PERSIST }; case POST_PERSIST: - return new int[]{ LifecycleEvent.AFTER_PERSIST }; + return new int[]{ LifecycleEvent.AFTER_PERSIST_PERFORMED }; case PRE_REMOVE: return new int[]{ LifecycleEvent.BEFORE_DELETE }; case POST_REMOVE: - return new int[]{ LifecycleEvent.AFTER_DELETE }; + return new int[]{ LifecycleEvent.AFTER_DELETE_PERFORMED }; case PRE_UPDATE: - return new int[]{ LifecycleEvent.BEFORE_STORE }; + return new int[]{ LifecycleEvent.BEFORE_UPDATE }; case POST_UPDATE: - return new int[]{ LifecycleEvent.AFTER_STORE }; + return new int[]{ LifecycleEvent.AFTER_UPDATE_PERFORMED }; case POST_LOAD: return new int[]{ LifecycleEvent.AFTER_LOAD, LifecycleEvent.AFTER_REFRESH };