blob: ddd68c4447f072c77e22061073c27311dc32e6c1 [file] [log] [blame]
/*
* 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:
* Simon McDuff - initial API and implementation
* Eike Stepper - maintenance
* Martin Fluegge - maintenance, bug 318518
* Christian W. Damus (CEA LIST) - bug 399487
*/
package org.eclipse.emf.cdo.internal.server;
import org.eclipse.emf.cdo.common.CDOCommonRepository.IDGenerationLocation;
import org.eclipse.emf.cdo.common.branch.CDOBranch;
import org.eclipse.emf.cdo.common.branch.CDOBranchPoint;
import org.eclipse.emf.cdo.common.branch.CDOBranchVersion;
import org.eclipse.emf.cdo.common.commit.CDOCommitData;
import org.eclipse.emf.cdo.common.commit.CDOCommitInfo;
import org.eclipse.emf.cdo.common.id.CDOID;
import org.eclipse.emf.cdo.common.id.CDOIDObject;
import org.eclipse.emf.cdo.common.id.CDOIDReference;
import org.eclipse.emf.cdo.common.id.CDOIDUtil;
import org.eclipse.emf.cdo.common.lock.CDOLockChangeInfo;
import org.eclipse.emf.cdo.common.lock.CDOLockChangeInfo.Operation;
import org.eclipse.emf.cdo.common.lock.CDOLockOwner;
import org.eclipse.emf.cdo.common.lock.CDOLockState;
import org.eclipse.emf.cdo.common.lock.CDOLockUtil;
import org.eclipse.emf.cdo.common.model.CDOPackageUnit;
import org.eclipse.emf.cdo.common.protocol.CDOProtocol.CommitNotificationInfo;
import org.eclipse.emf.cdo.common.protocol.CDOProtocolConstants;
import org.eclipse.emf.cdo.common.revision.CDOIDAndBranch;
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.delta.CDOAddFeatureDelta;
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.CDOFeatureDeltaVisitor;
import org.eclipse.emf.cdo.common.revision.delta.CDORevisionDelta;
import org.eclipse.emf.cdo.common.revision.delta.CDOSetFeatureDelta;
import org.eclipse.emf.cdo.common.security.NoPermissionException;
import org.eclipse.emf.cdo.common.util.CDOCommonUtil;
import org.eclipse.emf.cdo.common.util.CDOQueryInfo;
import org.eclipse.emf.cdo.internal.common.commit.FailureCommitInfo;
import org.eclipse.emf.cdo.internal.common.model.CDOPackageRegistryImpl;
import org.eclipse.emf.cdo.internal.server.bundle.OM;
import org.eclipse.emf.cdo.server.IRepository;
import org.eclipse.emf.cdo.server.IStoreAccessor;
import org.eclipse.emf.cdo.server.IStoreAccessor.QueryXRefsContext;
import org.eclipse.emf.cdo.server.IView;
import org.eclipse.emf.cdo.server.StoreThreadLocal;
import org.eclipse.emf.cdo.spi.common.commit.CDOCommitInfoUtil;
import org.eclipse.emf.cdo.spi.common.commit.InternalCDOCommitInfoManager;
import org.eclipse.emf.cdo.spi.common.model.InternalCDOPackageInfo;
import org.eclipse.emf.cdo.spi.common.model.InternalCDOPackageRegistry;
import org.eclipse.emf.cdo.spi.common.model.InternalCDOPackageUnit;
import org.eclipse.emf.cdo.spi.common.revision.CDOFeatureDeltaVisitorImpl;
import org.eclipse.emf.cdo.spi.common.revision.CDOIDMapper;
import org.eclipse.emf.cdo.spi.common.revision.CDOReferenceAdjuster;
import org.eclipse.emf.cdo.spi.common.revision.DetachedCDORevision;
import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevision;
import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevisionCache;
import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevisionDelta;
import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevisionManager;
import org.eclipse.emf.cdo.spi.common.revision.StubCDORevision;
import org.eclipse.emf.cdo.spi.server.InternalCommitContext;
import org.eclipse.emf.cdo.spi.server.InternalLockManager;
import org.eclipse.emf.cdo.spi.server.InternalRepository;
import org.eclipse.emf.cdo.spi.server.InternalTransaction;
import org.eclipse.net4j.util.CheckUtil;
import org.eclipse.net4j.util.StringUtil;
import org.eclipse.net4j.util.collection.IndexedList;
import org.eclipse.net4j.util.concurrent.IRWLockManager.LockType;
import org.eclipse.net4j.util.concurrent.RWOLockManager.LockState;
import org.eclipse.net4j.util.io.ExtendedDataInputStream;
import org.eclipse.net4j.util.lifecycle.LifecycleUtil;
import org.eclipse.net4j.util.om.monitor.Monitor;
import org.eclipse.net4j.util.om.monitor.OMMonitor;
import org.eclipse.net4j.util.om.trace.ContextTracer;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
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 Simon McDuff
* @since 2.0
*/
public class TransactionCommitContext implements InternalCommitContext
{
private static final ContextTracer TRACER = new ContextTracer(OM.DEBUG_TRANSACTION, TransactionCommitContext.class);
private static final InternalCDORevision DETACHED = new StubCDORevision(null);
private final InternalTransaction transaction;
private final CDOBranch branch;
private InternalRepository repository;
private InternalLockManager lockManager;
private InternalCDOPackageRegistry repositoryPackageRegistry;
private boolean packageRegistryLocked;
private TransactionPackageRegistry packageRegistry;
private IStoreAccessor accessor;
private long lastUpdateTime;
private long lastTreeRestructuringCommit;
private long timeStamp = CDORevision.UNSPECIFIED_DATE;
private long previousTimeStamp = CDORevision.UNSPECIFIED_DATE;
private String commitComment;
private boolean usingEcore;
private boolean usingEtypes;
private InternalCDOPackageUnit[] newPackageUnits = new InternalCDOPackageUnit[0];
private CDOLockState[] locksOnNewObjects = new CDOLockState[0];
private InternalCDORevision[] newObjects = new InternalCDORevision[0];
private InternalCDORevisionDelta[] dirtyObjectDeltas = new InternalCDORevisionDelta[0];
private CDOID[] detachedObjects = new CDOID[0];
private Map<CDOID, EClass> detachedObjectTypes;
private CDOBranchVersion[] detachedObjectVersions;
private InternalCDORevision[] dirtyObjects = new InternalCDORevision[0];
private InternalCDORevision[] cachedDetachedRevisions = new InternalCDORevision[0];
private Map<CDOID, InternalCDORevision> cachedRevisions;
private Set<Object> lockedObjects = new HashSet<Object>();
private List<CDOID> lockedTargets;
private Map<CDOID, CDOID> idMappings = CDOIDUtil.createMap();
private CDOReferenceAdjuster idMapper = new CDOIDMapper(idMappings);
private byte rollbackReason = CDOProtocolConstants.ROLLBACK_REASON_UNKNOWN;
private String rollbackMessage;
private List<CDOIDReference> xRefs;
private List<LockState<Object, IView>> postCommitLockStates;
private boolean hasChanges;
private boolean serializingCommits;
private boolean ensuringReferentialIntegrity;
private boolean autoReleaseLocksEnabled;
private ExtendedDataInputStream lobs;
private Map<Object, Object> data;
private CommitNotificationInfo commitNotificationInfo = new CommitNotificationInfo();
public TransactionCommitContext(InternalTransaction transaction)
{
this.transaction = transaction;
branch = transaction.getBranch();
repository = transaction.getRepository();
lockManager = repository.getLockingManager();
serializingCommits = repository.isSerializingCommits();
ensuringReferentialIntegrity = repository.isEnsuringReferentialIntegrity();
repositoryPackageRegistry = repository.getPackageRegistry(false);
}
public InternalTransaction getTransaction()
{
return transaction;
}
public CDOBranchPoint getBranchPoint()
{
return branch.getPoint(timeStamp);
}
public String getUserID()
{
return transaction.getSession().getUserID();
}
public String getCommitComment()
{
return commitComment;
}
public long getLastUpdateTime()
{
return lastUpdateTime;
}
public boolean isAutoReleaseLocksEnabled()
{
return autoReleaseLocksEnabled;
}
public byte getRollbackReason()
{
return rollbackReason;
}
public String getRollbackMessage()
{
return rollbackMessage;
}
public List<CDOIDReference> getXRefs()
{
return xRefs;
}
public InternalCDOPackageRegistry getPackageRegistry()
{
if (packageRegistry == null)
{
packageRegistry = new TransactionPackageRegistry(repositoryPackageRegistry);
packageRegistry.activate();
}
return packageRegistry;
}
public boolean isClearResourcePathCache()
{
return commitNotificationInfo.isClearResourcePathCache();
}
public byte getSecurityImpact()
{
return commitNotificationInfo.getSecurityImpact();
}
public boolean isUsingEcore()
{
return usingEcore;
}
public boolean isUsingEtypes()
{
return usingEtypes;
}
public InternalCDOPackageUnit[] getNewPackageUnits()
{
return newPackageUnits;
}
public CDOLockState[] getLocksOnNewObjects()
{
return locksOnNewObjects;
}
public InternalCDORevision[] getNewObjects()
{
return newObjects;
}
public InternalCDORevision[] getDirtyObjects()
{
return dirtyObjects;
}
public CDOID[] getDetachedObjects()
{
return detachedObjects;
}
public Map<CDOID, EClass> getDetachedObjectTypes()
{
return detachedObjectTypes;
}
public CDOBranchVersion[] getDetachedObjectVersions()
{
return detachedObjectVersions;
}
public InternalCDORevision[] getDetachedRevisions()
{
// This array can contain null values as they only come from the cache!
for (InternalCDORevision cachedDetachedRevision : cachedDetachedRevisions)
{
if (cachedDetachedRevision == null)
{
throw new AssertionError("Detached revisions are incomplete");
}
}
return cachedDetachedRevisions;
}
public InternalCDORevisionDelta[] getDirtyObjectDeltas()
{
return dirtyObjectDeltas;
}
public InternalCDORevision getRevision(CDOID id)
{
if (cachedRevisions == null)
{
cachedRevisions = cacheRevisions();
}
// Try "after state"
InternalCDORevision revision = cachedRevisions.get(id);
if (revision == DETACHED)
{
return null;
}
if (revision != null)
{
return revision;
}
// Fall back to "before state"
return (InternalCDORevision)transaction.getRevision(id);
}
private Map<CDOID, InternalCDORevision> cacheRevisions()
{
Map<CDOID, InternalCDORevision> cache = CDOIDUtil.createMap();
if (newObjects != null)
{
for (int i = 0; i < newObjects.length; i++)
{
InternalCDORevision revision = newObjects[i];
cache.put(revision.getID(), revision);
}
}
if (dirtyObjects != null)
{
for (int i = 0; i < dirtyObjects.length; i++)
{
InternalCDORevision revision = dirtyObjects[i];
cache.put(revision.getID(), revision);
}
}
if (detachedObjects != null)
{
for (int i = 0; i < detachedObjects.length; i++)
{
cache.put(detachedObjects[i], DETACHED);
}
}
return cache;
}
public Map<CDOID, CDOID> getIDMappings()
{
return Collections.unmodifiableMap(idMappings);
}
public void addIDMapping(CDOID oldID, CDOID newID)
{
if (CDOIDUtil.isNull(newID) || newID.isTemporary())
{
throw new IllegalStateException("newID=" + newID); //$NON-NLS-1$
}
CDOID previousMapping = idMappings.put(oldID, newID);
if (previousMapping != null && previousMapping != newID)
{
throw new IllegalStateException("previousMapping != null && previousMapping != newID"); //$NON-NLS-1$
}
}
public void applyIDMappings(OMMonitor monitor)
{
boolean mapIDs = !idMappings.isEmpty();
monitor.begin(1 + (mapIDs ? newObjects.length + dirtyObjects.length + dirtyObjectDeltas.length : 0));
try
{
if (mapIDs)
{
applyIDMappings(newObjects, monitor.fork(newObjects.length));
applyIDMappings(dirtyObjects, monitor.fork(dirtyObjects.length));
for (CDORevisionDelta dirtyObjectDelta : dirtyObjectDeltas)
{
((InternalCDORevisionDelta)dirtyObjectDelta).adjustReferences(idMapper);
monitor.worked();
}
}
// Do not notify handlers before the IDs are fully mapped!
notifyBeforeCommitting(monitor);
}
finally
{
monitor.done();
}
}
private void applyIDMappings(InternalCDORevision[] revisions, OMMonitor monitor)
{
try
{
monitor.begin(revisions.length);
for (InternalCDORevision revision : revisions)
{
if (revision != null)
{
CDOID newID = idMappings.get(revision.getID());
if (newID != null)
{
revision.setID(newID);
}
revision.adjustReferences(idMapper);
monitor.worked();
}
}
}
finally
{
monitor.done();
}
}
protected void notifyBeforeCommitting(OMMonitor monitor)
{
repository.notifyWriteAccessHandlers(transaction, this, true, monitor.fork());
}
public void preWrite()
{
// Allocate a store writer
accessor = repository.getStore().getWriter(transaction);
// Make the store writer available in a ThreadLocal variable
StoreThreadLocal.setAccessor(accessor);
StoreThreadLocal.setCommitContext(this);
}
public void setLastTreeRestructuringCommit(long lastTreeRestructuringCommit)
{
this.lastTreeRestructuringCommit = lastTreeRestructuringCommit;
}
public void setClearResourcePathCache(boolean clearResourcePathCache)
{
commitNotificationInfo.setClearResourcePathCache(clearResourcePathCache);
}
public void setSecurityImpact(byte securityImpact, Set<? extends Object> impactedRules)
{
commitNotificationInfo.setSecurityImpact(securityImpact);
commitNotificationInfo.setImpactedRules(impactedRules);
}
public void setUsingEcore(boolean usingEcore)
{
this.usingEcore = usingEcore;
}
public void setUsingEtypes(boolean usingEtypes)
{
this.usingEtypes = usingEtypes;
}
public void setNewPackageUnits(InternalCDOPackageUnit[] newPackageUnits)
{
this.newPackageUnits = newPackageUnits;
}
public void setLocksOnNewObjects(CDOLockState[] locksOnNewObjects)
{
this.locksOnNewObjects = locksOnNewObjects;
}
public void setNewObjects(InternalCDORevision[] newObjects)
{
this.newObjects = newObjects;
}
public void setDirtyObjectDeltas(InternalCDORevisionDelta[] dirtyObjectDeltas)
{
this.dirtyObjectDeltas = dirtyObjectDeltas;
}
public void setDetachedObjects(CDOID[] detachedObjects)
{
this.detachedObjects = detachedObjects;
}
public void setDetachedObjectTypes(Map<CDOID, EClass> detachedObjectTypes)
{
this.detachedObjectTypes = detachedObjectTypes;
}
public void setDetachedObjectVersions(CDOBranchVersion[] detachedObjectVersions)
{
this.detachedObjectVersions = detachedObjectVersions;
}
public void setLastUpdateTime(long lastUpdateTime)
{
this.lastUpdateTime = lastUpdateTime;
}
public void setAutoReleaseLocksEnabled(boolean on)
{
autoReleaseLocksEnabled = on;
}
public void setCommitComment(String commitComment)
{
this.commitComment = commitComment;
}
public ExtendedDataInputStream getLobs()
{
return lobs;
}
public void setLobs(ExtendedDataInputStream in)
{
lobs = in;
}
public <T> T getData(Object key)
{
if (data == null)
{
return null;
}
@SuppressWarnings("unchecked")
T result = (T)data.get(key);
return result;
}
public synchronized <T extends Object> T setData(Object key, T value)
{
if (data == null)
{
data = new HashMap<Object, Object>();
}
@SuppressWarnings("unchecked")
T old = (T)data.put(key, value);
return old;
}
private InternalCDOPackageUnit[] lockPackageRegistry(InternalCDOPackageUnit[] packageUnits)
throws InterruptedException
{
if (!packageRegistryLocked)
{
repository.getPackageRegistryCommitLock().acquire();
packageRegistryLocked = true;
}
List<InternalCDOPackageUnit> noDuplicates = new ArrayList<InternalCDOPackageUnit>();
for (InternalCDOPackageUnit packageUnit : packageUnits)
{
String id = packageUnit.getID();
if (!repositoryPackageRegistry.containsKey(id))
{
noDuplicates.add(packageUnit);
}
}
int newSize = noDuplicates.size();
if (packageUnits.length != newSize)
{
return noDuplicates.toArray(new InternalCDOPackageUnit[newSize]);
}
return packageUnits;
}
/**
* @since 2.0
*/
public void write(OMMonitor monitor)
{
try
{
monitor.begin(107);
hasChanges = newPackageUnits.length != 0 || newObjects.length != 0 || dirtyObjectDeltas.length != 0;
if (!hasChanges)
{
return;
}
dirtyObjects = new InternalCDORevision[dirtyObjectDeltas.length];
if (newPackageUnits.length != 0)
{
newPackageUnits = lockPackageRegistry(newPackageUnits);
}
lockObjects(); // Can take long and must come before setTimeStamp()
monitor.worked();
setTimeStamp(monitor.fork());
adjustForCommit();
monitor.worked();
computeDirtyObjects(monitor.fork());
checkContainmentCycles();
checkXRefs();
monitor.worked();
detachObjects(monitor.fork());
accessor.write(this, monitor.fork(100));
}
catch (RollbackException ex)
{
rollbackReason = ex.getRollbackReason();
rollback(ex.getRollbackMessage());
}
catch (Throwable t)
{
handleException(t);
}
finally
{
finishMonitor(monitor);
}
}
public void commit(OMMonitor monitor)
{
try
{
monitor.begin(101);
if (hasChanges)
{
accessor.commit(monitor.fork(100));
}
else
{
monitor.worked(100);
}
updateInfraStructure(monitor.fork());
if (hasChanges)
{
// Bug 297940
repository.endCommit(timeStamp);
}
}
catch (Throwable ex)
{
handleException(ex);
}
finally
{
finishMonitor(monitor);
}
}
public List<LockState<Object, IView>> getPostCommmitLockStates()
{
return postCommitLockStates;
}
private void handleException(Throwable ex)
{
try
{
if (TRACER.isEnabled())
{
TRACER.trace(ex);
}
if (ex instanceof IRepository.WriteAccessHandler.TransactionValidationException)
{
rollbackReason = CDOProtocolConstants.ROLLBACK_REASON_VALIDATION_ERROR;
rollback(ex.getLocalizedMessage());
}
else
{
String storeClass = repository.getStore().getClass().getSimpleName();
rollback("Rollback in " + storeClass + ": " + StringUtil.formatException(ex)); //$NON-NLS-1$ //$NON-NLS-2$
}
}
catch (Exception ex1)
{
if (rollbackMessage == null)
{
rollbackMessage = ex1.getMessage();
}
try
{
if (TRACER.isEnabled())
{
TRACER.trace(ex1);
}
}
catch (Exception ignore)
{
}
}
}
private void finishMonitor(OMMonitor monitor)
{
try
{
monitor.done();
}
catch (Exception ex)
{
try
{
OM.LOG.warn(ex);
}
catch (Exception ignore)
{
}
}
}
private void setTimeStamp(OMMonitor monitor)
{
long[] times = createTimeStamp(monitor); // Could throw an exception
timeStamp = times[0];
previousTimeStamp = times[1];
CheckUtil.checkState(timeStamp != CDOBranchPoint.UNSPECIFIED_DATE, "Commit timestamp must not be 0");
}
protected long[] createTimeStamp(OMMonitor monitor)
{
return repository.createCommitTimeStamp(monitor);
}
public long getTimeStamp()
{
return timeStamp;
}
protected void setTimeStamp(long timeStamp)
{
repository.forceCommitTimeStamp(timeStamp, new Monitor());
this.timeStamp = timeStamp;
}
public long getPreviousTimeStamp()
{
return previousTimeStamp;
}
public void postCommit(boolean success)
{
if (packageRegistryLocked)
{
repository.getPackageRegistryCommitLock().release();
}
try
{
// Send notifications (in particular FailureCommitInfos) only if timeStamp had been allocated
if (timeStamp != CDOBranchPoint.UNSPECIFIED_DATE)
{
sendCommitNotifications(success);
}
}
catch (Exception ex)
{
OM.LOG.warn("A problem occured while notifying other sessions", ex);
}
finally
{
StoreThreadLocal.release();
accessor = null;
lockedTargets = null;
if (packageRegistry != null)
{
packageRegistry.deactivate();
packageRegistry = null;
}
}
}
private void sendCommitNotifications(boolean success)
{
commitNotificationInfo.setSender(transaction.getSession());
commitNotificationInfo.setRevisionProvider(this);
if (success)
{
commitNotificationInfo.setCommitInfo(createCommitInfo());
}
else
{
commitNotificationInfo.setCommitInfo(createFailureCommitInfo());
}
repository.sendCommitNotification(commitNotificationInfo);
}
public CDOCommitInfo createCommitInfo()
{
String userID = transaction.getSession().getUserID();
CDOCommitData commitData = createCommitData();
InternalCDOCommitInfoManager commitInfoManager = repository.getCommitInfoManager();
return commitInfoManager.createCommitInfo(branch, timeStamp, previousTimeStamp, userID, commitComment, commitData);
}
public CDOCommitInfo createFailureCommitInfo()
{
return new FailureCommitInfo(timeStamp, previousTimeStamp);
}
private CDOCommitData createCommitData()
{
List<CDOPackageUnit> newPackageUnitsCollection = new IndexedList.ArrayBacked<CDOPackageUnit>()
{
@Override
protected CDOPackageUnit[] getArray()
{
return newPackageUnits;
}
};
List<CDOIDAndVersion> newObjectsCollection = new IndexedList.ArrayBacked<CDOIDAndVersion>()
{
@Override
protected CDOIDAndVersion[] getArray()
{
return newObjects;
}
};
List<CDORevisionKey> changedObjectsCollection = new IndexedList.ArrayBacked<CDORevisionKey>()
{
@Override
protected CDORevisionKey[] getArray()
{
return dirtyObjectDeltas;
}
};
List<CDOIDAndVersion> detachedObjectsCollection = new IndexedList<CDOIDAndVersion>()
{
@Override
public CDOIDAndVersion get(int i)
{
if (cachedDetachedRevisions[i] != null)
{
return cachedDetachedRevisions[i];
}
return CDOIDUtil.createIDAndVersion(detachedObjects[i], CDORevision.UNSPECIFIED_VERSION);
}
@Override
public int size()
{
return detachedObjects.length;
}
};
return CDOCommitInfoUtil.createCommitData(newPackageUnitsCollection, newObjectsCollection, changedObjectsCollection,
detachedObjectsCollection);
}
protected void adjustForCommit()
{
for (InternalCDOPackageUnit newPackageUnit : newPackageUnits)
{
newPackageUnit.setTimeStamp(timeStamp);
}
for (InternalCDORevision newObject : newObjects)
{
newObject.adjustForCommit(branch, timeStamp);
}
}
protected void lockObjects() throws InterruptedException
{
lockedObjects.clear();
lockedTargets = null;
try
{
CDOFeatureDeltaVisitor deltaTargetLocker = null;
if (ensuringReferentialIntegrity && !serializingCommits)
{
final Set<CDOID> newIDs = new HashSet<CDOID>();
for (int i = 0; i < newObjects.length; i++)
{
InternalCDORevision newRevision = newObjects[i];
CDOID newID = newRevision.getID();
if (newID instanceof CDOIDObject)
{
// After merges newObjects may contain non-TEMP ids
newIDs.add(newID);
}
}
final boolean supportingBranches = repository.isSupportingBranches();
deltaTargetLocker = new CDOFeatureDeltaVisitorImpl()
{
@Override
public void visit(CDOAddFeatureDelta delta)
{
lockTarget(delta.getValue(), newIDs, supportingBranches);
}
@Override
public void visit(CDOSetFeatureDelta delta)
{
lockTarget(delta.getValue(), newIDs, supportingBranches);
}
};
CDOReferenceAdjuster revisionTargetLocker = new CDOReferenceAdjuster()
{
public Object adjustReference(Object value, EStructuralFeature feature, int index)
{
lockTarget(value, newIDs, supportingBranches);
return value;
}
};
for (int i = 0; i < newObjects.length; i++)
{
InternalCDORevision newRevision = newObjects[i];
newRevision.adjustReferences(revisionTargetLocker);
}
}
for (int i = 0; i < dirtyObjectDeltas.length; i++)
{
InternalCDORevisionDelta delta = dirtyObjectDeltas[i];
CDOID id = delta.getID();
Object key = lockManager.getLockKey(id, branch);
lockedObjects.add(key);
}
if (deltaTargetLocker != null)
{
for (int i = 0; i < dirtyObjectDeltas.length; i++)
{
InternalCDORevisionDelta delta = dirtyObjectDeltas[i];
delta.accept(deltaTargetLocker);
}
}
for (int i = 0; i < detachedObjects.length; i++)
{
CDOID id = detachedObjects[i];
Object key = lockManager.getLockKey(id, branch);
lockedObjects.add(key);
}
if (!lockedObjects.isEmpty())
{
try
{
// First lock all objects (incl. possible ref targets).
// This is a transient operation, it does not check for existance!
long timeout = repository.getOptimisticLockingTimeout();
lockManager.lock2(LockType.WRITE, transaction, lockedObjects, timeout);
}
catch (Exception ex)
{
throw new RollbackException(CDOProtocolConstants.ROLLBACK_REASON_OPTIMISTIC_LOCKING, ex);
}
// If all locks could be acquired, check if locked targets do still exist
if (lockedTargets != null)
{
for (CDOID id : lockedTargets)
{
CDORevision revision = transaction.getRevision(id);
if (revision == null || revision instanceof DetachedCDORevision)
{
throw new RollbackException(CDOProtocolConstants.ROLLBACK_REASON_REFERENTIAL_INTEGRITY,
"Attempt by " + transaction + " to introduce a stale reference");
}
}
}
}
}
catch (RuntimeException ex)
{
lockedObjects.clear();
lockedTargets = null;
throw ex;
}
}
private void lockTarget(Object value, Set<CDOID> newIDs, boolean supportingBranches)
{
if (value instanceof CDOIDObject)
{
CDOIDObject id = (CDOIDObject)value;
if (id.isNull())
{
return;
}
if (newIDs.contains(id))
{
// After merges newObjects may contain non-TEMP ids
return;
}
if (detachedObjectTypes != null && detachedObjectTypes.containsKey(id))
{
throw new IllegalStateException("This commit deletes object " + id + " and adds a reference at the same time");
}
// Let this object be locked
Object key = lockManager.getLockKey(id, branch);
lockedObjects.add(key);
// Let this object be checked for existance after it has been locked
if (lockedTargets == null)
{
lockedTargets = new ArrayList<CDOID>();
}
lockedTargets.add(id);
}
}
private synchronized void unlockObjects()
{
// Unlock objects locked during commit
if (!lockedObjects.isEmpty())
{
lockManager.unlock2(LockType.WRITE, transaction, lockedObjects);
lockedObjects.clear();
}
// Release durable locks that have been acquired on detached objects
if (detachedObjects.length > 0)
{
boolean branching = repository.isSupportingBranches();
Collection<? extends Object> unlockables;
if (branching)
{
List<CDOIDAndBranch> keys = new ArrayList<CDOIDAndBranch>(detachedObjects.length);
for (CDOID id : detachedObjects)
{
CDOIDAndBranch idAndBranch = CDOIDUtil.createIDAndBranch(id, branch);
keys.add(idAndBranch);
}
unlockables = keys;
}
else
{
unlockables = Arrays.asList(detachedObjects);
}
// We only need to consider detached objects that have been explicitly locked
Collection<Object> detachedObjectsToUnlock = new ArrayList<Object>();
for (Object unlockable : unlockables)
{
if (lockManager.hasLock(LockType.WRITE, transaction, unlockable))
{
detachedObjectsToUnlock.add(unlockable);
}
}
lockManager.unlock2(true, LockType.WRITE, transaction, detachedObjectsToUnlock, false);
}
}
private void computeDirtyObjects(OMMonitor monitor)
{
try
{
monitor.begin(dirtyObjectDeltas.length);
for (int i = 0; i < dirtyObjectDeltas.length; i++)
{
dirtyObjects[i] = computeDirtyObject(dirtyObjectDeltas[i]);
if (dirtyObjects[i] == null)
{
throw new IllegalStateException("Can not retrieve origin revision for " + dirtyObjectDeltas[i]); //$NON-NLS-1$
}
if (!dirtyObjects[i].isWritable())
{
throw new NoPermissionException(dirtyObjects[i]);
}
monitor.worked();
}
}
finally
{
monitor.done();
}
}
private InternalCDORevision computeDirtyObject(InternalCDORevisionDelta delta)
{
CDOID id = delta.getID();
InternalCDORevision oldRevision = null;
try
{
oldRevision = (InternalCDORevision)transaction.getRevision(id);
if (oldRevision != null)
{
if (oldRevision.getBranch() != delta.getBranch() || oldRevision.getVersion() != delta.getVersion())
{
oldRevision = null;
}
}
}
catch (Exception ex)
{
OM.LOG.error(ex);
oldRevision = null;
}
if (oldRevision == null)
{
// If the object is logically locked (see lockObjects) but has a wrong (newer) version, someone else modified it
throw new RollbackException(CDOProtocolConstants.ROLLBACK_REASON_COMMIT_CONFLICT,
"Attempt by " + transaction + " to modify historical revision: " + delta);
}
// Make sure all chunks are loaded
repository.ensureChunks(oldRevision, CDORevision.UNCHUNKED);
InternalCDORevision newRevision = oldRevision.copy();
newRevision.adjustForCommit(branch, timeStamp);
delta.applyTo(newRevision);
return newRevision;
}
protected void checkContainmentCycles()
{
if (lastTreeRestructuringCommit == 0)
{
// If this was a tree-restructuring commit then lastTreeRestructuringCommit would be initialized.
return;
}
if (lastUpdateTime == CDOBranchPoint.UNSPECIFIED_DATE)
{
// Happens during replication (see CommitDelegationRequest). Commits are checked in the master repo.
return;
}
if (lastTreeRestructuringCommit <= lastUpdateTime)
{
// If this client's original state includes the state of the last tree-restructuring commit there's no danger.
return;
}
Set<CDOID> objectsThatReachTheRoot = new HashSet<CDOID>();
for (int i = 0; i < dirtyObjectDeltas.length; i++)
{
InternalCDORevisionDelta revisionDelta = dirtyObjectDeltas[i];
CDOFeatureDelta containerDelta = revisionDelta.getFeatureDelta(CDOContainerFeatureDelta.CONTAINER_FEATURE);
if (containerDelta != null)
{
InternalCDORevision revision = dirtyObjects[i];
if (!isTheRootReachable(revision, objectsThatReachTheRoot, new HashSet<CDOID>()))
{
throw new RollbackException(CDOProtocolConstants.ROLLBACK_REASON_CONTAINMENT_CYCLE,
"Attempt by " + transaction + " to introduce a containment cycle");
}
}
}
}
private boolean isTheRootReachable(InternalCDORevision revision, Set<CDOID> objectsThatReachTheRoot,
Set<CDOID> visited)
{
CDOID id = revision.getID();
if (!visited.add(id))
{
// Cycle detected on the way up to the root.
return false;
}
if (!objectsThatReachTheRoot.add(id))
{
// Has already been checked before.
return true;
}
CDOID containerID = (CDOID)revision.getContainerID();
if (CDOIDUtil.isNull(containerID))
{
// The tree root has been reached.
return true;
}
// Use this commit context as CDORevisionProvider for the container revisions.
// This is safe because Repository.commit() serializes all tree-restructuring commits.
InternalCDORevision containerRevision = getRevision(containerID);
// Recurse Up
return isTheRootReachable(containerRevision, objectsThatReachTheRoot, visited);
}
protected void checkXRefs()
{
if (ensuringReferentialIntegrity && detachedObjectTypes != null)
{
XRefContext context = new XRefContext();
xRefs = context.getXRefs(accessor);
if (!xRefs.isEmpty())
{
throw new RollbackException(CDOProtocolConstants.ROLLBACK_REASON_REFERENTIAL_INTEGRITY,
"Attempt by " + transaction + " to introduce a stale reference");
}
}
}
public synchronized void rollback(String message)
{
// Check if we already rolled back
if (rollbackMessage == null)
{
rollbackMessage = message;
if (accessor != null)
{
try
{
accessor.rollback();
}
catch (RuntimeException ex)
{
OM.LOG.warn("Problem while rolling back the transaction", ex); //$NON-NLS-1$
}
finally
{
repository.failCommit(timeStamp);
}
}
unlockObjects();
}
}
protected IStoreAccessor getAccessor()
{
return accessor;
}
private void updateInfraStructure(OMMonitor monitor)
{
try
{
monitor.begin(8);
addNewPackageUnits(monitor.fork());
addRevisions(newObjects, monitor.fork());
addRevisions(dirtyObjects, monitor.fork());
reviseDetachedObjects(monitor.fork());
unlockObjects();
monitor.worked();
applyLocksOnNewObjects();
monitor.worked();
if (isAutoReleaseLocksEnabled())
{
postCommitLockStates = repository.getLockingManager().unlock2(true, transaction);
if (!postCommitLockStates.isEmpty())
{
// TODO (CD) Does doing this here make sense?
// The commit notifications get sent later, from postCommit.
sendLockNotifications(postCommitLockStates);
}
}
monitor.worked();
repository.notifyWriteAccessHandlers(transaction, this, false, monitor.fork());
}
catch (Throwable t)
{
handleException(t);
}
finally
{
monitor.done();
}
}
private void applyLocksOnNewObjects() throws InterruptedException
{
final CDOLockOwner owner = CDOLockUtil.createLockOwner(transaction);
for (CDOLockState lockState : locksOnNewObjects)
{
Object target = lockState.getLockedObject();
if (transaction.getRepository().getIDGenerationLocation() == IDGenerationLocation.STORE)
{
CDOIDAndBranch idAndBranch = target instanceof CDOIDAndBranch ? (CDOIDAndBranch)target : null;
CDOID id = idAndBranch != null ? ((CDOIDAndBranch)target).getID() : (CDOID)target;
CDOID newID = idMappings.get(id);
CheckUtil.checkNull(newID, "newID");
target = idAndBranch != null ? CDOIDUtil.createIDAndBranch(newID, idAndBranch.getBranch()) : newID;
}
for (LockType type : LockType.values())
{
if (lockState.isLocked(type, owner, false))
{
lockManager.lock2(type, transaction, Collections.singleton(target), 0);
}
}
}
}
private void sendLockNotifications(List<LockState<Object, IView>> newLockStates)
{
CDOLockState[] newStates = Repository.toCDOLockStates(newLockStates);
long timeStamp = getTimeStamp();
Operation unlock = Operation.UNLOCK;
CDOLockChangeInfo info = CDOLockUtil.createLockChangeInfo(timeStamp, transaction, branch, unlock, null, newStates);
repository.getSessionManager().sendLockNotification(transaction.getSession(), info);
}
private void addNewPackageUnits(OMMonitor monitor)
{
InternalCDOPackageRegistry repositoryPackageRegistry = repository.getPackageRegistry(false);
synchronized (repositoryPackageRegistry)
{
try
{
monitor.begin(newPackageUnits.length);
for (int i = 0; i < newPackageUnits.length; i++)
{
InternalCDOPackageUnit packageUnit = newPackageUnits[i];
packageUnit.setState(CDOPackageUnit.State.LOADED);
packageUnit.setPackageRegistry(repositoryPackageRegistry);
repositoryPackageRegistry.putPackageUnit(packageUnit);
monitor.worked();
}
}
finally
{
monitor.done();
}
}
}
private void addRevisions(CDORevision[] revisions, OMMonitor monitor)
{
try
{
monitor.begin(revisions.length);
InternalCDORevisionManager revisionManager = repository.getRevisionManager();
for (CDORevision revision : revisions)
{
if (revision != null)
{
revisionManager.addRevision(revision);
}
monitor.worked();
}
}
finally
{
monitor.done();
}
}
private void reviseDetachedObjects(OMMonitor monitor)
{
try
{
monitor.begin(cachedDetachedRevisions.length);
long revised = getBranchPoint().getTimeStamp() - 1;
for (InternalCDORevision revision : cachedDetachedRevisions)
{
if (revision != null)
{
revision.setRevised(revised);
}
monitor.worked();
}
}
finally
{
monitor.done();
}
}
private void detachObjects(OMMonitor monitor)
{
int size = detachedObjects.length;
cachedDetachedRevisions = new InternalCDORevision[size];
CDOID[] detachedObjects = getDetachedObjects();
try
{
monitor.begin(size);
InternalCDORevisionCache cache = repository.getRevisionManager().getCache();
for (int i = 0; i < size; i++)
{
CDOID id = detachedObjects[i];
// Remember the cached revision that must be revised after successful commit through updateInfraStructure
cachedDetachedRevisions[i] = (InternalCDORevision)cache.getRevision(id, transaction);
monitor.worked();
}
}
finally
{
monitor.done();
}
}
@Override
public String toString()
{
return MessageFormat.format("TransactionCommitContext[{0}, {1}, {2}]", transaction.getSession(), transaction, //$NON-NLS-1$
CDOCommonUtil.formatTimeStamp(timeStamp));
}
/**
* @author Eike Stepper
*/
public static final class TransactionPackageRegistry extends CDOPackageRegistryImpl
{
private static final long serialVersionUID = 1L;
public TransactionPackageRegistry(InternalCDOPackageRegistry repositoryPackageRegistry)
{
delegateRegistry = repositoryPackageRegistry;
setPackageLoader(repositoryPackageRegistry.getPackageLoader());
}
@Override
public synchronized void putPackageUnit(InternalCDOPackageUnit packageUnit)
{
LifecycleUtil.checkActive(this);
packageUnit.setPackageRegistry(this);
for (InternalCDOPackageInfo packageInfo : packageUnit.getPackageInfos())
{
EPackage ePackage = packageInfo.getEPackage();
basicPut(ePackage.getNsURI(), ePackage);
}
resetInternalCaches();
}
@Override
protected void disposePackageUnits()
{
// Do nothing
}
@Override
public Collection<Object> values()
{
throw new UnsupportedOperationException();
}
@Override
public String toString()
{
return "TransactionPackageRegistry";
}
}
/**
* @author Eike Stepper
*/
protected static final class RollbackException extends RuntimeException
{
private static final long serialVersionUID = 1L;
private final byte rollbackReason;
private final String rollbackMessage;
public RollbackException(byte rollbackReason, String rollbackMessage)
{
this.rollbackReason = rollbackReason;
this.rollbackMessage = rollbackMessage;
}
public RollbackException(byte rollbackReason, Throwable cause)
{
super(cause);
this.rollbackReason = rollbackReason;
rollbackMessage = cause.getMessage();
}
public byte getRollbackReason()
{
return rollbackReason;
}
public String getRollbackMessage()
{
return rollbackMessage;
}
}
/**
* @author Eike Stepper
*/
private final class XRefContext implements QueryXRefsContext
{
private Map<EClass, List<EReference>> sourceCandidates = new HashMap<EClass, List<EReference>>();
private Set<CDOID> detachedIDs = new HashSet<CDOID>();
private Set<CDOID> dirtyIDs = new HashSet<CDOID>();
private List<CDOIDReference> result = new ArrayList<CDOIDReference>();
public XRefContext()
{
XRefsQueryHandler.collectSourceCandidates(transaction, detachedObjectTypes.values(), sourceCandidates);
for (CDOID id : detachedObjects)
{
detachedIDs.add(id);
}
for (InternalCDORevision revision : dirtyObjects)
{
dirtyIDs.add(revision.getID());
}
}
public List<CDOIDReference> getXRefs(IStoreAccessor accessor)
{
accessor.queryXRefs(this);
checkDirtyObjects();
return result;
}
private void checkDirtyObjects()
{
final CDOID[] dirtyID = { null };
CDOReferenceAdjuster dirtyObjectChecker = new CDOReferenceAdjuster()
{
public Object adjustReference(Object targetID, EStructuralFeature feature, int index)
{
if (feature != CDOContainerFeatureDelta.CONTAINER_FEATURE)
{
if (detachedIDs.contains(targetID))
{
result.add(new CDOIDReference((CDOID)targetID, dirtyID[0], feature, index));
}
}
return targetID;
}
};
for (InternalCDORevision dirtyObject : dirtyObjects)
{
dirtyID[0] = dirtyObject.getID();
dirtyObject.adjustReferences(dirtyObjectChecker);
}
}
public long getTimeStamp()
{
return CDOBranchPoint.UNSPECIFIED_DATE;
}
public CDOBranch getBranch()
{
return branch;
}
public Map<CDOID, EClass> getTargetObjects()
{
return detachedObjectTypes;
}
public EReference[] getSourceReferences()
{
return new EReference[0];
}
public Map<EClass, List<EReference>> getSourceCandidates()
{
return sourceCandidates;
}
public int getMaxResults()
{
return CDOQueryInfo.UNLIMITED_RESULTS;
}
public boolean addXRef(CDOID targetID, CDOID sourceID, EReference sourceReference, int sourceIndex)
{
if (CDOIDUtil.isNull(targetID))
{
// Compensate potential issues with the XRef implementation in the store accessor.
return true;
}
if (detachedIDs.contains(sourceID))
{
// Ignore XRefs from objects that are about to be detached themselves by this commit.
return true;
}
if (dirtyIDs.contains(sourceID))
{
// Ignore XRefs from objects that are about to be modified by this commit. They're handled later in getXRefs().
return true;
}
result.add(new CDOIDReference(targetID, sourceID, sourceReference, sourceIndex));
return true;
}
}
}