diff options
author | Simon McDuff | 2008-05-23 11:41:01 +0000 |
---|---|---|
committer | Simon McDuff | 2008-05-23 11:41:01 +0000 |
commit | 9b9bcfaac140cd53b70c433e6b87823aa7794bde (patch) | |
tree | db212d6a88b489d7634d56887caea8884bc0aa5e | |
parent | f060e6081b32db0db5116ec8eb0b8277af59823d (diff) | |
download | cdo-9b9bcfaac140cd53b70c433e6b87823aa7794bde.tar.gz cdo-9b9bcfaac140cd53b70c433e6b87823aa7794bde.tar.xz cdo-9b9bcfaac140cd53b70c433e6b87823aa7794bde.zip |
[215688] Create save points
https://bugs.eclipse.org/bugs/show_bug.cgi?id=215688
8 files changed, 753 insertions, 81 deletions
diff --git a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/AllTests.java b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/AllTests.java index 76551ab535..65c647a63b 100644 --- a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/AllTests.java +++ b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/AllTests.java @@ -40,6 +40,7 @@ public class AllTests suite.addTestSuite(RevisionDeltaTest.class); suite.addTestSuite(IndexReconstructionTest.class); suite.addTestSuite(NoLegacyTest.class); + suite.addTestSuite(SavePointTest.class); // TODO suite.addTestSuite(GeneratedEcoreTest.class); // $JUnit-END$ diff --git a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/SavePointTest.java b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/SavePointTest.java new file mode 100644 index 0000000000..aa3b55ad79 --- /dev/null +++ b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/SavePointTest.java @@ -0,0 +1,269 @@ +/*************************************************************************** + * Copyright (c) 2004 - 2008 Simon McDuff, Canada. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Simon McDuff - initial API and implementation + **************************************************************************/ + +package org.eclipse.emf.cdo.tests; + +import org.eclipse.emf.cdo.CDOObject; +import org.eclipse.emf.cdo.CDOSavePoint; +import org.eclipse.emf.cdo.CDOSession; +import org.eclipse.emf.cdo.CDOTransaction; +import org.eclipse.emf.cdo.eresource.CDOResource; +import org.eclipse.emf.cdo.tests.model1.Category; +import org.eclipse.emf.cdo.tests.model1.Company; +import org.eclipse.emf.cdo.tests.model1.Model1Factory; +import org.eclipse.emf.cdo.tests.model1.Model1Package; + +import org.eclipse.emf.internal.cdo.CDOTransactionImpl; +import org.eclipse.emf.internal.cdo.util.FSMUtil; + +import junit.framework.Assert; + +/** + * @author Simon McDuff + */ +public class SavePointTest extends AbstractCDOTest +{ + public void testRollbackWithNewObject_Collection() throws Exception + { + + CDOSession session = openModel1Session(); + + session.getPackageRegistry().putEPackage(Model1Package.eINSTANCE); + + CDOTransaction transaction1 = session.openTransaction(); + // Client1 + CDOResource resource1 = transaction1.createResource("/test1"); + + Company company1 = Model1Factory.eINSTANCE.createCompany(); + resource1.getContents().add(company1); + Category category1 = Model1Factory.eINSTANCE.createCategory(); + company1.getCategories().add(category1); + + transaction1.createSavePoint(); + + Category category2 = Model1Factory.eINSTANCE.createCategory(); + company1.getCategories().add(category2); + + CDOSavePoint savePoint2 = transaction1.createSavePoint(); + + Category category3 = Model1Factory.eINSTANCE.createCategory(); + company1.getCategories().add(category3); + + transaction1.createSavePoint(); + + transaction1.rollback(savePoint2, false); + + assertEquals(2, company1.getCategories().size()); + + transaction1.commit(); + + } + + public void testRollbackWithNewObject_Commit() throws Exception + { + flow1(false, true); + } + public void testRollbackWithNewObject_Rollback() throws Exception + { + + flow1(false, false); + } + + public void testRollbackWithPersistedObject_Commit() throws Exception + { + flow1(true, true); + } + public void testRollbackWithPersistedObject_Rollback() throws Exception + { + flow1(true, false); + } + + public void testWrongSavePoint() throws Exception + { + CDOSession session = openModel1Session(); + session.getPackageRegistry().putEPackage(Model1Package.eINSTANCE); + + CDOTransactionImpl transaction1 = (CDOTransactionImpl)session.openTransaction(); + // Client1 + CDOResource resource1 = transaction1.createResource("/test1"); + Company company1 = Model1Factory.eINSTANCE.createCompany(); + resource1.getContents().add(company1); + Category category1 = Model1Factory.eINSTANCE.createCategory(); + company1.getCategories().add(category1); + + CDOSavePoint savePoint1 = transaction1.createSavePoint(); + CDOSavePoint savePoint2 = transaction1.createSavePoint(); + transaction1.rollback(savePoint1, false); + try + { + transaction1.rollback(savePoint2, false); + Assert.assertEquals("Should have thrown an exception", false, true); + } + catch (IllegalArgumentException illegalArgumentException) + { + + } + try + { + transaction1.rollback(null, false); + Assert.assertEquals("Should have thrown an exception", false, true); + } + catch (IllegalArgumentException illegalArgumentException) + { + + } + } + public void testisDirty() throws Exception + { + CDOSession session = openModel1Session(); + session.getPackageRegistry().putEPackage(Model1Package.eINSTANCE); + + CDOTransactionImpl transaction1 = (CDOTransactionImpl)session.openTransaction(); + + CDOSavePoint savePoint0 = transaction1.createSavePoint(); + // Client1 + CDOResource resource1 = transaction1.createResource("/test1"); + Company company1 = Model1Factory.eINSTANCE.createCompany(); + resource1.getContents().add(company1); + Category category1 = Model1Factory.eINSTANCE.createCategory(); + company1.getCategories().add(category1); + + CDOSavePoint savePoint1 = transaction1.createSavePoint(); + Category category2 = Model1Factory.eINSTANCE.createCategory(); + company1.getCategories().add(category2); + + CDOSavePoint savePoint2 = transaction1.createSavePoint(); + CDOSavePoint savePoint3 = transaction1.createSavePoint(); + + assertEquals(true, transaction1.isDirty()); + + transaction1.rollback(savePoint3, false); + assertEquals(true, transaction1.isDirty()); + + transaction1.rollback(savePoint2, false); + assertEquals(true, transaction1.isDirty()); + + transaction1.rollback(savePoint1, false); + assertEquals(true, transaction1.isDirty()); + + // Didn`t make any modification prior to savepoint0 + transaction1.rollback(savePoint0, false); + assertEquals(false, transaction1.isDirty()); + + transaction1.rollback(false); + assertEquals(false, transaction1.isDirty()); + } + public void flow1(boolean commitBegin, boolean commitEnd) throws Exception + { + CDOSession session = openModel1Session(); + session.getPackageRegistry().putEPackage(Model1Package.eINSTANCE); + + CDOTransactionImpl transaction1 = (CDOTransactionImpl)session.openTransaction(); + // Client1 + CDOResource resource1 = transaction1.createResource("/test1"); + Category category3,category2, category4; + + Company company1 = Model1Factory.eINSTANCE.createCompany(); + resource1.getContents().add(company1); + Category category1 = Model1Factory.eINSTANCE.createCategory(); + company1.getCategories().add(category1); + + CDOSavePoint savePoint1 = transaction1.createSavePoint(); + + // Modification for savePoint1 + Company company2 = Model1Factory.eINSTANCE.createCompany(); + resource1.getContents().add(company2); + company1.setCity("CITY1"); + + assertEquals(2, resource1.getContents().size()); + + // Rollback + transaction1.rollback(savePoint1, false); + + if (commitBegin) + transaction1.commit(); + + + { + assertEquals(null, company1.getCity()); + assertEquals(1, resource1.getContents().size()); + company1.setCity("CITY1"); + category2 = Model1Factory.eINSTANCE.createCategory(); + company1.getCategories().add(category2); + } + + CDOSavePoint savePoint2 = transaction1.createSavePoint(); + { + company1.setCity("CITY2"); + category3 = Model1Factory.eINSTANCE.createCategory(); + company1.getCategories().add(category3); + } + + + transaction1.createSavePoint(); + { + company1.setCity("CITY3"); + assertEquals(3, company1.getCategories().size()); + category4 = Model1Factory.eINSTANCE.createCategory(); + company1.getCategories().add(category4); + + } + + transaction1.rollback(savePoint2, false); + + assertEquals(true , transaction1.isDirty()); + + // Test NEW TO NEW + assertEquals(false , FSMUtil.isTransient(company1)); + + // Test NEW TO TRANSIENT (2 step back) + assertEquals(true , FSMUtil.isTransient(category3)); + assertEquals(false, transaction1.getNewObjects().containsKey(((CDOObject)category3).cdoID())); + + // Test NEW TO TRANSIENT (1 step back) + assertEquals(true , FSMUtil.isTransient(category4)); + assertEquals(false, transaction1.getNewObjects().containsKey(((CDOObject)category4).cdoID())); + + // Test NEW TO NEW + assertEquals(false , FSMUtil.isTransient(category2)); + assertEquals(true, transaction1.getNewObjects().containsKey(((CDOObject)category2).cdoID())); + + // Test rollback NEW + assertEquals("CITY1", company1.getCity()); + assertEquals(2, company1.getCategories().size()); + if (commitEnd) + { + transaction1.commit(); + + assertClean(company1, transaction1); + assertClean(category2, transaction1); + + assertEquals("CITY1", company1.getCity()); + + assertEquals(2, company1.getCategories().size()); + + assertEquals(null, transaction1.getLastSavePoint().getPreviousSavePoint()); + } + else + { + transaction1.rollback(false); + assertEquals(false , transaction1.isDirty()); + assertEquals(null , transaction1.getLastSavePoint().getNextSavePoint()); + assertEquals(null , transaction1.getLastSavePoint().getPreviousSavePoint()); + + assertEquals(commitBegin , !FSMUtil.isTransient(company1)); + + assertEquals(commitBegin , !FSMUtil.isTransient(resource1)); + + } + + } +} diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/CDOSavePoint.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/CDOSavePoint.java new file mode 100644 index 0000000000..d7c69b28b6 --- /dev/null +++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/CDOSavePoint.java @@ -0,0 +1,22 @@ +/*************************************************************************** + * Copyright (c) 2004 - 2008 Simon McDuff, Canada. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Simon McDuff - initial API and implementation + **************************************************************************/ +package org.eclipse.emf.cdo; + +/** + * @author Simon McDuff + */ +public interface CDOSavePoint +{ + CDOTransaction getTransaction(); + CDOSavePoint getNextSavePoint(); + CDOSavePoint getPreviousSavePoint(); + +} diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/CDOTransaction.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/CDOTransaction.java index 34b3f49195..1360d9bb6e 100644 --- a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/CDOTransaction.java +++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/CDOTransaction.java @@ -8,6 +8,7 @@ * Contributors: * Eike Stepper - initial API and implementation * Simon McDuff - https://bugs.eclipse.org/bugs/show_bug.cgi?id=201266 + * Simon McDuff - https://bugs.eclipse.org/bugs/show_bug.cgi?id=215688 **************************************************************************/ package org.eclipse.emf.cdo; @@ -51,6 +52,12 @@ public interface CDOTransaction extends CDOView public void commit() throws TransactionException; public void rollback(boolean remote); + + public void rollback(CDOSavePoint savePoint, boolean remote); + + public CDOSavePoint createSavePoint(); + + public CDOSavePoint getLastSavePoint(); public void addHandler(CDOTransactionHandler handler); diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/CDOSavePointImpl.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/CDOSavePointImpl.java new file mode 100644 index 0000000000..c3f6b7343c --- /dev/null +++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/CDOSavePointImpl.java @@ -0,0 +1,124 @@ +/*************************************************************************** + * Copyright (c) 2004 - 2008 Simon McDuff, Canada. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Simon McDuff - initial API and implementation + * + **************************************************************************/ +package org.eclipse.emf.internal.cdo; + +import org.eclipse.emf.cdo.CDOObject; +import org.eclipse.emf.cdo.CDOSavePoint; +import org.eclipse.emf.cdo.CDOTransaction; +import org.eclipse.emf.cdo.eresource.CDOResource; +import org.eclipse.emf.cdo.internal.protocol.revision.CDORevisionImpl; +import org.eclipse.emf.cdo.protocol.id.CDOID; +import org.eclipse.emf.cdo.protocol.revision.delta.CDORevisionDelta; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * @author Simon McDuff + */ +public class CDOSavePointImpl implements CDOSavePoint +{ + private CDOTransactionImpl transaction = null; + + private Map<CDOID, CDOResource> newResources = new HashMap<CDOID, CDOResource>(); + + private Map<CDOID, CDOObject> newObjects = new HashMap<CDOID, CDOObject>(); + + private Map<CDOID, CDORevisionImpl> baseNewObjects = new HashMap<CDOID, CDORevisionImpl>(); + + private Map<CDOID, CDOObject> dirtyObjects = new HashMap<CDOID, CDOObject>(); + + private ConcurrentMap<CDOID, CDORevisionDelta> revisionDeltas = new ConcurrentHashMap<CDOID, CDORevisionDelta>(); + + private CDOSavePointImpl previousSavePoint = null; + + private CDOSavePointImpl nextSavePoint = null; + + private boolean isDirty = false; + + public CDOSavePointImpl(CDOTransactionImpl transaction, CDOSavePointImpl lastSavePoint) + { + this.transaction = transaction; + this.isDirty = transaction.isDirty(); + this.previousSavePoint = lastSavePoint; + if (this.previousSavePoint != null) + this.previousSavePoint.setNextSavePoint(this); + } + + public void clear() + { + newResources.clear(); + newObjects.clear(); + dirtyObjects.clear(); + revisionDeltas.clear(); + baseNewObjects.clear(); + } + + public boolean isDirty() + { + return isDirty; + } + + public Map<CDOID, CDOResource> getNewResources() + { + return newResources; + } + + public Map<CDOID, CDOObject> getNewObjects() + { + return newObjects; + } + + public Map<CDOID, CDOObject> getDirtyObjects() + { + return dirtyObjects; + } + + public ConcurrentMap<CDOID, CDORevisionDelta> getRevisionDeltas() + { + return revisionDeltas; + } + + + public Map<CDOID, CDORevisionImpl> getBaseNewObjects() + { + return baseNewObjects; + } + + public CDOSavePointImpl getPreviousSavePoint() + { + return previousSavePoint; + } + + public CDOSavePointImpl getNextSavePoint() + { + return nextSavePoint; + } + + public void setPreviousSavePoint(CDOSavePointImpl previousSavePoint) + { + this.previousSavePoint = previousSavePoint; + } + + public void setNextSavePoint(CDOSavePointImpl nextSavePoint) + { + this.nextSavePoint = nextSavePoint; + } + + public CDOTransaction getTransaction() + { + return transaction; + } + +} diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/CDOStateMachine.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/CDOStateMachine.java index 4e133d1f2e..c7e6acd864 100644 --- a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/CDOStateMachine.java +++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/CDOStateMachine.java @@ -8,9 +8,11 @@ * Contributors: * Eike Stepper - initial API and implementation * Simon McDuff - https://bugs.eclipse.org/bugs/show_bug.cgi?id=201266 + * Simon McDuff - https://bugs.eclipse.org/bugs/show_bug.cgi?id=215688 **************************************************************************/ package org.eclipse.emf.internal.cdo; +import org.eclipse.emf.cdo.CDOObject; import org.eclipse.emf.cdo.CDORevisionManager; import org.eclipse.emf.cdo.CDOSession; import org.eclipse.emf.cdo.CDOState; @@ -91,7 +93,7 @@ public final class CDOStateMachine extends FiniteStateMachine<CDOState, CDOEvent init(CDOState.NEW, CDOEvent.ATTACH, FAIL); init(CDOState.NEW, CDOEvent.DETACH, new DetachTransition()); init(CDOState.NEW, CDOEvent.READ, IGNORE); - init(CDOState.NEW, CDOEvent.WRITE, IGNORE); + init(CDOState.NEW, CDOEvent.WRITE, new NewObjectWriteTransition()); init(CDOState.NEW, CDOEvent.INVALIDATE, FAIL); init(CDOState.NEW, CDOEvent.RELOAD, FAIL); init(CDOState.NEW, CDOEvent.COMMIT, new CommitTransition()); @@ -354,6 +356,7 @@ public final class CDOStateMachine extends FiniteStateMachine<CDOState, CDOEvent object.cdoInternalSetID(id); object.cdoInternalSetResource(data.resource); object.cdoInternalSetView(data.view); + changeState(object, CDOState.PREPARED); // Create new revision @@ -385,6 +388,7 @@ public final class CDOStateMachine extends FiniteStateMachine<CDOState, CDOEvent { CDOTransactionImpl transaction = (CDOTransactionImpl)object.cdoView(); object.cdoInternalPostAttach(); + changeState(object, CDOState.NEW); // Attach content tree @@ -494,6 +498,26 @@ public final class CDOStateMachine extends FiniteStateMachine<CDOState, CDOEvent /** * @author Simon McDuff */ + private final class NewObjectWriteTransition implements ITransition<CDOState, CDOEvent, InternalCDOObject, Object> + { + public void execute(InternalCDOObject object, CDOState state, CDOEvent event, Object featureDelta) + { + CDOViewImpl view = (CDOViewImpl)object.cdoView(); + CDOTransactionImpl transaction = view.toTransaction(); + + // Register Delta for new objects only if objectA doesn't belong to this savepoint + if (transaction.getLastSavePoint().getPreviousSavePoint() == null || featureDelta == null) return; + + Map<CDOID, ? extends CDOObject> map = object instanceof CDOResource ? transaction.getLastSavePoint() + .getNewResources() : transaction.getLastSavePoint().getNewObjects(); + + if (!map.containsKey(object.cdoID())) transaction.registerFeatureDelta(object, (CDOFeatureDelta)featureDelta); + } + } + + /** + * @author Simon McDuff + */ private final class RewriteTransition implements ITransition<CDOState, CDOEvent, InternalCDOObject, Object> { public void execute(InternalCDOObject object, CDOState state, CDOEvent event, Object featureDelta) diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/CDOTransactionImpl.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/CDOTransactionImpl.java index 17ddf123e6..f8e45022de 100644 --- a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/CDOTransactionImpl.java +++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/CDOTransactionImpl.java @@ -9,10 +9,13 @@ * Eike Stepper - initial API and implementation * Simon McDuff - https://bugs.eclipse.org/bugs/show_bug.cgi?id=201266 * Simon McDuff - https://bugs.eclipse.org/bugs/show_bug.cgi?id=233314 + * Simon McDuff - https://bugs.eclipse.org/bugs/show_bug.cgi?id=215688 **************************************************************************/ package org.eclipse.emf.internal.cdo; import org.eclipse.emf.cdo.CDOObject; +import org.eclipse.emf.cdo.CDOSavePoint; +import org.eclipse.emf.cdo.CDOState; import org.eclipse.emf.cdo.CDOTransaction; import org.eclipse.emf.cdo.CDOTransactionConflictEvent; import org.eclipse.emf.cdo.CDOTransactionFinishedEvent; @@ -21,12 +24,15 @@ import org.eclipse.emf.cdo.CDOTransactionStartedEvent; import org.eclipse.emf.cdo.eresource.CDOResource; import org.eclipse.emf.cdo.eresource.impl.CDOResourceImpl; import org.eclipse.emf.cdo.internal.protocol.model.InternalCDOPackage; +import org.eclipse.emf.cdo.internal.protocol.revision.CDORevisionImpl; +import org.eclipse.emf.cdo.internal.protocol.revision.delta.CDORevisionDeltaImpl; import org.eclipse.emf.cdo.internal.protocol.revision.delta.InternalCDORevisionDelta; import org.eclipse.emf.cdo.protocol.id.CDOID; import org.eclipse.emf.cdo.protocol.id.CDOIDTemp; import org.eclipse.emf.cdo.protocol.id.CDOIDUtil; import org.eclipse.emf.cdo.protocol.model.CDOPackage; import org.eclipse.emf.cdo.protocol.revision.delta.CDOFeatureDelta; +import org.eclipse.emf.cdo.protocol.revision.delta.CDOListFeatureDelta; import org.eclipse.emf.cdo.protocol.revision.delta.CDORevisionDelta; import org.eclipse.emf.cdo.protocol.revision.delta.CDORevisionDeltaUtil; import org.eclipse.emf.cdo.util.CDOUtil; @@ -41,6 +47,7 @@ import org.eclipse.net4j.internal.util.event.Notifier; import org.eclipse.net4j.internal.util.om.trace.ContextTracer; import org.eclipse.net4j.signal.failover.IFailOverStrategy; import org.eclipse.net4j.util.ImplementationError; +import org.eclipse.net4j.util.collection.MultiMap; import org.eclipse.net4j.util.transaction.TransactionException; import org.eclipse.emf.common.util.URI; @@ -51,13 +58,12 @@ import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; /** * @author Eike Stepper @@ -73,13 +79,9 @@ public class CDOTransactionImpl extends CDOViewImpl implements CDOTransaction private List<CDOPackage> newPackages; - private Map<CDOID, CDOResource> newResources = new HashMap<CDOID, CDOResource>(); + private CDOSavePointImpl lastSavePoint = new CDOSavePointImpl(this, null); - private Map<CDOID, CDOObject> newObjects = new HashMap<CDOID, CDOObject>(); - - private Map<CDOID, CDOObject> dirtyObjects = new HashMap<CDOID, CDOObject>(); - - private ConcurrentMap<CDOID, CDORevisionDelta> revisionDeltas = new ConcurrentHashMap<CDOID, CDORevisionDelta>(); + private CDOSavePointImpl firstSavePoint = lastSavePoint; private boolean dirty; @@ -159,32 +161,6 @@ public class CDOTransactionImpl extends CDOViewImpl implements CDOTransaction return Collections.unmodifiableList(newPackages); } - public Map<CDOID, CDOResource> getNewResources() - { - return Collections.unmodifiableMap(newResources); - } - - public Map<CDOID, CDOObject> getNewObjects() - { - return Collections.unmodifiableMap(newObjects); - } - - /** - * TODO Consolidate with {@link #getRevisionDeltas()} - */ - public Map<CDOID, CDOObject> getDirtyObjects() - { - return Collections.unmodifiableMap(dirtyObjects); - } - - /** - * TODO Consolidate with {@link #getDirtyObjects()} - */ - public Map<CDOID, CDORevisionDelta> getRevisionDeltas() - { - return Collections.unmodifiableMap(revisionDeltas); - } - public CDOIDTemp getNextTemporaryID() { return CDOIDUtil.createCDOIDTempObject(++lastTemporaryID); @@ -209,6 +185,11 @@ public class CDOTransactionImpl extends CDOViewImpl implements CDOTransaction } } + public CDOSavePointImpl getLastSavePoint() + { + return this.lastSavePoint; + } + public void commit() throws TransactionException { if (dirty) @@ -227,8 +208,12 @@ public class CDOTransactionImpl extends CDOViewImpl implements CDOTransaction { newPackages = analyzeNewPackages(); - preCommit(newObjects); - preCommit(dirtyObjects); + for (CDOSavePointImpl itrSavePoint = lastSavePoint; itrSavePoint != null; itrSavePoint = itrSavePoint + .getPreviousSavePoint()) + { + preCommit(itrSavePoint.getNewObjects()); + preCommit(itrSavePoint.getDirtyObjects()); + } CDOSessionImpl session = getSession(); IChannel channel = session.getChannel(); @@ -242,15 +227,20 @@ public class CDOTransactionImpl extends CDOViewImpl implements CDOTransaction throw new TransactionException(rollbackMessage); } - postCommit(newResources, result); - postCommit(newObjects, result); - postCommit(dirtyObjects, result); - + for (CDOSavePointImpl irtSavePoint = lastSavePoint; irtSavePoint != null; irtSavePoint = irtSavePoint + .getPreviousSavePoint()) + { + postCommit(irtSavePoint.getNewResources(), result); + postCommit(irtSavePoint.getNewObjects(), result); + postCommit(irtSavePoint.getDirtyObjects(), result); + } for (CDOPackage newPackage : newPackages) { ((InternalCDOPackage)newPackage).setPersistent(true); } + Map<CDOID, CDOObject> dirtyObjects = this.getDirtyObjects(); + if (!dirtyObjects.isEmpty()) { session.notifyInvalidation(result.getTimeStamp(), dirtyObjects.keySet(), this); @@ -273,19 +263,36 @@ public class CDOTransactionImpl extends CDOViewImpl implements CDOTransaction public void rollback(boolean remote) { - if (dirty) + rollback(firstSavePoint, remote); + + cleanUp(); + } + + private Set<CDOID> rollbackTo(CDOSavePoint savePoint, boolean remote) + { + Set<CDOID> newObjectsDelta = new HashSet<CDOID>(); + + boolean isActiveSavePoint = false; + + // Start from the last savepoint and come back up to the active + + for (CDOSavePointImpl itrSavePoint = lastSavePoint; itrSavePoint != null; itrSavePoint = itrSavePoint + .getPreviousSavePoint()) { - if (TRACER.isEnabled()) - { - TRACER.trace("commit()"); - } - try + if (isActiveSavePoint == false) { + // Rollback new objects created after the save point + Map<CDOID, CDOResource> newResources = itrSavePoint.getNewResources(); + Map<CDOID, CDOObject> newObjects = itrSavePoint.getNewObjects(); + if (!newResources.isEmpty()) { for (CDOObject newResource : newResources.values()) { + // TODO Should call detach transition : not there yet + ((InternalCDOObject)newResource).cdoInternalSetState(CDOState.TRANSIENT); + removeObject(newResource.cdoID()); getResourceSet().getResources().remove(newResource); } @@ -295,38 +302,167 @@ public class CDOTransactionImpl extends CDOViewImpl implements CDOTransaction { for (CDOObject newObject : newObjects.values()) { + ((InternalCDOObject)newObject).cdoInternalSetState(CDOState.TRANSIENT); + removeObject(newObject.cdoID()); + + // TODO Should call detach transition : not there yet + // TODO How to remove it from Resource? + // CDOStateMachine.INSTANCE.detach(newObject); } } - if (!dirtyObjects.isEmpty()) + Map<CDOID, CDORevisionDelta> revisionDeltas = itrSavePoint.getRevisionDeltas(); + + if (!revisionDeltas.isEmpty()) { - for (CDOObject dirtyObject : dirtyObjects.values()) + for (CDORevisionDelta dirtyObject : revisionDeltas.values()) { - CDOStateMachine.INSTANCE.rollback((InternalCDOObject)dirtyObject, remote); + if (dirtyObject.getID().isTemporary()) newObjectsDelta.add(dirtyObject.getID()); } } + } - cleanUp(); - - Map<CDOIDTemp, CDOID> idMappings = Collections.emptyMap(); - - fireEvent(new FinishedEvent(CDOTransactionFinishedEvent.Type.ROLLED_BACK, idMappings)); - - for (CDOTransactionHandler handler : getHandlers()) + // Rollback every persisted objects + Map<CDOID, CDOObject> dirtyObjects = itrSavePoint.getDirtyObjects(); + if (!dirtyObjects.isEmpty()) + { + for (CDOObject dirtyObject : dirtyObjects.values()) { - handler.rollingbackTransaction(this); + CDOStateMachine.INSTANCE.rollback((InternalCDOObject)dirtyObject, remote); } } - catch (RuntimeException ex) + if (savePoint == itrSavePoint) isActiveSavePoint = true; + } + + return newObjectsDelta; + } + + private void loadSavePoint(CDOSavePoint savePoint, Set<CDOID> newObjectsDelta) + { + Map<CDOID, CDOObject> dirtyObjects = getDirtyObjects(); + Map<CDOID, CDOObject> newObjMaps = getNewObjects(); + + Map<CDOID, CDOResource> newResources = getNewResources(); + + Map<CDOID, CDORevisionImpl> newBaseRevision = getBaseNewObjects(); + + // Reload the objects (NEW) with their base. + for (CDOID newObject : newObjectsDelta) + { + InternalCDOObject object = (InternalCDOObject)newObjMaps.get(newObject); + if (object == null) object = (InternalCDOObject)newResources.get(newObject); + + CDORevisionImpl revisionImpl = newBaseRevision.get(newObject); + + if (revisionImpl != null) { - throw ex; + object.cdoInternalSetRevision(new CDORevisionImpl(revisionImpl)); + + // Load the object from revision to EObject + object.cdoInternalPostLoad(); } - catch (Exception ex) + } + + for (CDOSavePointImpl itrSavePoint = firstSavePoint; itrSavePoint != savePoint; itrSavePoint = itrSavePoint + .getNextSavePoint()) + { + CDOObjectMerger merger = new CDOObjectMerger(); + for (CDORevisionDelta delta : itrSavePoint.getRevisionDeltas().values()) { - throw new TransactionException(ex); + Map<CDOID, CDOObject> map = delta.getID().isTemporary() ? newObjMaps : dirtyObjects; + + InternalCDOObject object = (InternalCDOObject)map.get(delta.getID()); + if (object == null) object = (InternalCDOObject)newResources.get(delta.getID()); + + // Change state of the objects + merger.merge(object, delta); + + // Load the object from revision to EObject + object.cdoInternalPostLoad(); } } + dirty = ((CDOSavePointImpl)savePoint).isDirty(); + } + + public void rollback(CDOSavePoint savePoint, boolean remote) + { + if (savePoint == null || savePoint.getTransaction() != this) + throw new IllegalArgumentException("Save point to rollback doesn't belong to this transaction: " + savePoint); + + if (TRACER.isEnabled()) + { + TRACER.trace("commit()"); + } + + try + { + + boolean isPresent = false; + + for (CDOSavePointImpl indexsavePoint = lastSavePoint; indexsavePoint != null; indexsavePoint = indexsavePoint + .getPreviousSavePoint()) + { + if (indexsavePoint == savePoint) + { + isPresent = true; + break; + } + } + if (isPresent == false) + throw new IllegalArgumentException("Save point to rollback isn't valid for this transaction: " + savePoint); + + // Rollback objects + Set<CDOID> newObjectsDelta = rollbackTo(savePoint, remote); + + this.lastSavePoint = (CDOSavePointImpl)savePoint; + + // Make savePoint active. Erase savepoint that could have be after + this.lastSavePoint.setNextSavePoint(null); + this.lastSavePoint.clear(); + + // Load from first savepoint up to current savepoint + loadSavePoint(lastSavePoint, newObjectsDelta); + + Map<CDOIDTemp, CDOID> idMappings = Collections.emptyMap(); + + fireEvent(new FinishedEvent(CDOTransactionFinishedEvent.Type.ROLLED_BACK, idMappings)); + + for (CDOTransactionHandler handler : getHandlers()) + { + handler.rollingbackTransaction(this); + } + } + catch (RuntimeException ex) + { + throw ex; + } + catch (Exception ex) + { + throw new TransactionException(ex); + } + } + + public CDOSavePoint createSavePoint() + { + // Take a copy of all new objects for the current save point + for (CDOObject object : lastSavePoint.getNewObjects().values()) + { + // Load instance to revision + ((InternalCDOObject)object).cdoInternalPreCommit(); + lastSavePoint.getBaseNewObjects().put(object.cdoID(), new CDORevisionImpl((CDORevisionImpl)object.cdoRevision())); + } + // Take a copy of all new resources for the current save point + for (CDOObject object : lastSavePoint.getNewResources().values()) + { + // Load instance to revision + ((InternalCDOObject)object).cdoInternalPreCommit(); + lastSavePoint.getBaseNewObjects().put(object.cdoID(), new CDORevisionImpl((CDORevisionImpl)object.cdoRevision())); + } + + lastSavePoint = new CDOSavePointImpl(this, lastSavePoint); + + return lastSavePoint; } @Override @@ -349,24 +485,25 @@ public class CDOTransactionImpl extends CDOViewImpl implements CDOTransaction if (object instanceof CDOResourceImpl) { - register(newResources, object); + register(this.lastSavePoint.getNewResources(), object); } else { - register(newObjects, object); + register(this.lastSavePoint.getNewObjects(), object); } } public void registerFeatureDelta(InternalCDOObject object, CDOFeatureDelta featureDelta) { - InternalCDORevisionDelta revisionDelta = (InternalCDORevisionDelta)revisionDeltas.get(object.cdoID()); + CDORevisionDelta revisionDelta = (CDORevisionDelta)lastSavePoint.getRevisionDeltas().get(object.cdoID()); + if (revisionDelta == null) { - revisionDelta = (InternalCDORevisionDelta)CDORevisionDeltaUtil.create(object.cdoRevision()); - revisionDeltas.put(object.cdoID(), revisionDelta); + revisionDelta = (CDORevisionDelta)CDORevisionDeltaUtil.create(object.cdoRevision()); + lastSavePoint.getRevisionDeltas().put(object.cdoID(), revisionDelta); } - revisionDelta.addFeatureDelta(featureDelta); + ((InternalCDORevisionDelta)revisionDelta).addFeatureDelta(featureDelta); for (CDOTransactionHandler handler : getHandlers()) { handler.modifyingObject(this, object, featureDelta); @@ -375,7 +512,7 @@ public class CDOTransactionImpl extends CDOViewImpl implements CDOTransaction public void registerRevisionDelta(CDORevisionDelta revisionDelta) { - revisionDeltas.putIfAbsent(revisionDelta.getID(), revisionDelta); + lastSavePoint.getRevisionDeltas().putIfAbsent(revisionDelta.getID(), revisionDelta); } public void registerDirty(InternalCDOObject object, CDOFeatureDelta featureDelta) @@ -386,7 +523,7 @@ public class CDOTransactionImpl extends CDOViewImpl implements CDOTransaction } registerFeatureDelta(object, featureDelta); - register(dirtyObjects, object); + register(lastSavePoint.getDirtyObjects(), object); } @SuppressWarnings("unchecked") @@ -409,7 +546,7 @@ public class CDOTransactionImpl extends CDOViewImpl implements CDOTransaction { // Find all used classes and their super classes Set<EClass> usedClasses = new HashSet<EClass>(); - for (CDOObject object : newObjects.values()) + for (CDOObject object : getNewObjects().values()) { EClass usedClass = object.eClass(); if (usedClasses.add(usedClass)) @@ -486,15 +623,101 @@ public class CDOTransactionImpl extends CDOViewImpl implements CDOTransaction private void cleanUp() { newPackages = null; - newResources.clear(); - newObjects.clear(); - dirtyObjects.clear(); - revisionDeltas.clear(); + lastSavePoint = firstSavePoint; + firstSavePoint.clear(); + firstSavePoint.setNextSavePoint(null); dirty = false; conflict = false; lastTemporaryID = 0; } + public Map<CDOID, CDOObject> getDirtyObjects() + { + if (this.lastSavePoint.getPreviousSavePoint() == null) return lastSavePoint.getDirtyObjects(); + + MultiMap.ListBased<CDOID, CDOObject> dirtyObjects = new MultiMap.ListBased<CDOID, CDOObject>(); + + for (CDOSavePointImpl savePoint = lastSavePoint; savePoint != null; savePoint = savePoint.getPreviousSavePoint()) + { + dirtyObjects.getDelegates().add(savePoint.getDirtyObjects()); + } + return dirtyObjects; + } + + public Map<CDOID, CDOObject> getNewObjects() + { + if (this.lastSavePoint.getPreviousSavePoint() == null) + return Collections.unmodifiableMap(lastSavePoint.getNewObjects()); + + MultiMap.ListBased<CDOID, CDOObject> newObjects = new MultiMap.ListBased<CDOID, CDOObject>(); + for (CDOSavePointImpl savePoint = lastSavePoint; savePoint != null; savePoint = savePoint.getPreviousSavePoint()) + { + newObjects.getDelegates().add(savePoint.getNewObjects()); + } + return newObjects; + } + + public Map<CDOID, CDOResource> getNewResources() + { + if (this.lastSavePoint.getPreviousSavePoint() == null) + return Collections.unmodifiableMap(lastSavePoint.getNewResources()); + + MultiMap.ListBased<CDOID, CDOResource> newResources = new MultiMap.ListBased<CDOID, CDOResource>(); + for (CDOSavePointImpl savePoint = lastSavePoint; savePoint != null; savePoint = savePoint.getPreviousSavePoint()) + { + newResources.getDelegates().add(savePoint.getNewResources()); + } + return newResources; + } + + public Map<CDOID, CDORevisionImpl> getBaseNewObjects() + { + if (this.lastSavePoint.getPreviousSavePoint() == null) + return Collections.unmodifiableMap(lastSavePoint.getBaseNewObjects()); + + MultiMap.ListBased<CDOID, CDORevisionImpl> newObjects = new MultiMap.ListBased<CDOID, CDORevisionImpl>(); + for (CDOSavePointImpl savePoint = lastSavePoint; savePoint != null; savePoint = savePoint.getPreviousSavePoint()) + { + newObjects.getDelegates().add(savePoint.getBaseNewObjects()); + } + return newObjects; + } + + public Map<CDOID, CDORevisionDelta> getRevisionDeltas() + { + if (this.lastSavePoint.getPreviousSavePoint() == null) return lastSavePoint.getRevisionDeltas(); + + // We need to combined the result for all delta in different SavePoint + Map<CDOID, CDORevisionDelta> revisionDeltas = new ConcurrentHashMap<CDOID, CDORevisionDelta>(); + + for (CDOSavePointImpl savePoint = firstSavePoint; savePoint != null; savePoint = savePoint.getNextSavePoint()) + { + for (Entry<CDOID, CDORevisionDelta> entry : savePoint.getRevisionDeltas().entrySet()) + { + // Skipping temporary + if (entry.getKey().isTemporary()) continue; + + CDORevisionDeltaImpl revisionDelta = (CDORevisionDeltaImpl)revisionDeltas.get(entry.getKey()); + if (revisionDelta == null) + revisionDeltas.put(entry.getKey(), entry.getValue()); + else + { + for (CDOFeatureDelta delta : entry.getValue().getFeatureDeltas()) + { + if (delta instanceof CDOListFeatureDelta) + { + for (CDOFeatureDelta subDelta : ((CDOListFeatureDelta)delta).getListChanges()) + revisionDelta.addFeatureDelta(subDelta); + } + else + revisionDelta.addFeatureDelta(delta); + } + } + } + } + return revisionDeltas; + } + /** * @author Eike Stepper */ diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/protocol/CommitTransactionRequest.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/protocol/CommitTransactionRequest.java index c09232acf1..d58c75dc9a 100644 --- a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/protocol/CommitTransactionRequest.java +++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/protocol/CommitTransactionRequest.java @@ -8,6 +8,7 @@ * Contributors: * Eike Stepper - initial API and implementation * Simon McDuff - https://bugs.eclipse.org/bugs/show_bug.cgi?id=201266 + * Simon McDuff - https://bugs.eclipse.org/bugs/show_bug.cgi?id=215688 **************************************************************************/ package org.eclipse.emf.internal.cdo.protocol; @@ -39,6 +40,7 @@ import java.io.IOException; import java.util.Collection; import java.util.Iterator; import java.util.List; +import java.util.Map; /** * @author Eike Stepper @@ -67,12 +69,12 @@ public class CommitTransactionRequest extends CDOClientRequest<CommitTransaction List<CDOPackage> newPackages = transaction.getNewPackages(); Collection<CDOResource> newResources = transaction.getNewResources().values(); Collection<CDOObject> newObjects = transaction.getNewObjects().values(); - Collection<CDORevisionDelta> dirtyObjects = transaction.getRevisionDeltas().values(); + Collection<CDORevisionDelta> revisionDeltas = transaction.getRevisionDeltas().values(); out.writeInt(transaction.getViewID()); out.writeInt(newPackages.size()); out.writeInt(newResources.size() + newObjects.size()); - out.writeInt(dirtyObjects.size()); + out.writeInt(revisionDeltas.size()); if (PROTOCOL.isEnabled()) { @@ -99,14 +101,14 @@ public class CommitTransactionRequest extends CDOClientRequest<CommitTransaction if (PROTOCOL.isEnabled()) { - PROTOCOL.format("Writing {0} dirty objects", dirtyObjects.size()); + PROTOCOL.format("Writing {0} dirty objects", revisionDeltas.size()); } - + Map<CDOID, CDOObject> dirtyObjects = transaction.getDirtyObjects(); RevisionAdjuster revisionAdjuster = new RevisionAdjuster(transaction); - for (CDORevisionDelta revisionDelta : dirtyObjects) + for (CDORevisionDelta revisionDelta : revisionDeltas) { revisionDelta.write(out, transaction); - CDOObject object = transaction.getDirtyObjects().get(revisionDelta.getID()); + CDOObject object = dirtyObjects.get(revisionDelta.getID()); InternalCDORevision revision = (InternalCDORevision)object.cdoRevision(); revisionAdjuster.adjustRevision(revision, revisionDelta); } |