summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCaspar De Groot2010-09-13 01:34:41 (EDT)
committerCaspar De Groot2010-09-13 01:34:41 (EDT)
commit37caac1363b62130146f43a45e91b74dcaa09198 (patch)
tree48f4339cb18bdec9a4145acc4e9c9f924c3abe10
parent51739dba02253c70c474a5556d5718548141d043 (diff)
downloadcdo-37caac1363b62130146f43a45e91b74dcaa09198.zip
cdo-37caac1363b62130146f43a45e91b74dcaa09198.tar.gz
cdo-37caac1363b62130146f43a45e91b74dcaa09198.tar.bz2
[312535] Partial commits
https://bugs.eclipse.org/bugs/show_bug.cgi?id=312535
-rw-r--r--plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/revision/delta/CDOSetFeatureDelta.java10
-rw-r--r--plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/internal/common/revision/delta/CDORevisionDeltaImpl.java2
-rw-r--r--plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/internal/common/revision/delta/CDOSetFeatureDeltaImpl.java32
-rw-r--r--plugins/org.eclipse.emf.cdo.tests/META-INF/MANIFEST.MF1
-rw-r--r--plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/AllConfigs.java1
-rw-r--r--plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/PartialCommitTest.java1257
-rw-r--r--plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/transaction/CDOCommitContext.java5
-rw-r--r--plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/transaction/CDOPushTransaction.java16
-rw-r--r--plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/transaction/CDOUserTransaction.java14
-rw-r--r--plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/util/CommitIntegrityCheck.java422
-rw-r--r--plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/util/CommitIntegrityException.java37
-rw-r--r--plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/CDOStateMachine.java2
-rw-r--r--plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOTransactionImpl.java168
-rw-r--r--plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOXACommitContextImpl.java5
-rw-r--r--plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOXATransactionImpl.java11
-rw-r--r--plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/spi/cdo/InternalCDOTransaction.java6
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 b929ff5..a8cadf0 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 d2f421d..9411b0c 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 9a29cfa..a6ada05 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 6258ee7..f9e4cc4 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 cb5873d..71d042e 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 0000000..902e006
--- /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 a1290c7..5e2cf48 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 d1f2297..35249d7 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 378ad14..7e86d9a 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 0000000..92e5b38
--- /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 0000000..f4d08ec
--- /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 805c175..5cbdb0d 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 462441d..bb9d1ef 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 3d29784..03c6920 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 9da79f9..e9d23ac 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 a934e17..886fb14 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