Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/util/CommitIntegrityCheck.java')
-rw-r--r--plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/util/CommitIntegrityCheck.java422
1 files changed, 422 insertions, 0 deletions
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
+ }
+}

Back to the top