diff options
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.java | 422 |
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 + } +} |