| /* |
| * Copyright (c) 2010-2015 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: |
| * Eike Stepper - initial API and implementation |
| */ |
| package org.eclipse.emf.spi.cdo; |
| |
| import org.eclipse.emf.cdo.CDOObject; |
| import org.eclipse.emf.cdo.CDOState; |
| import org.eclipse.emf.cdo.common.CDOCommonSession.Options.PassiveUpdateMode; |
| import org.eclipse.emf.cdo.common.branch.CDOBranchPoint; |
| import org.eclipse.emf.cdo.common.commit.CDOChangeSet; |
| import org.eclipse.emf.cdo.common.commit.CDOChangeSetData; |
| import org.eclipse.emf.cdo.common.id.CDOID; |
| import org.eclipse.emf.cdo.common.id.CDOIDUtil; |
| import org.eclipse.emf.cdo.common.model.EMFUtil; |
| import org.eclipse.emf.cdo.common.revision.CDOList; |
| import org.eclipse.emf.cdo.common.revision.CDORevisionKey; |
| import org.eclipse.emf.cdo.common.revision.CDORevisionValueVisitor; |
| 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.CDORemoveFeatureDelta; |
| import org.eclipse.emf.cdo.common.revision.delta.CDORevisionDelta; |
| import org.eclipse.emf.cdo.common.revision.delta.CDOSetFeatureDelta; |
| import org.eclipse.emf.cdo.common.revision.delta.CDOUnsetFeatureDelta; |
| import org.eclipse.emf.cdo.spi.common.revision.CDOFeatureDeltaVisitorImpl; |
| import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevision; |
| import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevisionDelta; |
| import org.eclipse.emf.cdo.transaction.CDOCommitContext; |
| import org.eclipse.emf.cdo.transaction.CDOMerger; |
| import org.eclipse.emf.cdo.transaction.CDOMerger.ConflictException; |
| import org.eclipse.emf.cdo.transaction.CDOSavepoint; |
| import org.eclipse.emf.cdo.transaction.CDOTransaction; |
| import org.eclipse.emf.cdo.view.CDOAdapterPolicy; |
| |
| import org.eclipse.emf.internal.cdo.bundle.OM; |
| import org.eclipse.emf.internal.cdo.view.CDOViewImpl; |
| |
| import org.eclipse.net4j.util.om.trace.ContextTracer; |
| |
| import org.eclipse.emf.ecore.EStructuralFeature; |
| |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * If the meaning of this type isn't clear, there really should be more of a description here... |
| * |
| * @author Eike Stepper |
| * @since 4.0 |
| */ |
| public class CDOMergingConflictResolver extends AbstractChangeSetsConflictResolver |
| { |
| private static final ContextTracer TRACER = new ContextTracer(OM.DEBUG_VIEW, CDOViewImpl.class); |
| |
| private CDOMerger merger; |
| |
| private long lastNonConflictTimeStamp = CDOBranchPoint.UNSPECIFIED_DATE; |
| |
| private boolean conflict; |
| |
| public CDOMergingConflictResolver(CDOMerger merger) |
| { |
| this.merger = merger; |
| } |
| |
| /** |
| * @param ensureRemoteNotifications boolean to disable the use of {@link CDOAdapterPolicy} to ensure remote changes reception for conflict resolution, true by default. Can be disabled to limit network traffic when {@link PassiveUpdateMode} is enabled and in {@link PassiveUpdateMode#CHANGES} or {@link PassiveUpdateMode#ADDITIONS} |
| * @since 4.4 |
| */ |
| public CDOMergingConflictResolver(CDOMerger merger, boolean ensureRemoteNotifications) |
| { |
| super(ensureRemoteNotifications); |
| this.merger = merger; |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| public CDOMergingConflictResolver(DefaultCDOMerger.ResolutionPreference resolutionPreference) |
| { |
| this(new DefaultCDOMerger.PerFeature.ManyValued(resolutionPreference)); |
| } |
| |
| /** |
| * @param ensureRemoteNotifications boolean to disable the use of {@link CDOAdapterPolicy} to ensure remote changes reception for conflict resolution, true by default. Can be disabled to limit network traffic when {@link PassiveUpdateMode} is enabled and in {@link PassiveUpdateMode#CHANGES} or {@link PassiveUpdateMode#ADDITIONS} |
| * @since 4.4 |
| */ |
| public CDOMergingConflictResolver(DefaultCDOMerger.ResolutionPreference resolutionPreference, |
| boolean ensureRemoteNotifications) |
| { |
| this(new DefaultCDOMerger.PerFeature.ManyValued(resolutionPreference), ensureRemoteNotifications); |
| } |
| |
| /** |
| * @since 4.4 |
| */ |
| public CDOMergingConflictResolver() |
| { |
| this(new DefaultCDOMerger.PerFeature.ManyValued()); |
| } |
| |
| /** |
| * @param ensureRemoteNotifications boolean to disable the use of {@link CDOAdapterPolicy} to ensure remote changes reception for conflict resolution, true by default. Can be disabled to limit network traffic when {@link PassiveUpdateMode} is enabled and in {@link PassiveUpdateMode#CHANGES} or {@link PassiveUpdateMode#ADDITIONS} |
| * @since 4.4 |
| */ |
| public CDOMergingConflictResolver(boolean ensureRemoteNotifications) |
| { |
| this(new DefaultCDOMerger.PerFeature.ManyValued(), ensureRemoteNotifications); |
| } |
| |
| public CDOMerger getMerger() |
| { |
| return merger; |
| } |
| |
| /** |
| * @since 4.4 |
| */ |
| public long getLastNonConflictTimeStamp() |
| { |
| return lastNonConflictTimeStamp; |
| } |
| |
| /** |
| * @since 4.4 |
| */ |
| public boolean isConflict() |
| { |
| return conflict; |
| } |
| |
| public void resolveConflicts(Set<CDOObject> conflicts) |
| { |
| CDOChangeSet remoteChangeSet = getRemoteChangeSet(); |
| while (remoteChangeSet != null) |
| { |
| resolveConflicts(conflicts, remoteChangeSet); |
| remoteChangeSet = getRemoteChangeSet(); |
| } |
| } |
| |
| /** |
| * @since 4.4 |
| */ |
| protected void resolveConflicts(Set<CDOObject> conflicts, CDOChangeSet remoteChangeSet) |
| { |
| CDOChangeSet localChangeSet = getLocalChangeSet(); |
| CDOChangeSetData result; |
| |
| try |
| { |
| result = merger.merge(localChangeSet, remoteChangeSet); |
| |
| if (!conflict) |
| { |
| lastNonConflictTimeStamp = getRemoteTimeStamp(); |
| } |
| } |
| catch (ConflictException ex) |
| { |
| result = handleConflict(ex.getResult()); |
| if (result == null) |
| { |
| conflict = true; |
| return; |
| } |
| } |
| |
| updateTransactionWithResult(conflicts, remoteChangeSet, result); |
| } |
| |
| /** |
| * @since 4.4 |
| */ |
| protected CDOChangeSetData handleConflict(CDOChangeSetData result) |
| { |
| return null; |
| } |
| |
| @Override |
| protected void hookTransaction(CDOTransaction transaction) |
| { |
| lastNonConflictTimeStamp = transaction.getSession().getLastUpdateTime(); |
| conflict = false; |
| |
| super.hookTransaction(transaction); |
| } |
| |
| @Override |
| protected void transactionCommitted(CDOCommitContext commitContext) |
| { |
| super.transactionCommitted(commitContext); |
| resetConflict(); |
| } |
| |
| @Override |
| protected void transactionRolledBack() |
| { |
| super.transactionRolledBack(); |
| resetConflict(); |
| } |
| |
| private void resetConflict() |
| { |
| lastNonConflictTimeStamp = getTransaction().getLastUpdateTime(); |
| conflict = false; |
| } |
| |
| private void updateTransactionWithResult(Set<CDOObject> conflicts, CDOChangeSet remoteChangeSet, |
| CDOChangeSetData result) |
| { |
| InternalCDOTransaction transaction = (InternalCDOTransaction)getTransaction(); |
| InternalCDOSavepoint savepoint = transaction.getLastSavepoint(); |
| |
| Map<InternalCDOObject, InternalCDORevision> cleanRevisions = transaction.getCleanRevisions(); |
| final ObjectsMapUpdater detachedObjectsUpdater = new ObjectsMapUpdater(savepoint.getDetachedObjects()); |
| |
| Map<CDOID, CDORevisionDelta> remoteDeltas = getRemoteDeltas(remoteChangeSet); |
| |
| for (CDORevisionKey key : result.getChangedObjects()) |
| { |
| InternalCDORevisionDelta resultDelta = (InternalCDORevisionDelta)key; |
| CDOID id = resultDelta.getID(); |
| |
| InternalCDOObject object = (InternalCDOObject)transaction.getObject(id, false); |
| if (object != null && conflicts.contains(object)) |
| { |
| // TODO Should the merge result be compared to non-conflicting revisions, too? |
| |
| int newVersion = computeNewVersion(object); |
| InternalCDORevision cleanRevision = cleanRevisions.get(object); |
| InternalCDORevision newLocalRevision = computeNewLocalRevision(resultDelta, newVersion, cleanRevision); |
| |
| // Adjust local object |
| object.cdoInternalSetRevision(newLocalRevision); |
| |
| final InternalCDORevision newCleanRevision = computeNewCleanRevision(remoteDeltas, id, newVersion, |
| cleanRevision); |
| |
| // Compute new local delta |
| InternalCDORevisionDelta newLocalDelta = newLocalRevision.compare(newCleanRevision); |
| if (newLocalDelta.isEmpty()) |
| { |
| CDOSavepoint currentCDOSavePoint = savepoint; |
| while (currentCDOSavePoint != null) |
| { |
| currentCDOSavePoint.getRevisionDeltas2().remove(id); |
| currentCDOSavePoint.getDirtyObjects().remove(id); |
| currentCDOSavePoint = currentCDOSavePoint.getPreviousSavepoint(); |
| } |
| object.cdoInternalSetState(CDOState.CLEAN); |
| } |
| else |
| { |
| newLocalDelta.setTarget(null); |
| CDOSavepoint currentCDOSavePoint = savepoint; |
| while (currentCDOSavePoint != null) |
| { |
| currentCDOSavePoint.getRevisionDeltas2().put(id, newLocalDelta); |
| currentCDOSavePoint.getDirtyObjects().put(id, object); |
| currentCDOSavePoint = currentCDOSavePoint.getPreviousSavepoint(); |
| } |
| object.cdoInternalSetState(CDOState.DIRTY); |
| |
| cleanRevisions.put(object, newCleanRevision); |
| |
| updateObjects(newCleanRevision, newLocalDelta, detachedObjectsUpdater); |
| } |
| object.cdoInternalPostLoad(); |
| } |
| } |
| } |
| |
| private int computeNewVersion(InternalCDOObject object) |
| { |
| InternalCDORevision localRevision = object.cdoRevision(); |
| int newVersion = localRevision.getVersion() + 1; |
| return newVersion; |
| } |
| |
| private InternalCDORevision computeNewLocalRevision(InternalCDORevisionDelta resultDelta, int newVersion, |
| InternalCDORevision cleanRevision) |
| { |
| InternalCDORevision newLocalRevision = cleanRevision.copy(); |
| newLocalRevision.setVersion(newVersion); |
| resultDelta.applyTo(newLocalRevision); |
| return newLocalRevision; |
| } |
| |
| private InternalCDORevision computeNewCleanRevision(Map<CDOID, CDORevisionDelta> remoteDeltas, CDOID id, |
| int newVersion, InternalCDORevision cleanRevision) |
| { |
| CDORevisionDelta remoteDelta = remoteDeltas.get(id); |
| if (remoteDelta != null) |
| { |
| InternalCDORevision newCleanRevision = cleanRevision.copy(); |
| newCleanRevision.setVersion(newVersion); |
| remoteDelta.applyTo(newCleanRevision); |
| return newCleanRevision; |
| } |
| |
| return cleanRevision; |
| } |
| |
| private void updateObjects(final InternalCDORevision newCleanRevision, InternalCDORevisionDelta newLocalDelta, |
| final ObjectsMapUpdater detachedObjectsUpdater) |
| { |
| newLocalDelta.accept(new CDOFeatureDeltaVisitorImpl() |
| { |
| @Override |
| public void visit(CDOAddFeatureDelta delta) |
| { |
| // recurse(newObjectsUpdater, (CDOID)delta.getValue()); |
| } |
| |
| @Override |
| public void visit(CDOClearFeatureDelta delta) |
| { |
| // TODO Only for reference features? |
| CDOList list = newCleanRevision.getList(delta.getFeature()); |
| for (Object id : list) |
| { |
| recurse(detachedObjectsUpdater, (CDOID)id); |
| } |
| } |
| |
| @Override |
| public void visit(CDORemoveFeatureDelta delta) |
| { |
| // TODO Only for reference features? |
| recurse(detachedObjectsUpdater, (CDOID)delta.getValue()); |
| } |
| |
| @Override |
| public void visit(CDOSetFeatureDelta delta) |
| { |
| // recurse(detachedObjectsUpdater, (CDOID)delta.getOldValue()); |
| // recurse(newObjectsUpdater, (CDOID)delta.getValue()); |
| } |
| |
| @Override |
| public void visit(CDOUnsetFeatureDelta delta) |
| { |
| // TODO: implement CDOMergingConflictResolver.resolveConflicts(...).new CDOFeatureDeltaVisitorImpl() |
| } |
| |
| private void recurse(final ObjectsMapUpdater objectsUpdater, CDOID id) |
| { |
| CDOObject object = objectsUpdater.update(id); |
| if (object != null) |
| { |
| InternalCDORevision revision = (InternalCDORevision)object.cdoRevision(); |
| if (revision != null) |
| { |
| revision.accept(new CDORevisionValueVisitor() |
| { |
| public void visit(EStructuralFeature feature, Object value, int index) |
| { |
| recurse(objectsUpdater, (CDOID)value); |
| } |
| }, EMFUtil.CONTAINMENT_REFERENCES); |
| } |
| } |
| } |
| }, EMFUtil.CONTAINMENT_REFERENCES); |
| } |
| |
| private Map<CDOID, CDORevisionDelta> getRemoteDeltas(CDOChangeSet remoteChangeSet) |
| { |
| Map<CDOID, CDORevisionDelta> remoteDeltas = CDOIDUtil.createMap(); |
| for (CDORevisionKey key : remoteChangeSet.getChangedObjects()) |
| { |
| if (key instanceof CDORevisionDelta) |
| { |
| CDORevisionDelta delta = (CDORevisionDelta)key; |
| remoteDeltas.put(key.getID(), delta); |
| } |
| else if (TRACER.isEnabled()) |
| { |
| TRACER.format("Not a CDORevisionDelta: {0}", key); //$NON-NLS-1$ |
| } |
| } |
| |
| return remoteDeltas; |
| } |
| |
| /** |
| * @author Eike Stepper |
| */ |
| private final class ObjectsMapUpdater |
| { |
| private final Map<CDOID, CDOObject> map; |
| |
| private final Map<CDOID, CDOObject> mapCopy; |
| |
| public ObjectsMapUpdater(Map<CDOID, CDOObject> map) |
| { |
| mapCopy = new HashMap<CDOID, CDOObject>(map); |
| map.clear(); |
| |
| this.map = map; |
| } |
| |
| public CDOObject update(CDOID id) |
| { |
| CDOObject object = mapCopy.get(id); |
| if (object == null) |
| { |
| object = getTransaction().getObject(id, true); |
| } |
| |
| if (object != null) |
| { |
| map.put(id, object); |
| return object; |
| } |
| |
| return null; |
| } |
| } |
| } |