diff options
author | Caspar De Groot | 2010-09-13 05:34:41 +0000 |
---|---|---|
committer | Caspar De Groot | 2010-09-13 05:34:41 +0000 |
commit | 37caac1363b62130146f43a45e91b74dcaa09198 (patch) | |
tree | 48f4339cb18bdec9a4145acc4e9c9f924c3abe10 | |
parent | 51739dba02253c70c474a5556d5718548141d043 (diff) | |
download | cdo-37caac1363b62130146f43a45e91b74dcaa09198.tar.gz cdo-37caac1363b62130146f43a45e91b74dcaa09198.tar.xz cdo-37caac1363b62130146f43a45e91b74dcaa09198.zip |
[312535] Partial commits
https://bugs.eclipse.org/bugs/show_bug.cgi?id=312535
16 files changed, 1965 insertions, 24 deletions
diff --git a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/revision/delta/CDOSetFeatureDelta.java b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/revision/delta/CDOSetFeatureDelta.java index b929ff5524..a8cadf0aec 100644 --- a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/revision/delta/CDOSetFeatureDelta.java +++ b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/revision/delta/CDOSetFeatureDelta.java @@ -17,7 +17,17 @@ package org.eclipse.emf.cdo.common.revision.delta; */ public interface CDOSetFeatureDelta extends CDOFeatureDelta { + /** + * @since 4.0 + */ + public static final Object UNSPECIFIED = new Object(); + public int getIndex(); public Object getValue(); + + /** + * @since 4.0 + */ + public Object getOldValue(); } diff --git a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/internal/common/revision/delta/CDORevisionDeltaImpl.java b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/internal/common/revision/delta/CDORevisionDeltaImpl.java index d2f421d7f4..9411b0ca60 100644 --- a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/internal/common/revision/delta/CDORevisionDeltaImpl.java +++ b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/internal/common/revision/delta/CDORevisionDeltaImpl.java @@ -364,7 +364,7 @@ public class CDORevisionDeltaImpl implements InternalCDORevisionDelta } else { - addFeatureDelta(new CDOSetFeatureDeltaImpl(feature, 0, dirtyValue)); + addFeatureDelta(new CDOSetFeatureDeltaImpl(feature, 0, dirtyValue, originValue)); } } } diff --git a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/internal/common/revision/delta/CDOSetFeatureDeltaImpl.java b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/internal/common/revision/delta/CDOSetFeatureDeltaImpl.java index 9a29cfaa27..a6ada0529c 100644 --- a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/internal/common/revision/delta/CDOSetFeatureDeltaImpl.java +++ b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/internal/common/revision/delta/CDOSetFeatureDeltaImpl.java @@ -23,6 +23,7 @@ import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EStructuralFeature; import java.io.IOException; +import java.text.MessageFormat; /** * @author Simon McDuff @@ -30,11 +31,19 @@ import java.io.IOException; public class CDOSetFeatureDeltaImpl extends CDOSingleValueFeatureDeltaImpl implements CDOSetFeatureDelta, ListTargetAdding { + private Object oldValue = CDOSetFeatureDelta.UNSPECIFIED; + public CDOSetFeatureDeltaImpl(EStructuralFeature feature, int index, Object value) { super(feature, index, value); } + public CDOSetFeatureDeltaImpl(EStructuralFeature feature, int index, Object value, Object oldValue) + { + super(feature, index, value); + this.oldValue = oldValue; + } + public CDOSetFeatureDeltaImpl(CDODataInput in, EClass eClass) throws IOException { super(in, eClass); @@ -47,7 +56,7 @@ public class CDOSetFeatureDeltaImpl extends CDOSingleValueFeatureDeltaImpl imple public CDOFeatureDelta copy() { - return new CDOSetFeatureDeltaImpl(getFeature(), getIndex(), getValue()); + return new CDOSetFeatureDeltaImpl(getFeature(), getIndex(), getValue(), getOldValue()); } public void apply(CDORevision revision) @@ -59,4 +68,25 @@ public class CDOSetFeatureDeltaImpl extends CDOSingleValueFeatureDeltaImpl imple { visitor.visit(this); } + + public Object getOldValue() + { + return oldValue; + } + + @Override + protected String toStringAdditional() + { + String oldValueForMessage; + if (oldValue != CDOSetFeatureDelta.UNSPECIFIED) + { + oldValueForMessage = oldValue == null ? "null" : oldValue.toString(); + } + else + { + oldValueForMessage = "UNSPECIFIED"; //$NON-NLS-1$ + } + + return super.toStringAdditional() + MessageFormat.format(", oldValue={0}", oldValueForMessage); //$NON-NLS-1$ + } } diff --git a/plugins/org.eclipse.emf.cdo.tests/META-INF/MANIFEST.MF b/plugins/org.eclipse.emf.cdo.tests/META-INF/MANIFEST.MF index 6258ee768b..f9e4cc4e79 100644 --- a/plugins/org.eclipse.emf.cdo.tests/META-INF/MANIFEST.MF +++ b/plugins/org.eclipse.emf.cdo.tests/META-INF/MANIFEST.MF @@ -15,6 +15,7 @@ Require-Bundle: org.eclipse.net4j.tests;bundle-version="[3.0.0,4.0.0)";visibilit org.eclipse.net4j.db.h2;bundle-version="[4.0.0,5.0.0)";visibility:=reexport, org.eclipse.emf.ecore.xmi;bundle-version="[2.4.0,3.0.0)";visibility:=reexport, org.eclipse.emf.edit;bundle-version="[2.4.0,3.0.0)", + org.eclipse.emf.transaction;bundle-version="[1.4.0,1.5.0)", org.eclipse.emf.cdo.common;bundle-version="[4.0.0,5.0.0)";visibility:=reexport, org.eclipse.emf.cdo.common.db;bundle-version="[3.0.0,4.0.0)";visibility:=reexport, org.eclipse.emf.cdo;bundle-version="[4.0.0,5.0.0)";visibility:=reexport, diff --git a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/AllConfigs.java b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/AllConfigs.java index cb5873d868..71d042ef17 100644 --- a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/AllConfigs.java +++ b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/AllConfigs.java @@ -137,6 +137,7 @@ public abstract class AllConfigs extends ConfigTestSuite testClasses.add(ChunkingTest.class); testClasses.add(ChunkingWithMEMTest.class); testClasses.add(PackageRegistryTest.class); + testClasses.add(PartialCommitTest.class); testClasses.add(MetaTest.class); testClasses.add(RevisionDeltaTest.class); testClasses.add(RevisionHolderTest.class); diff --git a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/PartialCommitTest.java b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/PartialCommitTest.java new file mode 100644 index 0000000000..902e006257 --- /dev/null +++ b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/PartialCommitTest.java @@ -0,0 +1,1257 @@ +package org.eclipse.emf.cdo.tests; + +import org.eclipse.emf.cdo.CDOObject; +import org.eclipse.emf.cdo.common.id.CDOID; +import org.eclipse.emf.cdo.eresource.CDOResource; +import org.eclipse.emf.cdo.session.CDOSession; +import org.eclipse.emf.cdo.tests.legacy.model1.Model1Package; +import org.eclipse.emf.cdo.tests.legacy.model4.model4Package; +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.Product1; +import org.eclipse.emf.cdo.tests.model1.PurchaseOrder; +import org.eclipse.emf.cdo.tests.model1.Supplier; +import org.eclipse.emf.cdo.tests.model4.ContainedElementNoOpposite; +import org.eclipse.emf.cdo.tests.model4.MultiNonContainedElement; +import org.eclipse.emf.cdo.tests.model4.RefMultiNonContained; +import org.eclipse.emf.cdo.tests.model4.RefSingleContainedNPL; +import org.eclipse.emf.cdo.tests.model4.RefSingleNonContained; +import org.eclipse.emf.cdo.tests.model4.SingleNonContainedElement; +import org.eclipse.emf.cdo.tests.model4.model4Factory; +import org.eclipse.emf.cdo.util.CDOUtil; +import org.eclipse.emf.cdo.util.CommitException; +import org.eclipse.emf.cdo.util.CommitIntegrityCheck; +import org.eclipse.emf.cdo.util.CommitIntegrityCheck.Style; +import org.eclipse.emf.cdo.util.CommitIntegrityException; +import org.eclipse.emf.cdo.view.CDOView; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EReference; +import org.eclipse.emf.ecore.util.EcoreUtil; +import org.eclipse.emf.spi.cdo.InternalCDOTransaction; +import org.eclipse.emf.spi.cdo.InternalCDOTransaction.InternalCDOCommitContext; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +public class PartialCommitTest extends AbstractCDOTest +{ + private static String RESOURCENAME = "/r1"; + + private CDOSession session; + + private InternalCDOTransaction tx; + + private CDOResource resource1; + + /* ---- Model 1 stuff ---- */ + + private Company company1, company2, company3, company99; + + private PurchaseOrder purchaseOrder; + + private Supplier supplier1; + + /* ---- Model 4 stuff ---- */ + + private RefSingleContainedNPL refSingleContained1, refSingleContained2; + + private ContainedElementNoOpposite singleContainedElement1; + + private RefSingleNonContained refSingleNonContained1, refSingleNonContained2; + + private SingleNonContainedElement singleNonContainedElement1, singleNonContainedElement2; + + private RefMultiNonContained refMultiNonContained1, refMultiNonContained2; + + private MultiNonContainedElement multiNonContainedElement1, multiNonContainedElement2; + + @Override + public void setUp() throws Exception + { + super.setUp(); + session = openSession(); + session.options().setPassiveUpdateEnabled(false); + tx = (InternalCDOTransaction)session.openTransaction(); + } + + @Override + public void tearDown() throws Exception + { + tx.close(); + session.close(); + super.tearDown(); + } + + public void testNewTopLevelResource() throws CommitException + { + CDOResource topResource1 = tx.createResource("/top1"); + tx.commit(); + + topResource1.setName("top1_newname"); // Make dirty but don't include; this causes partial commit + CDOResource topResource2 = tx.createResource("/top2"); + tx.setCommittables(createSet(topResource2, tx.getRootResource())); + goodAll(); + } + + public void testNewTopLevelResource_rootResourceNotIncluded() throws CommitException + { + CDOResource topResource1 = tx.createResource("/top1"); + tx.commit(); + + topResource1.setName("top1_newname"); // Make dirty but don't include; this causes partial commit + CDOResource topResource2 = tx.createResource("/top2"); + tx.setCommittables(createSet(topResource2)); + badAll(createSet(tx.getRootResource())); + } + + public void testNewNestedResource() throws CommitException + { + CDOResource topResource1 = tx.createResource("/top1"); + tx.commit(); + + topResource1.setName("top1_newname"); // Make dirty but don't include; this causes partial commit + CDOResource nestedResource = tx.createResource("/folder/nested"); + tx.setCommittables(createSet(nestedResource, nestedResource.getFolder(), tx.getRootResource())); + goodAll(); + } + + public void testNewNestedResource_rootResourceNotIncluded() throws CommitException + { + CDOResource topResource1 = tx.createResource("/top1"); + tx.commit(); + + topResource1.setName("top1_newname"); // Make dirty but don't include; this causes partial commit + CDOResource nestedResource = tx.createResource("/folder/nested"); + tx.setCommittables(createSet(nestedResource, nestedResource.getFolder())); + badAll(createSet(tx.getRootResource())); + } + + public void testNewNestedResource_resourceFolderNotIncluded() throws CommitException + { + CDOResource topResource1 = tx.createResource("/top1"); + tx.commit(); + + topResource1.setName("top1_newname"); // Make dirty but don't include; this causes partial commit + CDOResource nestedResource = tx.createResource("/folder/nested"); + tx.setCommittables(createSet(nestedResource, tx.getRootResource())); + badAll(createSet(nestedResource.getFolder())); + } + + public void testPartialCleanUp_dirtyObjects() throws CommitException + { + simpleModel1Setup(); + + company1.setName("Company1"); + company2.setName("Company2"); + company3.setName("Company3"); + + tx.setCommittables(createSet(company1)); + tx.commit(); + + assertClean(company1, tx); + assertDirty(company2, tx); + assertDirty(company3, tx); + assertTrue(tx.isDirty()); + + tx.setCommittables(createSet(company2)); + tx.commit(); + + assertClean(company1, tx); + assertClean(company2, tx); + assertDirty(company3, tx); + assertTrue(tx.isDirty()); + + tx.setCommittables(createSet(company3)); + tx.commit(); + + assertClean(company1, tx); + assertClean(company2, tx); + assertClean(company3, tx); + assertFalse(tx.isDirty()); + } + + public void testPartialCleanUp_newObjects() throws CommitException + { + simpleModel1Setup(); + Category cat = Model1Factory.eINSTANCE.createCategory(); + resource1.getContents().add(cat); + tx.commit(); + + company1.setName("Zzz"); // Make dirty but don't include; so as to force partial commit + + // Make some new objects; but with different containers + Company company4 = Model1Factory.eINSTANCE.createCompany(); + resource1.getContents().add(company4); + PurchaseOrder po = Model1Factory.eINSTANCE.createPurchaseOrder(); + company2.getPurchaseOrders().add(po); + Product1 product = Model1Factory.eINSTANCE.createProduct1(); + cat.getProducts().add(product); + + tx.setCommittables(createSet(company4, resource1)); + tx.commit(); + + assertClean(company4, tx); + assertNew(po, tx); + assertNew(product, tx); + assertTrue(tx.isDirty()); + + tx.setCommittables(createSet(po, company2)); + tx.commit(); + + assertClean(company4, tx); + assertClean(po, tx); + assertNew(product, tx); + assertTrue(tx.isDirty()); + + tx.setCommittables(createSet(product, cat)); + tx.commit(); + + assertClean(company4, tx); + assertClean(po, tx); + assertClean(product, tx); + assertTrue(tx.isDirty()); + + tx.setCommittables(createSet(company1)); + tx.commit(); + assertFalse(tx.isDirty()); + } + + public void testPartialCleanUp_detachedObjects() throws CommitException + { + simpleModel1Setup(); + Category cat = Model1Factory.eINSTANCE.createCategory(); + resource1.getContents().add(cat); + + // Make some new objects; but with different containers + Company company4 = Model1Factory.eINSTANCE.createCompany(); + resource1.getContents().add(company4); + PurchaseOrder po = Model1Factory.eINSTANCE.createPurchaseOrder(); + company2.getPurchaseOrders().add(po); + Product1 product = Model1Factory.eINSTANCE.createProduct1(); + cat.getProducts().add(product); + tx.commit(); + + company1.setName("Zzz"); // Make dirty but don't include; so as to force partial commit + + resource1.getContents().remove(company4); + company2.getPurchaseOrders().remove(po); + cat.getProducts().remove(product); + + assertTrue(tx.getDetachedObjects().containsValue(company4)); + assertTrue(tx.getFormerRevisionKeys().containsKey(company4)); + assertTrue(tx.getDetachedObjects().containsValue(po)); + assertTrue(tx.getFormerRevisionKeys().containsKey(company4)); + assertTrue(tx.getDetachedObjects().containsValue(product)); + assertTrue(tx.getFormerRevisionKeys().containsKey(company4)); + assertTrue(tx.isDirty()); + + tx.setCommittables(createSet(company4, resource1)); + tx.commit(); + + assertFalse(tx.getDetachedObjects().containsValue(company4)); + assertFalse(tx.getFormerRevisionKeys().containsKey(company4)); + assertTrue(tx.getDetachedObjects().containsValue(po)); + assertTrue(tx.getFormerRevisionKeys().containsKey(po)); + assertTrue(tx.getDetachedObjects().containsValue(product)); + assertTrue(tx.getFormerRevisionKeys().containsKey(product)); + assertTrue(tx.isDirty()); + + tx.setCommittables(createSet(po, company2)); + tx.commit(); + + assertFalse(tx.getDetachedObjects().containsValue(company4)); + assertFalse(tx.getFormerRevisionKeys().containsKey(company4)); + assertFalse(tx.getDetachedObjects().containsValue(po)); + assertFalse(tx.getFormerRevisionKeys().containsKey(po)); + assertTrue(tx.getDetachedObjects().containsValue(product)); + assertTrue(tx.getFormerRevisionKeys().containsKey(product)); + assertTrue(tx.isDirty()); + + tx.setCommittables(createSet(product, cat)); + tx.commit(); + + assertFalse(tx.getDetachedObjects().containsValue(company4)); + assertFalse(tx.getFormerRevisionKeys().containsKey(company4)); + assertFalse(tx.getDetachedObjects().containsValue(po)); + assertFalse(tx.getFormerRevisionKeys().containsKey(po)); + assertFalse(tx.getDetachedObjects().containsValue(product)); + assertFalse(tx.getFormerRevisionKeys().containsKey(product)); + assertTrue(tx.isDirty()); + + tx.setCommittables(createSet(company1)); + tx.commit(); + assertFalse(tx.isDirty()); + } + + public void testDirty() throws CommitException + { + simpleModel1Setup(); + supplier1.setName("Supplier"); + company1.setName("Company"); + + tx.setCommittables(createSet(supplier1)); + tx.commit(); + + assertDirty(company1, tx); + assertEquals(company1.getName(), "Company"); + + assertClean(supplier1, tx); + assertEquals(supplier1.getName(), "Supplier"); + } + + public void testNew() throws CommitException + { + simpleModel1Setup(); + PurchaseOrder po = Model1Factory.eINSTANCE.createPurchaseOrder(); + company2.getPurchaseOrders().add(po); + + // Include both the new object and its container + tx.setCommittables(createSet(company2, po)); + goodAll(); + } + + public void testNew_containerOfNewObjectNotIncluded() throws CommitException + { + simpleModel1Setup(); + PurchaseOrder po = Model1Factory.eINSTANCE.createPurchaseOrder(); + company2.getPurchaseOrders().add(po); + + // Include only the new object + tx.setCommittables(createSet(po)); + badAll(createSet(company2)); + } + + public void testNew_newObjectNotIncluded() throws CommitException + { + simpleModel1Setup(); + PurchaseOrder po = Model1Factory.eINSTANCE.createPurchaseOrder(); + company2.getPurchaseOrders().add(po); + + // Include only the new object's container + tx.setCommittables(createSet(company2)); + badAll(createSet(po)); + } + + public void testDetach() throws CommitException + { + simpleModel1Setup(); + EcoreUtil.delete(purchaseOrder); + + // Include the detached object and its old container + tx.setCommittables(createSet(company1, purchaseOrder)); + goodAll(); + } + + public void testDetach_containerOfDetachedObjectNotIncluded() throws CommitException + { + simpleModel1Setup(); + EcoreUtil.delete(purchaseOrder); + + // Include only the detached object + tx.setCommittables(createSet(purchaseOrder)); + badAll(createSet(company1)); + } + + public void testDetach_detachedObjectNotIncluded() throws CommitException + { + simpleModel1Setup(); + EcoreUtil.delete(purchaseOrder); + + // Include only the detached object's old container + tx.setCommittables(createSet(company1)); + badAll(createSet(purchaseOrder)); + } + + public void testMove() throws CommitException + { + simpleModel1Setup(); + company2.getPurchaseOrders().add(purchaseOrder); + supplier1.setName("Supplier"); + + // Include the old and new containers as well as the object that was moved + tx.setCommittables(createSet(purchaseOrder, company1, company2)); + goodAll(); + + assertClean(company1, tx); + assertClean(company2, tx); + assertClean(purchaseOrder, tx); + assertDirty(supplier1, tx); + + assertFalse(company1.getPurchaseOrders().contains(purchaseOrder)); + assertTrue(company2.getPurchaseOrders().contains(purchaseOrder)); + assertEquals("Supplier", supplier1.getName()); + assertSame(company2, purchaseOrder.eContainer()); + } + + public void testMove_newContainerNotIncluded() throws CommitException + { + simpleModel1Setup(); + company2.getPurchaseOrders().add(purchaseOrder); + + // Include only the object that was moved and its old container + tx.setCommittables(createSet(purchaseOrder, company1)); + badAll(createSet(company2)); + } + + public void testMove_oldContainerNotIncluded() throws CommitException + { + simpleModel1Setup(); + company2.getPurchaseOrders().add(purchaseOrder); + + // Include only the object that was moved and its new container + tx.setCommittables(createSet(purchaseOrder, company2)); + badAll(createSet(company1)); + } + + public void testMove_movedObjectNotIncluded() throws CommitException + { + simpleModel1Setup(); + company2.getPurchaseOrders().add(purchaseOrder); + + // Include only the old and new containers + tx.setCommittables(createSet(company1, company2)); + badAll(createSet(purchaseOrder)); + } + + public void testMove_onlyOldContainerIncluded() throws CommitException + { + simpleModel1Setup(); + company2.getPurchaseOrders().add(purchaseOrder); + + // Include only the old container + tx.setCommittables(createSet(company1)); + badAll(createSet(company2, purchaseOrder)); + } + + public void testMove_onlyNewContainerIncluded() throws CommitException + { + simpleModel1Setup(); + company2.getPurchaseOrders().add(purchaseOrder); + + // Include only the new container + tx.setCommittables(createSet(company2)); + badAll(createSet(company1, purchaseOrder)); + } + + public void testMove_onlyMovedObjectIncluded() throws CommitException + { + simpleModel1Setup(); + company2.getPurchaseOrders().add(purchaseOrder); + + // Include only the moved object + tx.setCommittables(createSet(purchaseOrder)); + badAll(createSet(company1, company2)); + } + + public void testDoubleMove() throws CommitException + { + simpleModel1Setup(); + company2.getPurchaseOrders().add(purchaseOrder); + company3.getPurchaseOrders().add(purchaseOrder); + + // Include the old and new containers as well as the object that was moved + // (The point here is that company2 does NOT have to be included.) + tx.setCommittables(createSet(purchaseOrder, company1, company3)); + System.out.printf("---> purchaseOrder=%s company1=%s company2=%s company3=%s\n", purchaseOrder, company1, company2, + company3); + goodAll(); + } + + public void test_noCommittablesAfterCommit() throws CommitException + { + simpleModel1Setup(); + company1.setName("zzz"); + tx.setCommittables(createSet(company1)); + tx.commit(); + + assertNull(tx.getCommittables()); + } + + public void testNewSingle() throws CommitException + { + simpleModel4ContainmentSetup(); + ContainedElementNoOpposite singleContainedElement = model4Factory.eINSTANCE.createContainedElementNoOpposite(); + refSingleContained2.setElement(singleContainedElement); + + // Include both the new object and its container + tx.setCommittables(createSet(refSingleContained2, singleContainedElement)); + goodAll(); + } + + public void testNewSingle_containerOfNewObjectNotIncluded() throws CommitException + { + simpleModel4ContainmentSetup(); + ContainedElementNoOpposite singleContainedElement = model4Factory.eINSTANCE.createContainedElementNoOpposite(); + refSingleContained2.setElement(singleContainedElement); + + // Include only the new object + tx.setCommittables(createSet(singleContainedElement)); + badAll(createSet(refSingleContained2)); + } + + public void testNewSingle_newObjectNotIncluded() throws CommitException + { + simpleModel4ContainmentSetup(); + ContainedElementNoOpposite singleContainedElement = model4Factory.eINSTANCE.createContainedElementNoOpposite(); + refSingleContained2.setElement(singleContainedElement); + + // Include only the new object's container + tx.setCommittables(createSet(refSingleContained2)); + badAll(createSet(singleContainedElement)); + } + + public void testDetachSingleRef() throws CommitException + { + simpleModel4ContainmentSetup(); + refSingleContained1.setElement(null); + + // Include the detached object and its old container + tx.setCommittables(createSet(refSingleContained1, singleContainedElement1)); + goodAll(); + } + + public void testDetachSingleRef_containerOfDetachedObjectNotIncluded() throws CommitException + { + simpleModel4ContainmentSetup(); + refSingleContained1.setElement(null); + + // Include only the detached object + tx.setCommittables(createSet(singleContainedElement1)); + badAll(createSet(refSingleContained1)); + } + + public void testDetachSingleRef_detachedObjectNotIncluded() throws CommitException + { + simpleModel4ContainmentSetup(); + refSingleContained1.setElement(null); + + // Include only the detached object's old container + tx.setCommittables(createSet(refSingleContained1)); + badAll(createSet(singleContainedElement1)); + } + + public void testMoveSingleRef() throws CommitException + { + simpleModel4ContainmentSetup(); + refSingleContained2.setElement(singleContainedElement1); + + // Include the old and new containers as well as the object that was moved + tx.setCommittables(createSet(refSingleContained1, refSingleContained2, singleContainedElement1)); + goodAll(); + } + + public void testMoveSingleRef_newContainerNotIncluded() throws CommitException + { + simpleModel4ContainmentSetup(); + refSingleContained2.setElement(singleContainedElement1); + + // Include only the object that was moved and its old container + tx.setCommittables(createSet(refSingleContained1, singleContainedElement1)); + badAll(createSet(refSingleContained2)); + } + + public void testMoveSingleRef_oldContainerNotIncluded() throws CommitException + { + simpleModel4ContainmentSetup(); + refSingleContained2.setElement(singleContainedElement1); + + // Include only the object that was moved and its new container + tx.setCommittables(createSet(refSingleContained2, singleContainedElement1)); + badAll(createSet(refSingleContained1)); + } + + public void testMoveSingleRef_movedObjectNotIncluded() throws CommitException + { + simpleModel4ContainmentSetup(); + refSingleContained2.setElement(singleContainedElement1); + + // Include only the old and new containers + tx.setCommittables(createSet(refSingleContained1, refSingleContained2)); + badAll(createSet(singleContainedElement1)); + } + + public void testMoveSingleRef_onlyOldContainerIncluded() throws CommitException + { + simpleModel4ContainmentSetup(); + refSingleContained2.setElement(singleContainedElement1); + + // Include only the old container + tx.setCommittables(createSet(refSingleContained1)); + badAll(createSet(singleContainedElement1, refSingleContained2)); + } + + public void testMoveSingleRef_onlyNewContainerIncluded() throws CommitException + { + simpleModel4ContainmentSetup(); + refSingleContained2.setElement(singleContainedElement1); + + // Include only the new container + tx.setCommittables(createSet(refSingleContained2)); + badAll(createSet(singleContainedElement1, refSingleContained1)); + } + + public void testMoveSingleRef_onlyMovedObjectIncluded() throws CommitException + { + simpleModel4ContainmentSetup(); + refSingleContained2.setElement(singleContainedElement1); + + // Include only the moved object + tx.setCommittables(createSet(singleContainedElement1)); + badAll(createSet(refSingleContained1, refSingleContained2)); + } + + public void testNewTopLevel() throws CommitException + { + simpleModel1Setup(); + Company company = Model1Factory.eINSTANCE.createCompany(); + resource1.getContents().add(company); + + // Include both the resource and the new object + tx.setCommittables(createSet(resource1, company)); + goodAll(); + } + + public void testNewTopLevel_newObjectNotIncluded() throws CommitException + { + simpleModel1Setup(); + Company company = Model1Factory.eINSTANCE.createCompany(); + resource1.getContents().add(company); + + // Include only the resource + tx.setCommittables(createSet(resource1)); + badAll(createSet(company)); + } + + public void testNewTopLevel_resourceNotIncluded() throws CommitException + { + simpleModel1Setup(); + Company company = Model1Factory.eINSTANCE.createCompany(); + resource1.getContents().add(company); + + // Include only the new object + tx.setCommittables(createSet(company)); + badAll(createSet(resource1)); + } + + public void _testNewTopLevel_resourceNotIncluded() throws CommitException + { + simpleModel1Setup(); + + CDOID companyID = null; + { + Company company = Model1Factory.eINSTANCE.createCompany(); + resource1.getContents().add(company); + + // Include only the new object + tx.setCommittables(createSet(company)); + tx.commit(); + + companyID = CDOUtil.getCDOObject(company).cdoID(); + } + + System.out.println("---> companyID = " + companyID); + System.out.println("---> " + CDOUtil.getCDOObject(resource1).cdoState()); + + { + CDOSession session2 = openSession(); + CDOView view = session2.openView(); + CDOResource resource = view.getResource(resource1.getPath()); + + // We want to know if the new company that was committed, is an element + // in the getContents() collection of the Resource. We cannot just call + // getContents().contains(), because of the odd implementation of + // CDOResourceImpl.contains: it actually asks the element, rather than + // checking its own collection. So, we have to do this the hard way: + // + boolean found = false; + Iterator<EObject> iter = resource.getContents().iterator(); + while (!found && iter.hasNext()) + { + CDOObject o = CDOUtil.getCDOObject(iter.next()); + if (o.cdoID().equals(companyID)) + { + found = true; + } + } + assertTrue(found); + + view.close(); + session2.close(); + } + } + + // -------- Tests concerning bi-di references ---------- + // + // Cases to test: + // Bi-di refs are analogous to containment, the only difference being that + // bi-di refs are symmetrical, whereas containment/container is not. + // + // So: + // + // For DIRTY objects, the cases are: + // 1. Setting a bidi ref to null where it was previously non-null + // Must check that object owning opposite feature is included + // 2. Setting a bidi ref to non-null where it was previously null + // Must check that object owning opposite feature is included + // 3. Changing a bidi ref from one non-null value to another + // Must check that both the object owning the NEW opposite feature, + // as well as the OLD one, are included + // + // For NEW objects, the + // If the detached object had any non-null bidi refs, we must check + // whether the bidi target is included. + // + // For DETACHED objects: + // If the detached object had any non-null bidi refs, we must check + // whether the bidi target is included. + + public void testDirtySingleBidiNew() throws CommitException + { + simpleModel4SingleBidiSetup(); + singleNonContainedElement2.setParent(refSingleNonContained2); + + tx.setCommittables(createSet(singleNonContainedElement2, refSingleNonContained2)); + goodAll(); + } + + public void testDirtySingleBidiNew_newtargetNotIncluded() throws CommitException + { + simpleModel4SingleBidiSetup(); + singleNonContainedElement2.setParent(refSingleNonContained2); + + tx.setCommittables(createSet(singleNonContainedElement2)); + badAll(createSet(refSingleNonContained2)); + } + + public void testDirtySingleBidiChanged() throws CommitException + { + simpleModel4SingleBidiSetup(); + // We "reparent" the singleNonContainedElement1 + singleNonContainedElement1.setParent(refSingleNonContained2); + + tx.setCommittables(createSet(singleNonContainedElement1, refSingleNonContained1, refSingleNonContained2)); + goodAll(); + } + + public void testDirtySingleBidiChanged_newTargetNotIncluded() throws CommitException + { + simpleModel4SingleBidiSetup(); + // We "reparent" the singleNonContainedElement1 + singleNonContainedElement1.setParent(refSingleNonContained2); + + tx.setCommittables(createSet(singleNonContainedElement1, refSingleNonContained1)); + badAll(createSet(refSingleNonContained2)); + } + + public void testDirtySingleBidiChanged_oldTargetNotIncluded() throws CommitException + { + simpleModel4SingleBidiSetup(); + // We "reparent" the singleNonContainedElement1 + singleNonContainedElement1.setParent(refSingleNonContained2); + + tx.setCommittables(createSet(singleNonContainedElement1, refSingleNonContained2)); + badAll(createSet(refSingleNonContained1)); + } + + public void testDirtySingleBidiRemoved() throws CommitException + { + simpleModel4SingleBidiSetup(); + singleNonContainedElement1.setParent(null); + + tx.setCommittables(createSet(singleNonContainedElement1, refSingleNonContained1)); + goodAll(); + } + + public void testDirtySingleBidiRemoved_oldTargetNotIncluded() throws CommitException + { + simpleModel4SingleBidiSetup(); + singleNonContainedElement1.setParent(null); + + tx.setCommittables(createSet(singleNonContainedElement1)); + badAll(createSet(refSingleNonContained1)); + } + + public void testSingleBidiOnNewObject() throws CommitException + { + simpleModel4SingleBidiSetup(); + SingleNonContainedElement newNonContainedElement = model4Factory.eINSTANCE.createSingleNonContainedElement(); + resource1.getContents().add(newNonContainedElement); + newNonContainedElement.setParent(refSingleNonContained2); + + tx.setCommittables(createSet(newNonContainedElement, resource1, refSingleNonContained2)); + goodAll(); + } + + public void testSingleBidiOnNewObject_targetNotIncluded() throws CommitException + { + simpleModel4SingleBidiSetup(); + SingleNonContainedElement newNonContainedElement = model4Factory.eINSTANCE.createSingleNonContainedElement(); + resource1.getContents().add(newNonContainedElement); + newNonContainedElement.setParent(refSingleNonContained2); + + tx.setCommittables(createSet(newNonContainedElement, resource1)); + badAll(createSet(refSingleNonContained2)); + } + + public void testSingleBidiOnRemovedObject() throws CommitException + { + simpleModel4SingleBidiSetup(); + EcoreUtil.delete(singleNonContainedElement1); + + tx.setCommittables(createSet(singleNonContainedElement1, resource1, refSingleNonContained1)); + goodAll(); + } + + public void testSingleBidiOnRemovedObject_targetNotIncluded() throws CommitException + { + simpleModel4SingleBidiSetup(); + EcoreUtil.delete(singleNonContainedElement1); + + tx.setCommittables(createSet(singleNonContainedElement1, resource1)); + badAll(createSet(refSingleNonContained1)); + } + + public void testDirtyMultiBidiNew() throws CommitException + { + simpleModel4MultiBidiSetup(); + multiNonContainedElement2.setParent(refMultiNonContained2); + + tx.setCommittables(createSet(multiNonContainedElement2, refMultiNonContained2)); + goodAll(); + } + + public void testDirtyMultiBidiNew_newtargetNotIncluded() throws CommitException + { + simpleModel4MultiBidiSetup(); + multiNonContainedElement2.setParent(refMultiNonContained2); + + tx.setCommittables(createSet(multiNonContainedElement2)); + badAll(createSet(refMultiNonContained2)); + } + + public void testDirtyMultiBidiChanged() throws CommitException + { + simpleModel4MultiBidiSetup(); + // We "reparent" the multiNonContainedElement1 + multiNonContainedElement1.setParent(refMultiNonContained2); + + tx.setCommittables(createSet(multiNonContainedElement1, refMultiNonContained1, refMultiNonContained2)); + goodAll(); + } + + public void testDirtyMultiBidiChanged_newTargetNotIncluded() throws CommitException + { + simpleModel4MultiBidiSetup(); + // We "reparent" the multiNonContainedElement1 + multiNonContainedElement1.setParent(refMultiNonContained2); + + tx.setCommittables(createSet(multiNonContainedElement1, refMultiNonContained1)); + badAll(createSet(refMultiNonContained2)); + } + + public void testDirtyMultiBidiChanged_oldTargetNotIncluded() throws CommitException + { + simpleModel4MultiBidiSetup(); + // We "reparent" the multiNonContainedElement1 + multiNonContainedElement1.setParent(refMultiNonContained2); + + tx.setCommittables(createSet(multiNonContainedElement1, refMultiNonContained2)); + badAll(createSet(refMultiNonContained1)); + } + + public void testDirtyMultiBidiRemoved() throws CommitException + { + simpleModel4MultiBidiSetup(); + multiNonContainedElement1.setParent(null); + + tx.setCommittables(createSet(multiNonContainedElement1, refMultiNonContained1)); + goodAll(); + } + + public void testDirtyMultiBidiRemoved_oldTargetNotIncluded() throws CommitException + { + simpleModel4MultiBidiSetup(); + multiNonContainedElement1.setParent(null); + + tx.setCommittables(createSet(multiNonContainedElement1)); + badAll(createSet(refMultiNonContained1)); + } + + public void testMultiBidiOnNewObject() throws CommitException + { + simpleModel4MultiBidiSetup(); + MultiNonContainedElement newNonContainedElement = model4Factory.eINSTANCE.createMultiNonContainedElement(); + resource1.getContents().add(newNonContainedElement); + newNonContainedElement.setParent(refMultiNonContained2); + + tx.setCommittables(createSet(newNonContainedElement, resource1, refMultiNonContained2)); + goodAll(); + } + + public void testMultiBidiOnNewObject_targetNotIncluded() throws CommitException + { + simpleModel4MultiBidiSetup(); + MultiNonContainedElement newNonContainedElement = model4Factory.eINSTANCE.createMultiNonContainedElement(); + resource1.getContents().add(newNonContainedElement); + newNonContainedElement.setParent(refMultiNonContained2); + + tx.setCommittables(createSet(newNonContainedElement, resource1)); + badAll(createSet(refMultiNonContained2)); + } + + public void testMultiBidiOnRemovedObject() throws CommitException + { + simpleModel4MultiBidiSetup(); + EcoreUtil.delete(multiNonContainedElement1); + + tx.setCommittables(createSet(multiNonContainedElement1, resource1, refMultiNonContained1)); + goodAll(); + } + + public void testMultiBidiOnRemovedObject_targetNotIncluded() throws CommitException + { + simpleModel4MultiBidiSetup(); + EcoreUtil.delete(multiNonContainedElement1); + + tx.setCommittables(createSet(multiNonContainedElement1, resource1)); + badAll(createSet(refMultiNonContained1)); + } + + public void testCheckWithoutCommit_exceptionFast() throws CommitException + { + simpleModel1Setup(); + PurchaseOrder po = Model1Factory.eINSTANCE.createPurchaseOrder(); + company2.getPurchaseOrders().add(po); + + // Include only the new object + tx.setCommittables(createSet(po)); + InternalCDOCommitContext ctx = tx.createCommitContext(); + try + { + new CommitIntegrityCheck(ctx, CommitIntegrityCheck.Style.EXCEPTION_FAST).check(); + } + catch (CommitIntegrityException e) + { + // Good + } + } + + public void testCheckWithoutCommit_exception() throws CommitException + { + simpleModel1Setup(); + PurchaseOrder po = Model1Factory.eINSTANCE.createPurchaseOrder(); + company2.getPurchaseOrders().add(po); + + // Include only the new object + tx.setCommittables(createSet(po)); + InternalCDOCommitContext ctx = tx.createCommitContext(); + try + { + new CommitIntegrityCheck(ctx, CommitIntegrityCheck.Style.EXCEPTION_FAST).check(); + } + catch (CommitIntegrityException e) + { + // Good + } + } + + public void testCommittablesContainUncommittableObjects() + { + // Idea here is to include some objects in the committables, that exist, but + // are neither dirty nor detached nor new. + // + // Actually, one could wonder what the desirable behavior is in this case. + // Should there be a failure? Or should the "committables" be considered more like + // a filter; i.e. it's ok for the filter to cover more than what can actually be committed. + // Hmm... *ponder* *ponder*. + // + } + + /** + * Test the commit integrity, assuming that it is good, using all possible checking styles. + */ + private void goodAll() throws CommitException + { + good(Style.NO_EXCEPTION); + good(Style.EXCEPTION_FAST); + good(Style.EXCEPTION); + good(null); + } + + /** + * Test the commit integrity, assuming that it is good. + * + * @param style + * - the checking style to be used; if null, just commit. In that case, the commit logic will choose the + * checking style. + */ + private void good(Style style) throws CommitException + { + if (style != null) + { + InternalCDOCommitContext ctx = tx.createCommitContext(); + CommitIntegrityCheck check = new CommitIntegrityCheck(ctx, style); + + try + { + check.check(); + assertTrue("check.getMissingObjects() should have been empty", check.getMissingObjects().isEmpty()); + } + catch (CommitIntegrityException e) + { + fail("Should not have thrown " + CommitIntegrityException.class.getName()); + } + } + else + { + try + { + // We always make company99 dirty if it's present + // (This is just a control object to verify that some stuff does NOT get + // committed.) + if (company99 != null) + { + company99.setName("000"); + } + + tx.commit(); + + // And we verify that it didn't get included in the commit + if (company99 != null) + { + assertDirty(company99, tx); + assertTrue("Transaction should still have been dirty", tx.isDirty()); + } + } + catch (CommitException e) + { + Throwable cause = e.getCause().getCause(); + if (cause instanceof CommitIntegrityException) + { + fail("---> Should not have failed with: " + e.getCause().getMessage()); + } + else + { + throw e; + } + } + } + } + + /** + * Test the commit integrity, assuming that it is bad, using all possible checking styles. + */ + private void badAll(Set<EObject> expectedMissingObjects) throws CommitException + { + bad(Style.NO_EXCEPTION, expectedMissingObjects); + bad(Style.EXCEPTION_FAST, expectedMissingObjects); + bad(Style.EXCEPTION, expectedMissingObjects); + bad(null, expectedMissingObjects); + } + + /** + * Test the commit integrity, assuming that it is bad. + * + * @param style + * - the checking style to be used; if null, just commit. In that case, the commit logic will choose the + * checking style. + */ + private void bad(Style style, Set<EObject> expectedMissingObjects) throws CommitException + { + CommitIntegrityException commitIntegrityEx = null; + Set<? extends EObject> missingObjects = null; + + CommitIntegrityCheck check = null; + if (style != null) + { + InternalCDOCommitContext ctx = tx.createCommitContext(); + check = new CommitIntegrityCheck(ctx, style); + } + + if (style == Style.NO_EXCEPTION) + { + try + { + check.check(); + } + catch (CommitIntegrityException e) + { + fail("Should not have thrown " + CommitIntegrityException.class.getName()); + } + } + else if (style == CommitIntegrityCheck.Style.EXCEPTION || style == CommitIntegrityCheck.Style.EXCEPTION_FAST) + { + try + { + check.check(); + fail("Should have thrown " + CommitIntegrityException.class.getName()); + } + catch (CommitIntegrityException e) + { + commitIntegrityEx = e; + } + } + else if (style == null) + { + try + { + tx.commit(); + fail("Should have thrown " + CommitException.class.getName()); + } + catch (CommitException e) + { + Throwable cause = e.getCause().getCause(); + if (cause instanceof CommitIntegrityException) + { + // Good + commitIntegrityEx = (CommitIntegrityException)cause; + System.out.println("---> Failed properly: " + e.getCause().getMessage()); + } + else + { + throw e; + } + } + } + else + { + fail("Unknown style"); + } + + if (commitIntegrityEx != null) + { + missingObjects = commitIntegrityEx.getMissingObjects(); + } + else + { + missingObjects = check.getMissingObjects(); + } + + if (style == Style.EXCEPTION_FAST) + { + assertEquals(1, missingObjects.size()); + } + else + { + // We cannot use == here, because it isn't (always) possible for the logic to + // find all missing objects + assertTrue(missingObjects.size() <= expectedMissingObjects.size()); + } + + for (EObject missingObject : missingObjects) + { + assertTrue(expectedMissingObjects.contains(missingObject)); + } + } + + private void simpleModel1Setup() throws CommitException + { + EReference ref = Model1Package.eINSTANCE.getCompany_PurchaseOrders(); + boolean preconditions = ref.isContainment() && ref.getEOpposite() == null && ref.isMany(); + if (!preconditions) + { + throw new RuntimeException("Model1 does not meet prerequirements for this test"); + } + + resource1 = tx.createResource(RESOURCENAME); + company1 = Model1Factory.eINSTANCE.createCompany(); + company2 = Model1Factory.eINSTANCE.createCompany(); + company3 = Model1Factory.eINSTANCE.createCompany(); + company99 = Model1Factory.eINSTANCE.createCompany(); + supplier1 = Model1Factory.eINSTANCE.createSupplier(); + purchaseOrder = Model1Factory.eINSTANCE.createPurchaseOrder(); + company1.getPurchaseOrders().add(purchaseOrder); + resource1.getContents().add(company1); + resource1.getContents().add(company2); + resource1.getContents().add(company3); + resource1.getContents().add(company99); + resource1.getContents().add(supplier1); + tx.commit(); + } + + private void simpleModel4ContainmentSetup() throws CommitException + { + EReference ref = model4Package.eINSTANCE.getRefSingleContainedNPL_Element(); + boolean preconditions = ref.isContainment() && ref.getEOpposite() == null && !ref.isMany(); + if (!preconditions) + { + throw new RuntimeException("Model4 does not meet prerequirements for this test"); + } + + resource1 = tx.createResource(RESOURCENAME); + + refSingleContained1 = model4Factory.eINSTANCE.createRefSingleContainedNPL(); + refSingleContained2 = model4Factory.eINSTANCE.createRefSingleContainedNPL(); + singleContainedElement1 = model4Factory.eINSTANCE.createContainedElementNoOpposite(); + refSingleContained1.setElement(singleContainedElement1); + resource1.getContents().add(refSingleContained1); + resource1.getContents().add(refSingleContained2); + + tx.commit(); + } + + private void simpleModel4SingleBidiSetup() throws CommitException + { + EReference ref = model4Package.eINSTANCE.getRefSingleNonContained_Element(); + boolean preconditions = !ref.isContainment() && ref.getEOpposite() != null && !ref.isMany(); + if (!preconditions) + { + throw new RuntimeException("Model4 does not meet prerequirements for this test"); + } + + resource1 = tx.createResource(RESOURCENAME); + + refSingleNonContained1 = model4Factory.eINSTANCE.createRefSingleNonContained(); + refSingleNonContained2 = model4Factory.eINSTANCE.createRefSingleNonContained(); + singleNonContainedElement1 = model4Factory.eINSTANCE.createSingleNonContainedElement(); + singleNonContainedElement2 = model4Factory.eINSTANCE.createSingleNonContainedElement(); + refSingleNonContained1.setElement(singleNonContainedElement1); + resource1.getContents().add(refSingleNonContained1); + resource1.getContents().add(refSingleNonContained2); + resource1.getContents().add(singleNonContainedElement1); + resource1.getContents().add(singleNonContainedElement2); + + tx.commit(); + } + + private void simpleModel4MultiBidiSetup() throws CommitException + { + EReference ref = model4Package.eINSTANCE.getRefMultiNonContained_Elements(); + boolean preconditions = !ref.isContainment() && ref.getEOpposite() != null && ref.isMany(); + if (!preconditions) + { + throw new RuntimeException("Model4 does not meet prerequirements for this test"); + } + + resource1 = tx.createResource(RESOURCENAME); + + refMultiNonContained1 = model4Factory.eINSTANCE.createRefMultiNonContained(); + refMultiNonContained2 = model4Factory.eINSTANCE.createRefMultiNonContained(); + multiNonContainedElement1 = model4Factory.eINSTANCE.createMultiNonContainedElement(); + multiNonContainedElement2 = model4Factory.eINSTANCE.createMultiNonContainedElement(); + refMultiNonContained1.getElements().add(multiNonContainedElement1); + resource1.getContents().add(refMultiNonContained1); + resource1.getContents().add(refMultiNonContained2); + resource1.getContents().add(multiNonContainedElement1); + resource1.getContents().add(multiNonContainedElement2); + + tx.commit(); + } + + private Set<EObject> createSet(EObject... objects) + { + Set<EObject> committables = new HashSet<EObject>(); + for (EObject o : objects) + { + if (o == null) + { + throw new NullPointerException(); + } + committables.add(o); + } + return committables; + } +} diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/transaction/CDOCommitContext.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/transaction/CDOCommitContext.java index a1290c7d77..5e2cf48af8 100644 --- a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/transaction/CDOCommitContext.java +++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/transaction/CDOCommitContext.java @@ -56,4 +56,9 @@ public interface CDOCommitContext * Returns a map of the {@link CDORevisionDelta revision deltas} that are to be committed with this commit context. */ public Map<CDOID, CDORevisionDelta> getRevisionDeltas(); + + /** + * @since 4.0 + */ + public boolean isPartialCommit(); } diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/transaction/CDOPushTransaction.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/transaction/CDOPushTransaction.java index d1f2297933..35249d7fd1 100644 --- a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/transaction/CDOPushTransaction.java +++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/transaction/CDOPushTransaction.java @@ -597,4 +597,20 @@ public class CDOPushTransaction extends Notifier implements CDOTransaction { delegate.setCommitComment(comment); } + + /** + * @since 4.0 + */ + public void setCommittables(Set<EObject> committables) + { + delegate.setCommittables(committables); + } + + /** + * @since 4.0 + */ + public Set<EObject> getCommittables() + { + return delegate.getCommittables(); + } } diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/transaction/CDOUserTransaction.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/transaction/CDOUserTransaction.java index 378ad147f0..7e86d9a3c5 100644 --- a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/transaction/CDOUserTransaction.java +++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/transaction/CDOUserTransaction.java @@ -14,8 +14,12 @@ package org.eclipse.emf.cdo.transaction; import org.eclipse.emf.cdo.common.commit.CDOCommitInfo; import org.eclipse.emf.cdo.util.CommitException; +import org.eclipse.emf.ecore.EObject; + import org.eclipse.core.runtime.IProgressMonitor; +import java.util.Set; + /** * Only deal with transaction process. * @@ -50,4 +54,14 @@ public interface CDOUserTransaction * @since 3.0 */ public CDOUserSavepoint getLastSavepoint(); + + /** + * @since 4.0 + */ + public void setCommittables(Set<EObject> committables); + + /** + * @since 4.0 + */ + public Set<EObject> getCommittables(); } diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/util/CommitIntegrityCheck.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/util/CommitIntegrityCheck.java new file mode 100644 index 0000000000..92e5b38cf4 --- /dev/null +++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/util/CommitIntegrityCheck.java @@ -0,0 +1,422 @@ +/** + * Copyright (c) 2004 - 2010 Eike Stepper (Berlin, Germany) and others. + * 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: + * Caspar De Groot - initial API and implementation + */ +package org.eclipse.emf.cdo.util; + +import org.eclipse.emf.cdo.CDOObject; +import org.eclipse.emf.cdo.common.id.CDOID; +import org.eclipse.emf.cdo.common.id.CDOID.Type; +import org.eclipse.emf.cdo.common.revision.delta.CDOAddFeatureDelta; +import org.eclipse.emf.cdo.common.revision.delta.CDOClearFeatureDelta; +import org.eclipse.emf.cdo.common.revision.delta.CDOContainerFeatureDelta; +import org.eclipse.emf.cdo.common.revision.delta.CDOFeatureDelta; +import org.eclipse.emf.cdo.common.revision.delta.CDOListFeatureDelta; +import org.eclipse.emf.cdo.common.revision.delta.CDORemoveFeatureDelta; +import org.eclipse.emf.cdo.common.revision.delta.CDORevisionDelta; +import org.eclipse.emf.cdo.common.revision.delta.CDORevisionDeltaUtil; +import org.eclipse.emf.cdo.common.revision.delta.CDOSetFeatureDelta; +import org.eclipse.emf.cdo.common.revision.delta.CDOUnsetFeatureDelta; +import org.eclipse.emf.cdo.eresource.CDOResource; +import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevision; + +import org.eclipse.net4j.util.CheckUtil; + +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EReference; +import org.eclipse.emf.ecore.EStructuralFeature; +import org.eclipse.emf.ecore.InternalEObject.EStore; +import org.eclipse.emf.spi.cdo.InternalCDOObject; +import org.eclipse.emf.spi.cdo.InternalCDOTransaction; +import org.eclipse.emf.spi.cdo.InternalCDOTransaction.InternalCDOCommitContext; + +import java.util.HashSet; +import java.util.Set; + +/** + * @author Caspar De Groot + * @since 4.0 + */ +public class CommitIntegrityCheck +{ + private InternalCDOTransaction transaction; + + private Style style; + + private Set<CDOID> newIDs, dirtyIDs, detachedIDs; + + private Set<CDOObject> missingObjects = new HashSet<CDOObject>(); + + private StringBuilder exceptionMessage = new StringBuilder(); + + public CommitIntegrityCheck(InternalCDOCommitContext commitContext) + { + this(commitContext, Style.EXCEPTION_FAST); + } + + public CommitIntegrityCheck(InternalCDOCommitContext commitContext, Style style) + { + transaction = commitContext.getTransaction(); + + CheckUtil.checkNull(style, "style should not be null"); + this.style = style; + + newIDs = commitContext.getNewObjects().keySet(); + dirtyIDs = commitContext.getDirtyObjects().keySet(); + detachedIDs = commitContext.getDetachedObjects().keySet(); + } + + public void check() throws CommitIntegrityException + { + // For new objects: ensure that their container is included, + // as well as the targets of the new object's bidi references + for (CDOID newID : newIDs) + { + CDOObject newObject = transaction.getObject(newID); + checkContainerIncluded(newObject, "new"); + checkCurrentBidiRefTargetsIncluded(newObject, "new"); + } + + // For detached objects: ensure that their former container is included, + // as well as the targets of the detached object's bidi references + for (CDOID detachedID : detachedIDs) + { + CDOObject detachedObject = transaction.getObject(detachedID); + checkFormerContainerIncluded(detachedObject); + checkFormerBidiRefTargetsIncluded(detachedObject, "detached"); + } + + // For dirty objects: if any of the deltas for the object, affect containment (i.e. object was moved) + // or a bi-di reference, ensure that for containment, both the old and new containers are included, + // (or that the child is included if we are considering the dirty parent), + // and that for a bi-di reference, the object holding the other end of the bi-di is included, as + // well as possibly the *former* object holding the other end. + // + for (CDOID dirtyID : dirtyIDs) + { + CDOObject dirtyObject = transaction.getObject(dirtyID); + analyzeRevisionDelta((InternalCDOObject)dirtyObject); + } + + if (!missingObjects.isEmpty() && style == Style.EXCEPTION) + { + throw createException(); + } + } + + public Set<? extends EObject> getMissingObjects() + { + return missingObjects; + } + + private CDOID getContainerOrResourceID(InternalCDORevision revision) + { + CDOID containerOrResourceID = null; + Object idOrObject = revision.getContainerID(); + if (idOrObject != null) + { + containerOrResourceID = (CDOID)transaction.convertObjectToID(idOrObject); + } + + if (containerOrResourceID == null || containerOrResourceID.getType() == Type.NULL) + { + idOrObject = revision.getResourceID(); + if (idOrObject != null) + { + containerOrResourceID = (CDOID)transaction.convertObjectToID(idOrObject); + } + } + + return containerOrResourceID; + } + + private void analyzeRevisionDelta(InternalCDOObject dirtyObject) throws CommitIntegrityException + { + // Getting the deltas from the TX is not a good idea... + // We better recompute a fresh delta: + // + InternalCDORevision cleanRev = transaction.getCleanRevisions().get(dirtyObject); + CheckUtil.checkNull(cleanRev, "Could not obtain clean revision for dirty object " + dirtyObject); + InternalCDORevision dirtyRev = dirtyObject.cdoRevision(); + CDORevisionDelta rDelta = CDORevisionDeltaUtil.create(cleanRev, dirtyRev); + + for (CDOFeatureDelta featureDelta : rDelta.getFeatureDeltas()) + { + EStructuralFeature feat = featureDelta.getFeature(); + if (feat == CDOContainerFeatureDelta.CONTAINER_FEATURE) + { + // Object is dirty with respect to its container; this means it was moved; + // We must ensure that both the old and new containers are included + checkContainerIncluded(dirtyObject, "moved"); + CDOID containerOrResourceID = getContainerOrResourceID(cleanRev); + checkIncluded(containerOrResourceID, "former container (or resource) of moved", dirtyObject); + } + else if (feat instanceof EReference) + { + EReference ref = (EReference)feat; + if (ref.isContainment() || ref.getEOpposite() != null) + { + // Object is dirty with respect to a containment feature + // We must ensure that any children that were added/removed, are also + // included in the commit + + if (featureDelta instanceof CDOListFeatureDelta) + { + for (CDOFeatureDelta innerFeatDelta : ((CDOListFeatureDelta)featureDelta).getListChanges()) + { + checkContainmentDelta(innerFeatDelta, dirtyObject); + } + } + else + { + checkContainmentDelta(featureDelta, dirtyObject); + } + } + } + } + } + + private void checkContainmentDelta(CDOFeatureDelta featureDelta, CDOObject dirtyObject) + throws CommitIntegrityException + { + if (featureDelta instanceof CDORemoveFeatureDelta) + { + Object idOrObject = ((CDORemoveFeatureDelta)featureDelta).getValue(); + CDOID id = (CDOID)transaction.convertObjectToID(idOrObject); + checkIncluded(id, "removed child of", dirtyObject); + } + else if (featureDelta instanceof CDOAddFeatureDelta) + { + Object idOrObject = ((CDOAddFeatureDelta)featureDelta).getValue(); + CDOID id = (CDOID)transaction.convertObjectToID(idOrObject); + if (id.getType() != CDOID.Type.NULL) + { + checkIncluded(id, "added child of", dirtyObject); + } + } + else if (featureDelta instanceof CDOSetFeatureDelta) + { + Object newIDOrObject = ((CDOSetFeatureDelta)featureDelta).getValue(); + Object oldIDOrObject = ((CDOSetFeatureDelta)featureDelta).getOldValue(); + CDOID oldID = (CDOID)transaction.convertObjectToID(oldIDOrObject); + if (oldIDOrObject != null) + { + if (newIDOrObject == null) + { + // Removal: old child must be included + checkIncluded(oldID, "removed child of", dirtyObject); + } + else + { + // Change: both old and new child must be included + checkIncluded(oldID, "former child of", dirtyObject); + CDOID newID = (CDOID)transaction.convertObjectToID(newIDOrObject); + checkIncluded(newID, "new child of", dirtyObject); + } + } + else + { + // New child, no old child + CDOID newID = (CDOID)transaction.convertObjectToID(newIDOrObject); + checkIncluded(newID, "new child of", dirtyObject); + } + } + else if (featureDelta instanceof CDOClearFeatureDelta) + { + EStructuralFeature feat = ((CDOClearFeatureDelta)featureDelta).getFeature(); + InternalCDORevision cleanRev = transaction.getCleanRevisions().get(dirtyObject); + int n = cleanRev.size(feat); + for (int i = 0; i < n; i++) + { + Object idOrObject = cleanRev.get(feat, i); + CDOID id = (CDOID)transaction.convertObjectToID(idOrObject); + checkIncluded(id, "removed child of", dirtyObject); + } + } + else if (featureDelta instanceof CDOUnsetFeatureDelta) + { + EStructuralFeature feat = ((CDOUnsetFeatureDelta)featureDelta).getFeature(); + InternalCDORevision cleanRev = transaction.getCleanRevisions().get(dirtyObject); + Object idOrObject = cleanRev.getValue(feat); + CDOID id = (CDOID)transaction.convertObjectToID(idOrObject); + checkIncluded(id, "removed child of", dirtyObject); + } + else + { + throw new RuntimeException("Unexpected delta type: " + featureDelta.getClass().getSimpleName()); + } + } + + private void checkIncluded(CDOID id, String msg, CDOObject o) throws CommitIntegrityException + { + if (id.getType() == Type.NULL) + { + throw new IllegalArgumentException("CDOID must not be of type NULL"); + } + + if (!dirtyIDs.contains(id) && !detachedIDs.contains(id) && !newIDs.contains(id)) + { + CDOObject missingObject = transaction.getObject(id); + if (missingObject == null) + { + throw new IllegalStateException("Could not find object for CDOID " + id); + } + missingObjects.add(missingObject); + + if (exceptionMessage.length() > 0) + { + exceptionMessage.append('\n'); + } + String m = String.format("The %s object %s needs to be included in the commit but isn't", msg, o); + exceptionMessage.append(m); + + if (style == Style.EXCEPTION_FAST) + { + throw createException(); + } + } + } + + private CommitIntegrityException createException() + { + return new CommitIntegrityException(exceptionMessage.toString(), missingObjects); + } + + /** + * Checks whether the container of a given object is included in the commit + * + * @param msgFrag + * @throws CommitIntegrityException + */ + private void checkContainerIncluded(CDOObject object, String msgFrag) throws CommitIntegrityException + { + EObject eContainer = object.eContainer(); + if (eContainer == null) + { + // It's a top-level object + CDOResource resource = object.cdoDirectResource(); + checkIncluded(resource.cdoID(), "resource of " + msgFrag, object); + } + else + { + CDOObject container = CDOUtil.getCDOObject(eContainer); + checkIncluded(container.cdoID(), "container of " + msgFrag, object); + } + } + + private void checkCurrentBidiRefTargetsIncluded(CDOObject referencer, String msgFrag) throws CommitIntegrityException + { + for (EReference eRef : referencer.eClass().getEAllReferences()) + { + if (eRef.getEOpposite() != null) + { + if (eRef.isMany()) + { + EList<?> list = (EList<?>)referencer.eGet(eRef); + for (Object element : list) + { + checkBidiRefTargetIncluded(element, referencer, msgFrag); + } + } + else + { + Object refTarget = referencer.eGet(eRef); + if (refTarget != null) + { + checkBidiRefTargetIncluded(refTarget, referencer, msgFrag); + } + } + } + } + } + + private void checkFormerBidiRefTargetsIncluded(CDOObject referencer, String msgFrag) throws CommitIntegrityException + { + // The referencer argument should really be a detached object, and so we know + // that we can find the pre-detach revision in tx.getFormerRevisions(). However, + // the object may have already been dirty prior to detachment, so we check the + // clean revisions first. + // + InternalCDORevision cleanRev = transaction.getCleanRevisions().get(referencer); + CheckUtil.checkState(cleanRev, "cleanRev"); + + for (EReference eRef : referencer.eClass().getEAllReferences()) + { + if (eRef.getEOpposite() != null) + { + Object value = cleanRev.get(eRef, EStore.NO_INDEX); + if (value != null) + { + if (eRef.isMany()) + { + EList<?> list = (EList<?>)value; + for (Object element : list) + { + checkBidiRefTargetIncluded(element, referencer, msgFrag); + } + } + else + { + checkBidiRefTargetIncluded(value, referencer, msgFrag); + } + } + } + } + } + + private void checkBidiRefTargetIncluded(Object refTarget, CDOObject referencer, String msgFrag) + throws CommitIntegrityException + { + CheckUtil.checkArg(refTarget, "refTarget"); + CDOID refTargetID = null; + if (refTarget instanceof EObject) + { + refTargetID = CDOUtil.getCDOObject((EObject)refTarget).cdoID(); + } + else if (refTarget instanceof CDOID) + { + refTargetID = (CDOID)refTarget; + } + checkIncluded(refTargetID, "reference target of " + msgFrag, referencer); + } + + private void checkFormerContainerIncluded(CDOObject detachedObject) throws CommitIntegrityException + { + InternalCDORevision rev = transaction.getCleanRevisions().get(detachedObject); + CheckUtil.checkNull(rev, "Could not obtain clean revision for detached object " + detachedObject); + CDOID id = getContainerOrResourceID(rev); + checkIncluded(id, "former container (or resource) of detached", detachedObject); + } + + /** + * Designates an exception style for a {@link CommitIntegrityCheck} + * + * @author Caspar De Groot + */ + public enum Style + { + /** + * Throw an exception as soon as this {@link CommitIntegrityCheck} encounters the first problem + */ + EXCEPTION_FAST, + + /** + * Throw an exception when this {@link CommitIntegrityCheck} finishes performing all possible checks, in case any + * problems were found + */ + EXCEPTION, + + /** + * Do not throw an exception. Caller must invoke {@link CommitIntegrityCheck#getMissingObjects()} to find out if the + * check discovered any problems. + */ + NO_EXCEPTION + } +} diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/util/CommitIntegrityException.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/util/CommitIntegrityException.java new file mode 100644 index 0000000000..f4d08ec278 --- /dev/null +++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/util/CommitIntegrityException.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2004 - 2010 Eike Stepper (Berlin, Germany) and others. + * 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: + * Caspar De Groot - initial API and implementation + */ +package org.eclipse.emf.cdo.util; + +import org.eclipse.emf.ecore.EObject; + +import java.util.Set; + +/** + * @author Caspar De Groot + * @since 4.0 + */ +public class CommitIntegrityException extends CommitException +{ + private static final long serialVersionUID = 3302652235940254454L; + + private Set<? extends EObject> missingObjects; + + public CommitIntegrityException(String msg, Set<? extends EObject> missingObjects) + { + super(msg); + this.missingObjects = missingObjects; + } + + public Set<? extends EObject> getMissingObjects() + { + return missingObjects; + } +} 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 805c1754d8..5cbdb0dfe8 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 @@ -798,6 +798,8 @@ public final class CDOStateMachine extends FiniteStateMachine<CDOState, CDOEvent public void execute(InternalCDOObject object, CDOState state, CDOEvent event, Object featureDelta) { InternalCDOTransaction transaction = object.cdoView().toTransaction(); + InternalCDORevision cleanRevision = object.cdoRevision(); + transaction.getCleanRevisions().put(object, cleanRevision); // Copy revision InternalCDORevision revision = object.cdoRevision().copy(); diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOTransactionImpl.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOTransactionImpl.java index 462441d0d5..bb9d1ef556 100644 --- a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOTransactionImpl.java +++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOTransactionImpl.java @@ -69,6 +69,7 @@ import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevisionCache; import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevisionDelta; import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevisionManager; import org.eclipse.emf.cdo.spi.common.revision.PointerCDORevision; +import org.eclipse.emf.cdo.transaction.CDOCommitContext; import org.eclipse.emf.cdo.transaction.CDOConflictResolver; import org.eclipse.emf.cdo.transaction.CDOConflictResolver2; import org.eclipse.emf.cdo.transaction.CDODefaultTransactionHandler; @@ -83,6 +84,7 @@ import org.eclipse.emf.cdo.transaction.CDOUserSavepoint; import org.eclipse.emf.cdo.util.CDOURIUtil; import org.eclipse.emf.cdo.util.CDOUtil; import org.eclipse.emf.cdo.util.CommitException; +import org.eclipse.emf.cdo.util.CommitIntegrityCheck; import org.eclipse.emf.cdo.util.LegacyModeNotEnabledException; import org.eclipse.emf.cdo.util.ObjectNotFoundException; @@ -191,6 +193,10 @@ public class CDOTransactionImpl extends CDOViewImpl implements InternalCDOTransa } }; + private Set<EObject> committables; + + private Map<InternalCDOObject, InternalCDORevision> cleanRevisions = new HashMap<InternalCDOObject, InternalCDORevision>(); + public CDOTransactionImpl(CDOBranch branch) { super(branch, UNSPECIFIED_DATE); @@ -978,7 +984,7 @@ public class CDOTransactionImpl extends CDOViewImpl implements InternalCDOTransa { checkActive(); rollback(firstSavepoint); - cleanUp(); + cleanUp(null); } private void removeObject(CDOID id, CDOObject object) @@ -1203,6 +1209,11 @@ public class CDOTransactionImpl extends CDOViewImpl implements InternalCDOTransa formerRevisionKeys.put(object, revKey); } + if (!cleanRevisions.containsKey(object)) + { + cleanRevisions.put(object, object.cdoRevision()); + } + // Object may have been reattached previously, in which case it must // here be removed from the collection of reattached objects lastSavepoint.getReattachedObjects().remove(id); @@ -1551,19 +1562,60 @@ public class CDOTransactionImpl extends CDOViewImpl implements InternalCDOTransa return newPackages; } - private void cleanUp() + private void cleanUp(CDOCommitContext commitContext) { - lastSavepoint = firstSavepoint; - firstSavepoint.clear(); - firstSavepoint.setNextSavepoint(null); - firstSavepoint.getSharedDetachedObjects().clear(); + if (commitContext == null || !commitContext.isPartialCommit()) + { + lastSavepoint = firstSavepoint; + firstSavepoint.clear(); + firstSavepoint.setNextSavepoint(null); + firstSavepoint.getSharedDetachedObjects().clear(); + + // Bug 283985 (Re-attachment) + formerRevisionKeys.clear(); + + cleanRevisions.clear(); + dirty = false; + conflict = 0; + lastTemporaryID.set(0); + } + else + { + collapseSavepoints(commitContext); - // Bug 283985 (Re-attachment) - formerRevisionKeys.clear(); + for (CDOObject object : commitContext.getDetachedObjects().values()) + { + formerRevisionKeys.remove(object); + } + } - dirty = false; - conflict = 0; - lastTemporaryID.set(0); + // Reset partial-commit filter + committables = null; + } + + private void collapseSavepoints(CDOCommitContext commitContext) + { + InternalCDOSavepoint newSavepoint = createSavepoint(null); + copyUncommitted(lastSavepoint.getAllNewObjects(), commitContext.getNewObjects(), newSavepoint.getNewObjects()); + copyUncommitted(lastSavepoint.getAllDirtyObjects(), commitContext.getDirtyObjects(), newSavepoint.getDirtyObjects()); + copyUncommitted(lastSavepoint.getAllRevisionDeltas(), commitContext.getRevisionDeltas(), + newSavepoint.getRevisionDeltas()); + copyUncommitted(lastSavepoint.getAllDetachedObjects(), commitContext.getDetachedObjects(), + newSavepoint.getDetachedObjects()); + lastSavepoint = newSavepoint; + firstSavepoint = lastSavepoint; + } + + private <T> void copyUncommitted(Map<CDOID, T> oldSavepointMap, Map<CDOID, T> commitContextMap, + Map<CDOID, T> newSavepointMap) + { + for (Entry<CDOID, T> entry : oldSavepointMap.entrySet()) + { + if (!commitContextMap.containsKey(entry.getKey())) + { + newSavepointMap.put(entry.getKey(), entry.getValue()); + } + } } public CDOSavepoint[] exportChanges(OutputStream stream) throws IOException @@ -1925,6 +1977,21 @@ public class CDOTransactionImpl extends CDOViewImpl implements InternalCDOTransa commitComment = comment; } + public void setCommittables(Set<EObject> committables) + { + this.committables = committables; + } + + public Set<EObject> getCommittables() + { + return committables; + } + + public Map<InternalCDOObject, InternalCDORevision> getCleanRevisions() + { + return cleanRevisions; + } + /** * @author Simon McDuff */ @@ -1934,6 +2001,21 @@ public class CDOTransactionImpl extends CDOViewImpl implements InternalCDOTransa private CDOCommitData commitData; + private Map<CDOID, CDOObject> newObjects; + + private Map<CDOID, CDOObject> detachedObjects; + + private Map<CDOID, CDORevisionDelta> revisionDeltas; + + private Map<CDOID, CDOObject> dirtyObjects; + + /** + * Tracks whether this commit is *actually* partial or not. (Having tx.committables != null does not in itself mean + * that the commit will be partial, because the committables could cover all dirty/new/detached objects. But this + * boolean gets set to reflect whether the commit will really commit less than all dirty/new/detached objects.) + */ + private boolean isPartialCommit; + public CDOCommitContextImpl(InternalCDOTransaction transaction) { this.transaction = transaction; @@ -1943,29 +2025,59 @@ public class CDOTransactionImpl extends CDOViewImpl implements InternalCDOTransa private void calculateCommitData() { List<CDOPackageUnit> newPackageUnits = analyzeNewPackages(); - List<CDOIDAndVersion> revisions = new ArrayList<CDOIDAndVersion>(getNewObjects().size()); - for (CDOObject newObject : getNewObjects().values()) + newObjects = filterCommittables(transaction.getNewObjects()); + List<CDOIDAndVersion> revisions = new ArrayList<CDOIDAndVersion>(newObjects.size()); + for (CDOObject newObject : newObjects.values()) { revisions.add(newObject.cdoRevision()); } - List<CDORevisionKey> deltas = new ArrayList<CDORevisionKey>(getRevisionDeltas().size()); - for (CDORevisionDelta delta : getRevisionDeltas().values()) + revisionDeltas = filterCommittables(transaction.getRevisionDeltas()); + List<CDORevisionKey> deltas = new ArrayList<CDORevisionKey>(revisionDeltas.size()); + for (CDORevisionDelta delta : revisionDeltas.values()) { deltas.add(delta); } - List<CDOIDAndVersion> detached = new ArrayList<CDOIDAndVersion>(getDetachedObjects().size()); - for (CDOID id : getDetachedObjects().keySet()) + detachedObjects = filterCommittables(transaction.getDetachedObjects()); + List<CDOIDAndVersion> detached = new ArrayList<CDOIDAndVersion>(detachedObjects.size()); + for (CDOID id : detachedObjects.keySet()) { // Add "version-less" key. // CDOSessionImpl.reviseRevisions() will call reviseLatest() accordingly. detached.add(CDOIDUtil.createIDAndVersion(id, CDOBranchVersion.UNSPECIFIED_VERSION)); } + dirtyObjects = filterCommittables(transaction.getDirtyObjects()); + commitData = new CDOCommitDataImpl(newPackageUnits, revisions, deltas, detached); } + private <T> Map<CDOID, T> filterCommittables(Map<CDOID, T> map) + { + if (committables == null) + { + // No partial commit filter -- nothing to do + return map; + } + + Map<CDOID, T> newMap = new HashMap<CDOID, T>(); + for (CDOID id : map.keySet()) + { + CDOObject o = getObject(id); + if (committables.contains(o)) + { + newMap.put(id, map.get(id)); + } + else + { + isPartialCommit = true; + } + } + + return newMap; + } + public InternalCDOTransaction getTransaction() { return transaction; @@ -1978,12 +2090,12 @@ public class CDOTransactionImpl extends CDOViewImpl implements InternalCDOTransa public Map<CDOID, CDOObject> getDirtyObjects() { - return transaction.getDirtyObjects(); + return dirtyObjects; } public Map<CDOID, CDOObject> getNewObjects() { - return transaction.getNewObjects(); + return newObjects; } public List<CDOPackageUnit> getNewPackageUnits() @@ -1993,12 +2105,12 @@ public class CDOTransactionImpl extends CDOViewImpl implements InternalCDOTransa public Map<CDOID, CDOObject> getDetachedObjects() { - return transaction.getDetachedObjects(); + return detachedObjects; } public Map<CDOID, CDORevisionDelta> getRevisionDeltas() { - return transaction.getRevisionDeltas(); + return revisionDeltas; } public void preCommit() @@ -2046,6 +2158,13 @@ public class CDOTransactionImpl extends CDOViewImpl implements InternalCDOTransa try { + // TODO (CD) It might be better to always do the checks, + // instead of only for partial commits + if (isPartialCommit) + { + new CommitIntegrityCheck(this, CommitIntegrityCheck.Style.EXCEPTION_FAST).check(); + } + preCommit(getNewObjects()); preCommit(getDirtyObjects()); } @@ -2110,7 +2229,7 @@ public class CDOTransactionImpl extends CDOViewImpl implements InternalCDOTransa getChangeSubscriptionManager().committedTransaction(transaction, this); getAdapterManager().committedTransaction(transaction, this); - cleanUp(); + cleanUp(this); Map<CDOID, CDOID> idMappings = result.getIDMappings(); IListener[] listeners = getListeners(); if (listeners != null) @@ -2180,6 +2299,11 @@ public class CDOTransactionImpl extends CDOViewImpl implements InternalCDOTransa } } } + + public boolean isPartialCommit() + { + return isPartialCommit; + } } /** diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOXACommitContextImpl.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOXACommitContextImpl.java index 3d297842b4..03c69200b3 100644 --- a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOXACommitContextImpl.java +++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOXACommitContextImpl.java @@ -134,6 +134,11 @@ public class CDOXACommitContextImpl implements InternalCDOXACommitContext return delegateCommitContext.getCommitData(); } + public boolean isPartialCommit() + { + return delegateCommitContext.isPartialCommit(); + } + public Object call() throws Exception { state.handle(this, progressMonitor); diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOXATransactionImpl.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOXATransactionImpl.java index 9da79f9549..e9d23acd31 100644 --- a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOXATransactionImpl.java +++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOXATransactionImpl.java @@ -34,6 +34,7 @@ import org.eclipse.net4j.util.om.trace.ContextTracer; import org.eclipse.emf.common.notify.Adapter; import org.eclipse.emf.common.notify.Notification; import org.eclipse.emf.common.notify.Notifier; +import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.spi.cdo.CDOSessionProtocol; import org.eclipse.emf.spi.cdo.CDOSessionProtocol.CommitTransactionResult; import org.eclipse.emf.spi.cdo.CDOTransactionStrategy; @@ -435,6 +436,16 @@ public class CDOXATransactionImpl implements InternalCDOXATransaction } } + public void setCommittables(Set<EObject> committables) + { + throw new UnsupportedOperationException(); + } + + public Set<EObject> getCommittables() + { + throw new UnsupportedOperationException(); + } + /** * @author Simon McDuff */ diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/spi/cdo/InternalCDOTransaction.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/spi/cdo/InternalCDOTransaction.java index a934e1746a..886fb147f3 100644 --- a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/spi/cdo/InternalCDOTransaction.java +++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/spi/cdo/InternalCDOTransaction.java @@ -20,6 +20,7 @@ import org.eclipse.emf.cdo.common.revision.CDORevisionProvider; import org.eclipse.emf.cdo.common.revision.delta.CDOFeatureDelta; import org.eclipse.emf.cdo.common.revision.delta.CDORevisionDelta; import org.eclipse.emf.cdo.eresource.CDOResourceFolder; +import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevision; import org.eclipse.emf.cdo.transaction.CDOCommitContext; import org.eclipse.emf.cdo.transaction.CDOTransaction; @@ -92,6 +93,11 @@ public interface InternalCDOTransaction extends CDOTransaction, InternalCDOUserT public Map<InternalCDOObject, CDORevisionKey> getFormerRevisionKeys(); /** + * @since 4.0 + */ + public Map<InternalCDOObject, InternalCDORevision> getCleanRevisions(); + + /** * Provides a context for a commit operation. * * @author Simon McDuff |