blob: 08e64b74a3e2ecdf37160525f922e6696093ef00 [file] [log] [blame]
/*
* Copyright (c) 2009-2013 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.commit.CDOChangeSetData;
import org.eclipse.emf.cdo.common.id.CDOID;
import org.eclipse.emf.cdo.common.revision.CDOIDAndVersion;
import org.eclipse.emf.cdo.common.revision.CDORevision;
import org.eclipse.emf.cdo.common.revision.CDORevisionKey;
import org.eclipse.emf.cdo.common.revision.CDORevisionUtil;
import org.eclipse.emf.cdo.common.revision.delta.CDOFeatureDelta;
import org.eclipse.emf.cdo.common.revision.delta.CDORevisionDelta;
import org.eclipse.emf.cdo.common.util.CDOException;
import org.eclipse.emf.cdo.spi.common.revision.CDORevisionMerger;
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.CDOConflictResolver2;
import org.eclipse.emf.cdo.transaction.CDODefaultTransactionHandler;
import org.eclipse.emf.cdo.transaction.CDOTransaction;
import org.eclipse.emf.cdo.transaction.CDOTransactionHandler;
import org.eclipse.emf.cdo.util.CDOUtil;
import org.eclipse.emf.cdo.view.CDOAdapterPolicy;
import org.eclipse.emf.cdo.view.CDOViewInvalidationEvent;
import org.eclipse.emf.internal.cdo.bundle.OM;
import org.eclipse.emf.internal.cdo.messages.Messages;
import org.eclipse.emf.internal.cdo.object.CDOObjectMerger;
import org.eclipse.emf.internal.cdo.view.CDOStateMachine2;
import org.eclipse.net4j.util.collection.Pair;
import org.eclipse.net4j.util.event.IEvent;
import org.eclipse.net4j.util.event.IListener;
import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
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 2.0
*/
public abstract class AbstractObjectConflictResolver extends AbstractConflictResolver implements CDOConflictResolver2
{
public AbstractObjectConflictResolver()
{
}
public void resolveConflicts(Set<CDOObject> conflicts)
{
Map<CDOID, CDORevisionDelta> localDeltas = getTransaction().getRevisionDeltas();
for (CDOObject conflict : conflicts)
{
CDORevisionDelta localDelta = localDeltas.get(conflict.cdoID());
resolveConflict(conflict, localDelta);
}
}
/**
* Resolves the conflict of a single object in the current transaction.
*/
protected void resolveConflict(CDOObject conflict, CDORevisionDelta localDelta)
{
// Do nothing
}
/**
* @since 4.0
*/
public void resolveConflicts(Map<CDOObject, Pair<CDORevision, CDORevisionDelta>> conflicts,
List<CDORevisionDelta> allRemoteDeltas)
{
Map<CDOID, CDORevisionDelta> localDeltas = getTransaction().getRevisionDeltas();
for (Entry<CDOObject, Pair<CDORevision, CDORevisionDelta>> entry : conflicts.entrySet())
{
CDOObject conflict = entry.getKey();
CDORevision oldRevision = entry.getValue().getElement1();
CDORevisionDelta remoteDelta = entry.getValue().getElement2();
CDORevisionDelta localDelta = localDeltas.get(conflict.cdoID());
try
{
resolveConflict(conflict, oldRevision, localDelta, remoteDelta, allRemoteDeltas);
}
catch (Exception ex)
{
OM.LOG.error(ex);
}
}
}
/**
* Resolves the conflict of a single object in the current transaction. Depending on the decision taken to resolve the
* conflict, it may be necessary to adjust the notification that will be sent to the adapters in the current
* transaction. This can be achieved by adjusting the {@link CDORevisionDelta} in <code>deltas</code>.
*
* @since 4.0
*/
protected void resolveConflict(CDOObject conflict, CDORevision oldRemoteRevision, CDORevisionDelta localDelta,
CDORevisionDelta remoteDelta, List<CDORevisionDelta> allRemoteDeltas)
{
throw new UnsupportedOperationException("Must be overridden");
}
/**
* @deprecated Don't call this method. Rolling back single objects is exremely risky.
*/
@Deprecated
public static void rollbackObject(CDOObject object)
{
CDOStateMachine2.INSTANCE.rollback((InternalCDOObject)object);
}
public static void readObject(CDOObject object)
{
CDOStateMachine2.INSTANCE.read((InternalCDOObject)object);
}
/**
* TODO See {@link CDOObjectMerger}!!!
*/
public static void changeObject(CDOObject object, CDORevisionDelta revisionDelta)
{
readObject(object);
InternalCDORevision revision = (InternalCDORevision)object.cdoRevision().copy();
((InternalCDORevisionDelta)revisionDelta).setVersion(revision.getVersion());
CDORevisionMerger merger = new CDORevisionMerger();
merger.merge(revision, revisionDelta);
((InternalCDOObject)object).cdoInternalSetRevision(revision);
((InternalCDOObject)object).cdoInternalSetState(CDOState.DIRTY);
((InternalCDOObject)object).cdoInternalPostLoad();
}
/**
* A conflict resolver implementation that takes all the new remote state of the conflicting objects and then applies
* the locally existing changes of the current transaction.
*
* @author Eike Stepper
* @since 2.0
*/
@Deprecated
public static class TakeRemoteChangesThenApplyLocalChanges extends AbstractObjectConflictResolver
{
public TakeRemoteChangesThenApplyLocalChanges()
{
}
@Override
public void resolveConflicts(Map<CDOObject, Pair<CDORevision, CDORevisionDelta>> conflicts,
List<CDORevisionDelta> allRemoteDeltas)
{
CDOChangeSetData remoteChangeSet = createChangeSet(allRemoteDeltas);
InternalCDOTransaction transaction = (InternalCDOTransaction)getTransaction();
CDOChangeSetData localChangeSet = transaction.getChangeSetData();
transaction.rollback();
transaction.applyChangeSet(remoteChangeSet, transaction, transaction, transaction, false);
transaction.applyChangeSet(localChangeSet, transaction, transaction, transaction, false);
}
private CDOChangeSetData createChangeSet(List<CDORevisionDelta> revisionDeltas)
{
List<CDOIDAndVersion> newObjects = Collections.emptyList();
List<CDORevisionKey> changedObjects = new ArrayList<CDORevisionKey>();
List<CDOIDAndVersion> detachedObjects = Collections.emptyList();
for (CDORevisionDelta delta : revisionDeltas)
{
changedObjects.add(delta);
}
CDOChangeSetData remoteChangeSet = CDORevisionUtil.createChangeSetData(newObjects, changedObjects,
detachedObjects);
return remoteChangeSet;
}
}
/**
* If the meaning of this type isn't clear, there really should be more of a description here...
*
* @author Eike Stepper
* @since 2.0
*/
public static abstract class ThreeWayMerge extends AbstractObjectConflictResolver implements CDOAdapterPolicy
{
private CDOTransactionHandler handler = new CDODefaultTransactionHandler()
{
@Override
public void modifyingObject(CDOTransaction transaction, CDOObject object, CDOFeatureDelta ignored)
{
if (getTransaction() == transaction)
{
adapter.attach(object);
}
}
@Override
public void committedTransaction(CDOTransaction transaction, CDOCommitContext commitContext)
{
if (getTransaction() == transaction)
{
adapter.reset();
collector.reset();
}
}
@Override
public void rolledBackTransaction(CDOTransaction transaction)
{
// Reset the accumulation only if it rolled back the transaction completely
if (getTransaction() == transaction && transaction.getLastSavepoint().getPreviousSavepoint() == null)
{
adapter.reset();
collector.reset();
}
}
};
private CDOChangeSubscriptionAdapter adapter;
private RevisionDeltaCollector collector = new RevisionDeltaCollector();
public ThreeWayMerge()
{
}
public boolean isValid(EObject object, Adapter adapter)
{
return adapter instanceof CDOChangeSubscriptionAdapter;
}
@Override
protected void hookTransaction(CDOTransaction transaction)
{
transaction.addTransactionHandler(handler);
adapter = new CDOChangeSubscriptionAdapter(getTransaction());
transaction.addListener(collector);
}
@Override
protected void unhookTransaction(CDOTransaction transaction)
{
transaction.removeListener(collector);
adapter.dispose();
adapter = null;
transaction.removeTransactionHandler(handler);
}
@Override
public void resolveConflicts(Set<CDOObject> conflicts)
{
// Do nothing
}
@Override
protected void resolveConflict(CDOObject conflict, CDORevision oldRemoteRevision, CDORevisionDelta localDelta,
CDORevisionDelta remoteDelta, List<CDORevisionDelta> allRemoteDeltas)
{
resolveConflict(conflict, localDelta, collector.getDeltas(conflict));
}
protected abstract void resolveConflict(CDOObject conflict, CDORevisionDelta localDelta,
List<CDORevisionDelta> remoteDeltas);
/**
* 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 static class RevisionDeltaCollector implements IListener
{
private Map<CDOObject, List<CDORevisionDelta>> deltas = new HashMap<CDOObject, List<CDORevisionDelta>>();
public RevisionDeltaCollector()
{
}
public List<CDORevisionDelta> getDeltas(CDOObject notifier)
{
List<CDORevisionDelta> list = deltas.get(CDOUtil.getEObject(notifier));
if (list == null)
{
return Collections.emptyList();
}
return list;
}
public void reset()
{
deltas.clear();
}
public void notifyEvent(IEvent event)
{
try
{
if (event instanceof CDOViewInvalidationEvent)
{
CDOViewInvalidationEvent e = (CDOViewInvalidationEvent)event;
for (Map.Entry<CDOObject, CDORevisionDelta> entry : e.getRevisionDeltas().entrySet())
{
CDOObject notifier = entry.getKey();
List<CDORevisionDelta> list = deltas.get(notifier);
if (list == null)
{
list = new ArrayList<CDORevisionDelta>();
deltas.put(notifier, list);
}
CDORevisionDelta delta = entry.getValue();
list.add(delta);
}
}
}
catch (Exception ex)
{
OM.LOG.error(ex);
}
}
}
}
/**
* If the meaning of this type isn't clear, there really should be more of a description here...
*
* @author Eike Stepper
* @since 2.0
* @deprecated As of 4.0 use CDOMergingConflictResolver
*/
@Deprecated
public static class MergeLocalChangesPerFeature extends ThreeWayMerge
{
public MergeLocalChangesPerFeature()
{
}
@Override
protected void resolveConflict(CDOObject conflict, CDORevisionDelta localDelta, List<CDORevisionDelta> remoteDeltas)
{
if (hasFeatureConflicts(localDelta, remoteDeltas))
{
// TODO localDelta may be corrupt already and the transaction will not be able to restore it!!!
throw new CDOException(Messages.getString("AbstractObjectConflictResolver.0")); //$NON-NLS-1$
}
rollbackObject(conflict);
// Add remote deltas to local delta
for (CDORevisionDelta remoteDelta : remoteDeltas)
{
for (CDOFeatureDelta remoteFeatureDelta : remoteDelta.getFeatureDeltas())
{
// TODO Add public API for this:
((InternalCDORevisionDelta)localDelta).mergeFeatureDelta(remoteFeatureDelta, null);
}
}
changeObject(conflict, localDelta);
}
protected boolean hasFeatureConflicts(CDORevisionDelta localDelta, List<CDORevisionDelta> remoteDeltas)
{
Set<EStructuralFeature> features = new HashSet<EStructuralFeature>();
for (CDOFeatureDelta localFeatureDelta : localDelta.getFeatureDeltas())
{
features.add(localFeatureDelta.getFeature());
}
for (CDORevisionDelta remoteDelta : remoteDeltas)
{
for (CDOFeatureDelta remoteFeatureDelta : remoteDelta.getFeatureDeltas())
{
EStructuralFeature feature = remoteFeatureDelta.getFeature();
if (features.contains(feature))
{
return true;
}
}
}
return false;
}
}
}