blob: e418671a3fc5ae6e6bba2b8e3ecc41d18ec2061d [file] [log] [blame]
/***************************************************************************
* Copyright (c) 2004 - 2008 Eike Stepper, Germany.
* 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.CDOAdapterPolicy;
import org.eclipse.emf.cdo.CDOCommitContext;
import org.eclipse.emf.cdo.CDOConflictResolver;
import org.eclipse.emf.cdo.CDODeltaNotification;
import org.eclipse.emf.cdo.CDOObject;
import org.eclipse.emf.cdo.CDOState;
import org.eclipse.emf.cdo.CDOTransaction;
import org.eclipse.emf.cdo.CDOTransactionHandler;
import org.eclipse.emf.cdo.common.id.CDOID;
import org.eclipse.emf.cdo.common.model.CDOFeature;
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.internal.common.revision.delta.CDORevisionMerger;
import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevision;
import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevisionDelta;
import org.eclipse.emf.cdo.util.CDODefaultTransactionHandler;
import org.eclipse.emf.internal.cdo.CDOObjectMerger;
import org.eclipse.emf.internal.cdo.CDOStateMachine;
import org.eclipse.emf.internal.cdo.InternalCDOObject;
import org.eclipse.emf.internal.cdo.bundle.OM;
import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.notify.impl.AdapterImpl;
import org.eclipse.emf.ecore.EObject;
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.Set;
/**
* @author Eike Stepper
* @since 2.0
*/
public abstract class ObjectConflictResolver implements CDOConflictResolver
{
private CDOTransaction transaction;
public ObjectConflictResolver()
{
}
public CDOTransaction getTransaction()
{
return transaction;
}
public void setTransaction(CDOTransaction transaction)
{
if (this.transaction != transaction)
{
if (this.transaction != null)
{
unhookTransaction(this.transaction);
}
this.transaction = transaction;
if (this.transaction != null)
{
hookTransaction(this.transaction);
}
}
}
public void resolveConflicts(Set<CDOObject> conflicts)
{
Map<CDOID, CDORevisionDelta> revisionDeltas = transaction.getRevisionDeltas();
for (CDOObject conflict : conflicts)
{
CDORevisionDelta revisionDelta = revisionDeltas.get(conflict.cdoID());
resolveConflict(conflict, revisionDelta);
}
}
/**
* Resolves the conflict of a single object in the current transaction.
*/
protected abstract void resolveConflict(CDOObject conflict, CDORevisionDelta revisionDelta);
protected void hookTransaction(CDOTransaction transaction)
{
}
protected void unhookTransaction(CDOTransaction transaction)
{
}
public static void rollbackObject(CDOObject object)
{
CDOStateMachine.INSTANCE.rollback((InternalCDOObject)object);
}
public static void readObject(CDOObject object)
{
CDOStateMachine.INSTANCE.read((InternalCDOObject)object);
}
/**
* TODO See {@link CDOObjectMerger}!!!
*/
public static void changeObject(CDOObject object, CDORevisionDelta revisionDelta)
{
readObject(object);
InternalCDORevision revision = (InternalCDORevision)object.cdoRevision().copy();
int originVersion = revision.getVersion();
revision.setTransactional();
((InternalCDORevisionDelta)revisionDelta).setOriginVersion(originVersion);
((InternalCDORevisionDelta)revisionDelta).setDirtyVersion(revision.getVersion());
CDORevisionMerger merger = new CDORevisionMerger();
merger.merge(revision, revisionDelta);
((InternalCDOObject)object).cdoInternalSetRevision(revision);
((InternalCDOObject)object).cdoInternalSetState(CDOState.DIRTY);
}
/**
* 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
*/
public static class TakeRemoteChangesThenApplyLocalChanges extends ObjectConflictResolver
{
public TakeRemoteChangesThenApplyLocalChanges()
{
}
@Override
protected void resolveConflict(CDOObject conflict, CDORevisionDelta revisionDelta)
{
rollbackObject(conflict);
changeObject(conflict, revisionDelta);
}
}
/**
* @author Eike Stepper
* @since 2.0
*/
public static abstract class ThreeWayMerge extends ObjectConflictResolver implements CDOAdapterPolicy
{
private ChangeSubscriptionAdapter adapter = new ChangeSubscriptionAdapter();
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();
}
}
@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();
}
}
};
public ThreeWayMerge()
{
}
public boolean isValid(EObject object, Adapter adapter)
{
return adapter instanceof ChangeSubscriptionAdapter;
}
@Override
protected void hookTransaction(CDOTransaction transaction)
{
transaction.options().addChangeSubscriptionPolicy(this);
transaction.addHandler(handler);
}
@Override
protected void unhookTransaction(CDOTransaction transaction)
{
transaction.removeHandler(handler);
transaction.options().removeChangeSubscriptionPolicy(this);
}
@Override
protected void resolveConflict(CDOObject conflict, CDORevisionDelta revisionDelta)
{
resolveConflict(conflict, revisionDelta, adapter.getRevisionDeltas(conflict));
}
protected abstract void resolveConflict(CDOObject conflict, CDORevisionDelta localDelta,
List<CDORevisionDelta> remoteDeltas);
/**
* @author Eike Stepper
* @since 2.0
*/
public static class ChangeSubscriptionAdapter extends AdapterImpl
{
private Set<CDOObject> notifiers = new HashSet<CDOObject>();
private Map<CDOObject, List<CDORevisionDelta>> deltas = new HashMap<CDOObject, List<CDORevisionDelta>>();
public ChangeSubscriptionAdapter()
{
}
public List<CDORevisionDelta> getRevisionDeltas(CDOObject notifier)
{
List<CDORevisionDelta> list = deltas.get(notifier);
if (list == null)
{
return Collections.emptyList();
}
return list;
}
public Set<CDOObject> getNotifiers()
{
return notifiers;
}
public Map<CDOObject, List<CDORevisionDelta>> getDeltas()
{
return deltas;
}
public void attach(CDOObject notifier)
{
if (notifiers.add(notifier))
{
notifier.eAdapters().add(this);
}
}
public void reset()
{
for (CDOObject notifier : notifiers)
{
notifier.eAdapters().remove(this);
}
notifiers.clear();
deltas.clear();
}
@Override
public void notifyChanged(Notification msg)
{
try
{
if (msg instanceof CDODeltaNotification)
{
CDODeltaNotification deltaNotification = (CDODeltaNotification)msg;
Object notifier = deltaNotification.getNotifier();
if (!deltaNotification.hasNext() && notifiers.contains(notifier))
{
CDORevisionDelta revisionDelta = deltaNotification.getRevisionDelta();
List<CDORevisionDelta> list = deltas.get(notifier);
if (list == null)
{
list = new ArrayList<CDORevisionDelta>(1);
deltas.put((CDOObject)notifier, list);
}
list.add(revisionDelta);
}
}
}
catch (Exception ex)
{
OM.LOG.error(ex);
}
}
}
}
/**
* @author Eike Stepper
* @since 2.0
*/
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("Object has feature-level conflicts");
}
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).addFeatureDelta(remoteFeatureDelta);
}
}
changeObject(conflict, localDelta);
}
protected boolean hasFeatureConflicts(CDORevisionDelta localDelta, List<CDORevisionDelta> remoteDeltas)
{
Set<CDOFeature> features = new HashSet<CDOFeature>();
for (CDOFeatureDelta localFeatureDelta : localDelta.getFeatureDeltas())
{
features.add(localFeatureDelta.getFeature());
}
for (CDORevisionDelta remoteDelta : remoteDeltas)
{
for (CDOFeatureDelta remoteFeatureDelta : remoteDelta.getFeatureDeltas())
{
CDOFeature feature = remoteFeatureDelta.getFeature();
if (features.contains(feature))
{
return true;
}
}
}
return false;
}
}
}