| /* |
| * Copyright (c) 2010-2014 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; |
| } |
| } |
| } |