/* * Copyright (c) 2009-2016 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 * Simon McDuff - maintenance * Victor Roldan Betancort - maintenance * Gonzague Reydet - bug 298334 * Andre Dietisheim - bug 256649 * Caspar De Groot - bug 290032 (Sticky views) */ package org.eclipse.emf.internal.cdo.transaction; import org.eclipse.emf.cdo.CDOObject; import org.eclipse.emf.cdo.CDOObjectReference; import org.eclipse.emf.cdo.CDOState; import org.eclipse.emf.cdo.common.CDOCommonRepository; import org.eclipse.emf.cdo.common.branch.CDOBranch; import org.eclipse.emf.cdo.common.branch.CDOBranchManager; import org.eclipse.emf.cdo.common.branch.CDOBranchPoint; import org.eclipse.emf.cdo.common.branch.CDOBranchVersion; import org.eclipse.emf.cdo.common.commit.CDOChangeSet; import org.eclipse.emf.cdo.common.commit.CDOChangeSetData; import org.eclipse.emf.cdo.common.commit.CDOCommitData; import org.eclipse.emf.cdo.common.commit.CDOCommitInfo; import org.eclipse.emf.cdo.common.commit.CDOCommitInfoManager; import org.eclipse.emf.cdo.common.id.CDOID; import org.eclipse.emf.cdo.common.id.CDOIDGenerator; import org.eclipse.emf.cdo.common.id.CDOIDProvider; import org.eclipse.emf.cdo.common.id.CDOIDReference; import org.eclipse.emf.cdo.common.id.CDOIDTemp; import org.eclipse.emf.cdo.common.id.CDOIDUtil; import org.eclipse.emf.cdo.common.id.CDOIdentifiable; import org.eclipse.emf.cdo.common.lob.CDOLob; import org.eclipse.emf.cdo.common.lob.CDOLobStore; import org.eclipse.emf.cdo.common.lock.CDOLockChangeInfo; 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.CDOModelUtil; import org.eclipse.emf.cdo.common.model.CDOPackageRegistry; import org.eclipse.emf.cdo.common.model.CDOPackageUnit; import org.eclipse.emf.cdo.common.model.EMFUtil; import org.eclipse.emf.cdo.common.protocol.CDODataInput; import org.eclipse.emf.cdo.common.protocol.CDODataOutput; import org.eclipse.emf.cdo.common.protocol.CDOProtocol; import org.eclipse.emf.cdo.common.revision.CDOIDAndVersion; import org.eclipse.emf.cdo.common.revision.CDOList; import org.eclipse.emf.cdo.common.revision.CDOListFactory; import org.eclipse.emf.cdo.common.revision.CDORevision; import org.eclipse.emf.cdo.common.revision.CDORevisionFactory; import org.eclipse.emf.cdo.common.revision.CDORevisionKey; import org.eclipse.emf.cdo.common.revision.CDORevisionManager; import org.eclipse.emf.cdo.common.revision.CDORevisionProvider; 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.CDOOriginSizeProvider; import org.eclipse.emf.cdo.common.revision.delta.CDORevisionDelta; import org.eclipse.emf.cdo.common.util.CDOException; import org.eclipse.emf.cdo.common.util.CDOResourceNodeNotFoundException; import org.eclipse.emf.cdo.eresource.CDOBinaryResource; import org.eclipse.emf.cdo.eresource.CDOFileResource; import org.eclipse.emf.cdo.eresource.CDOResource; import org.eclipse.emf.cdo.eresource.CDOResourceFolder; import org.eclipse.emf.cdo.eresource.CDOResourceNode; import org.eclipse.emf.cdo.eresource.CDOTextResource; import org.eclipse.emf.cdo.eresource.EresourceFactory; import org.eclipse.emf.cdo.eresource.EresourcePackage; import org.eclipse.emf.cdo.eresource.impl.CDOResourceImpl; import org.eclipse.emf.cdo.eresource.impl.CDOResourceNodeImpl; import org.eclipse.emf.cdo.internal.common.commit.FailureCommitInfo; import org.eclipse.emf.cdo.internal.common.revision.CDOListWithElementProxiesImpl; import org.eclipse.emf.cdo.session.CDORepositoryInfo; import org.eclipse.emf.cdo.session.CDOSession; import org.eclipse.emf.cdo.spi.common.branch.CDOBranchUtil; 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.lock.InternalCDOLockState; import org.eclipse.emf.cdo.spi.common.model.InternalCDOClassInfo; import org.eclipse.emf.cdo.spi.common.model.InternalCDOPackageUnit; import org.eclipse.emf.cdo.spi.common.protocol.CDODataInputImpl; import org.eclipse.emf.cdo.spi.common.protocol.CDODataOutputImpl; import org.eclipse.emf.cdo.spi.common.revision.CDOIDMapper; import org.eclipse.emf.cdo.spi.common.revision.CDORevisionUnchunker; import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevision; 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.ManagedRevisionProvider; import org.eclipse.emf.cdo.spi.common.revision.SyntheticCDORevision; import org.eclipse.emf.cdo.transaction.CDOCommitContext; import org.eclipse.emf.cdo.transaction.CDOConflictResolver; import org.eclipse.emf.cdo.transaction.CDOConflictResolver2; import org.eclipse.emf.cdo.transaction.CDOConflictResolver3; import org.eclipse.emf.cdo.transaction.CDODefaultTransactionHandler1; import org.eclipse.emf.cdo.transaction.CDOMerger; import org.eclipse.emf.cdo.transaction.CDOSavepoint; import org.eclipse.emf.cdo.transaction.CDOStaleReferenceCleaner; import org.eclipse.emf.cdo.transaction.CDOTransaction; import org.eclipse.emf.cdo.transaction.CDOTransaction.Options.AutoReleaseLocksEvent.AutoReleaseLocksEnabledEvent; import org.eclipse.emf.cdo.transaction.CDOTransaction.Options.AutoReleaseLocksEvent.AutoReleaseLocksExemptionsEvent; import org.eclipse.emf.cdo.transaction.CDOTransactionConflictEvent; import org.eclipse.emf.cdo.transaction.CDOTransactionFinishedEvent; import org.eclipse.emf.cdo.transaction.CDOTransactionHandler; import org.eclipse.emf.cdo.transaction.CDOTransactionHandler1; import org.eclipse.emf.cdo.transaction.CDOTransactionHandler2; import org.eclipse.emf.cdo.transaction.CDOTransactionHandler3; import org.eclipse.emf.cdo.transaction.CDOTransactionHandlerBase; import org.eclipse.emf.cdo.transaction.CDOTransactionStartedEvent; import org.eclipse.emf.cdo.transaction.CDOUndoDetector; import org.eclipse.emf.cdo.transaction.CDOUserSavepoint; import org.eclipse.emf.cdo.util.CDOURIUtil; import org.eclipse.emf.cdo.util.CDOUtil; import org.eclipse.emf.cdo.util.CommitException; import org.eclipse.emf.cdo.util.DanglingIntegrityException; import org.eclipse.emf.cdo.util.DanglingReferenceException; import org.eclipse.emf.cdo.util.LocalCommitConflictException; import org.eclipse.emf.cdo.util.ObjectNotFoundException; import org.eclipse.emf.cdo.view.CDOQuery; import org.eclipse.emf.internal.cdo.CDOObjectImpl; import org.eclipse.emf.internal.cdo.bundle.OM; import org.eclipse.emf.internal.cdo.messages.Messages; import org.eclipse.emf.internal.cdo.object.CDONotificationBuilder; import org.eclipse.emf.internal.cdo.object.CDOObjectMerger; import org.eclipse.emf.internal.cdo.object.CDOObjectReferenceImpl; import org.eclipse.emf.internal.cdo.object.CDOObjectWrapper; import org.eclipse.emf.internal.cdo.query.CDOQueryImpl; import org.eclipse.emf.internal.cdo.util.CommitIntegrityCheck; import org.eclipse.emf.internal.cdo.util.CompletePackageClosure; import org.eclipse.emf.internal.cdo.util.IPackageClosure; import org.eclipse.emf.internal.cdo.view.CDOStateMachine; import org.eclipse.emf.internal.cdo.view.CDOViewImpl; import org.eclipse.net4j.util.CheckUtil; import org.eclipse.net4j.util.ObjectUtil; import org.eclipse.net4j.util.WrappedException; import org.eclipse.net4j.util.collection.AbstractCloseableIterator; import org.eclipse.net4j.util.collection.ByteArrayWrapper; import org.eclipse.net4j.util.collection.CloseableIterator; import org.eclipse.net4j.util.collection.ComposedIterator; import org.eclipse.net4j.util.collection.ConcurrentArray; import org.eclipse.net4j.util.collection.DelegatingCloseableIterator; import org.eclipse.net4j.util.collection.Pair; import org.eclipse.net4j.util.concurrent.IRWLockManager; import org.eclipse.net4j.util.concurrent.TimeoutRuntimeException; import org.eclipse.net4j.util.event.IEvent; import org.eclipse.net4j.util.event.IListener; import org.eclipse.net4j.util.io.ExtendedDataInputStream; import org.eclipse.net4j.util.io.ExtendedDataOutputStream; import org.eclipse.net4j.util.om.monitor.MonitorCanceledException; import org.eclipse.net4j.util.om.trace.ContextTracer; import org.eclipse.net4j.util.options.OptionsEvent; import org.eclipse.net4j.util.transaction.TransactionException; import org.eclipse.emf.common.notify.NotificationChain; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.common.util.TreeIterator; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EPackage; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.ecore.InternalEObject; import org.eclipse.emf.ecore.impl.EClassImpl; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.util.EContentsEList; import org.eclipse.emf.ecore.util.ECrossReferenceEList; import org.eclipse.emf.spi.cdo.CDOSessionProtocol; import org.eclipse.emf.spi.cdo.CDOSessionProtocol.CommitTransactionResult; import org.eclipse.emf.spi.cdo.CDOTransactionStrategy; import org.eclipse.emf.spi.cdo.FSMUtil; import org.eclipse.emf.spi.cdo.InternalCDOObject; import org.eclipse.emf.spi.cdo.InternalCDOSavepoint; import org.eclipse.emf.spi.cdo.InternalCDOSession; import org.eclipse.emf.spi.cdo.InternalCDOSession.CommitToken; import org.eclipse.emf.spi.cdo.InternalCDOSession.InvalidationData; import org.eclipse.emf.spi.cdo.InternalCDOSession.MergeData; import org.eclipse.emf.spi.cdo.InternalCDOTransaction; import org.eclipse.emf.spi.cdo.InternalCDOViewSet; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.WeakHashMap; import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicInteger; /** * @author Eike Stepper */ public class CDOTransactionImpl extends CDOViewImpl implements InternalCDOTransaction { private static final ContextTracer TRACER = new ContextTracer(OM.DEBUG_TRANSACTION, CDOTransactionImpl.class); private Object transactionHandlersLock = new Object(); private ConcurrentArray<CDOTransactionHandler1> transactionHandlers1 = new ConcurrentArray<CDOTransactionHandler1>() { @Override protected CDOTransactionHandler1[] newArray(int length) { return new CDOTransactionHandler1[length]; } }; private ConcurrentArray<CDOTransactionHandler2> transactionHandlers2 = new ConcurrentArray<CDOTransactionHandler2>() { @Override protected CDOTransactionHandler2[] newArray(int length) { return new CDOTransactionHandler2[length]; } }; private InternalCDOSavepoint lastSavepoint = createSavepoint(null); private InternalCDOSavepoint firstSavepoint = lastSavepoint; private boolean dirty; private int conflict; private CDOTransactionStrategy transactionStrategy; private CDOIDGenerator idGenerator; private volatile long lastCommitTime = UNSPECIFIED_DATE; private String commitComment; private CommitToken commitToken; private CDOBranchPoint commitMergeSource; // Bug 283985 (Re-attachment) private final ThreadLocal<Boolean> providingCDOID = new InheritableThreadLocal<Boolean>() { @Override protected Boolean initialValue() { return false; } }; /** * An optional set to specify which objects in this TX are to be committed by {@link #commit()} */ private Set<? extends EObject> committables; /** * A map to hold a clean (i.e. unmodified) revision for objects that have been modified or detached. */ private Map<InternalCDOObject, InternalCDORevision> cleanRevisions = new CleanRevisionsMap(); public CDOTransactionImpl(CDOSession session, CDOBranch branch) { super(session, branch, UNSPECIFIED_DATE); } public CDOTransactionImpl(CDOSession session, String durableLockingID) { super(session, durableLockingID); } /** * @since 2.0 */ @Override public OptionsImpl options() { return (OptionsImpl)super.options(); } /** * @since 2.0 */ @Override protected OptionsImpl createOptions() { return new OptionsImpl(); } @Override public boolean isReadOnly() { return false; } @Override public boolean setBranchPoint(CDOBranchPoint branchPoint) { synchronized (getViewMonitor()) { lockView(); try { if (branchPoint.getTimeStamp() != UNSPECIFIED_DATE) { throw new IllegalArgumentException("Changing the target time is not supported by transactions"); } if (isDirty() && !getBranch().equals(branchPoint.getBranch())) { throw new IllegalStateException("Changing the target branch is impossible while transaction is dirty"); } return super.setBranchPoint(branchPoint); } finally { unlockView(); } } } public void addTransactionHandler(CDOTransactionHandlerBase handler) { synchronized (transactionHandlersLock) { if (handler instanceof CDOTransactionHandler1) { transactionHandlers1.add((CDOTransactionHandler1)handler); } if (handler instanceof CDOTransactionHandler2) { transactionHandlers2.add((CDOTransactionHandler2)handler); } } } public void removeTransactionHandler(CDOTransactionHandlerBase handler) { synchronized (transactionHandlersLock) { if (handler instanceof CDOTransactionHandler1) { transactionHandlers1.remove((CDOTransactionHandler1)handler); } if (handler instanceof CDOTransactionHandler2) { transactionHandlers2.remove((CDOTransactionHandler2)handler); } } } public CDOTransactionHandler[] getTransactionHandlers() { Set<CDOTransactionHandler> result = new HashSet<CDOTransactionHandler>(); synchronized (transactionHandlersLock) { CDOTransactionHandler1[] handlers1 = transactionHandlers1.get(); if (handlers1 != null) { for (CDOTransactionHandler1 handler : handlers1) { if (handler instanceof CDOTransactionHandler) { result.add((CDOTransactionHandler)handler); } } } CDOTransactionHandler2[] handlers2 = transactionHandlers2.get(); if (handlers2 != null) { for (CDOTransactionHandler2 handler : handlers2) { if (handler instanceof CDOTransactionHandler) { result.add((CDOTransactionHandler)handler); } } } } return result.toArray(new CDOTransactionHandler[result.size()]); } public CDOTransactionHandler1[] getTransactionHandlers1() { synchronized (transactionHandlersLock) { return transactionHandlers1.get(); } } public CDOTransactionHandler2[] getTransactionHandlers2() { synchronized (transactionHandlersLock) { return transactionHandlers2.get(); } } @Override public boolean isDirty() { if (isClosed()) { return false; } return dirty; } public void setDirty(boolean dirty) { if (this.dirty != dirty) { this.dirty = dirty; IListener[] listeners = getListeners(); if (listeners != null) { IEvent event = dirty ? new StartedEvent() : new FinishedEvent(false); fireEvent(event, listeners); } } } @Override public boolean hasConflict() { checkActive(); return conflict > 0; } public void setConflict(InternalCDOObject object) { IEvent event = null; synchronized (getViewMonitor()) { lockView(); try { event = new ConflictEvent(object, conflict == 0); ++conflict; } finally { unlockView(); } } fireEvent(event); } public void removeConflict(InternalCDOObject object) { synchronized (getViewMonitor()) { lockView(); try { if (conflict > 0) { --conflict; } } finally { unlockView(); } } } /** * @since 2.0 */ public Set<CDOObject> getConflicts() { synchronized (getViewMonitor()) { lockView(); try { Set<CDOObject> conflicts = new HashSet<CDOObject>(); for (CDOObject object : getDirtyObjects().values()) { if (object.cdoConflict()) { conflicts.add(object); } } for (CDOObject object : getDetachedObjects().values()) { if (object.cdoConflict()) { conflicts.add(object); } } return conflicts; } finally { unlockView(); } } } public CDOChangeSetData getChangeSetData() { checkActive(); synchronized (getViewMonitor()) { lockView(); try { return lastSavepoint.getAllChangeSetData(); } finally { unlockView(); } } } public CDOChangeSetData merge(CDOBranch source, CDOMerger merger) { return merge(source.getHead(), merger); } public CDOChangeSetData merge(CDOBranchPoint source, CDOMerger merger) { return merge(source, CDOBranchUtil.AUTO_BRANCH_POINT, merger); } public CDOChangeSetData merge(CDOBranchPoint source, CDOBranchPoint sourceBase, CDOMerger merger) { return merge(source, sourceBase, null, merger); } public CDOChangeSetData merge(CDOBranchPoint source, CDOBranchPoint sourceBase, CDOBranchPoint targetBase, CDOMerger merger) { synchronized (getViewMonitor()) { lockView(); try { if (isDirty()) { throw new IllegalStateException("Merging into dirty transactions not yet supported"); } long now = getLastUpdateTime(); CDOBranchPoint target = getBranch().getPoint(now); if (source.getTimeStamp() == UNSPECIFIED_DATE) { source = source.getBranch().getPoint(now); } if (CDOBranchUtil.isContainedBy(source, target)) { throw new IllegalArgumentException("Source is already contained in " + target); } if (sourceBase != CDOBranchUtil.AUTO_BRANCH_POINT) { if (sourceBase != null && !CDOBranchUtil.isContainedBy(sourceBase, source)) { throw new IllegalArgumentException("Source base is not contained in " + source); } if (targetBase != null && !CDOBranchUtil.isContainedBy(targetBase, target)) { throw new IllegalArgumentException("Target base is not contained in " + target); } } InternalCDOSession session = getSession(); MergeData mergeData = session.getMergeData(target, source, targetBase, sourceBase, true); CDOChangeSet targetChanges = mergeData.getTargetChanges(); CDOChangeSet sourceChanges = mergeData.getSourceChanges(); CDOChangeSetData result = merger.merge(targetChanges, sourceChanges); if (result == null) { return null; } CDORevisionProvider targetProvider = mergeData.getTargetInfo(); CDORevisionProvider resultBaseProvider; CDORevisionManager revisionManager = session.getRevisionManager(); CDOBranchPoint resultBase = mergeData.getResultBase(); if (resultBase != null) { resultBaseProvider = new ManagedRevisionProvider(revisionManager, resultBase); } else { resultBaseProvider = mergeData.getTargetBaseInfo(); } ApplyChangeSetResult changeSet = applyChangeSet(result, resultBaseProvider, targetProvider, source, false); commitMergeSource = source; return changeSet.getChangeSetData(); } finally { unlockView(); } } } public CDOChangeSetData remerge(CDOBranchPoint source, CDOMerger merger) { return merge(source, CDOBranchUtil.AUTO_BRANCH_POINT, merger); } @Deprecated public Pair<CDOChangeSetData, Pair<Map<CDOID, CDOID>, List<CDOID>>> applyChangeSetData(CDOChangeSetData changeSetData, CDORevisionProvider targetBaseProvider, CDORevisionProvider targetProvider, CDOBranchPoint source) { throw new UnsupportedOperationException(); } public ApplyChangeSetResult applyChangeSet(CDOChangeSetData changeSetData, CDORevisionProvider resultBaseProvider, CDORevisionProvider targetProvider, CDOBranchPoint source, boolean keepVersions) throws ChangeSetOutdatedException { synchronized (getViewMonitor()) { lockView(); try { ApplyChangeSetResult result = new ApplyChangeSetResult(); // Merges from local offline branches may require additional ID mappings: localID -> tempID if (source != null && source.getBranch().isLocal() && getSession().getRepositoryInfo().getIDGenerationLocation() == CDOCommonRepository.IDGenerationLocation.STORE) { applyLocalIDMapping(changeSetData, result); } CDOChangeSetData resultData = result.getChangeSetData(); // New objects applyNewObjects(changeSetData.getNewObjects(), resultData.getNewObjects()); // Detached objects Set<CDOObject> detachedSet = applyDetachedObjects(changeSetData.getDetachedObjects(), resultData.getDetachedObjects()); // Changed objects Map<CDOID, InternalCDORevision> oldRevisions = applyChangedObjects(changeSetData.getChangedObjects(), resultBaseProvider, targetProvider, keepVersions, resultData.getChangedObjects()); // Delta notifications Collection<CDORevisionDelta> notificationDeltas = lastSavepoint.getRevisionDeltas2().values(); if (!notificationDeltas.isEmpty() || !detachedSet.isEmpty()) { sendDeltaNotifications(notificationDeltas, detachedSet, oldRevisions); } return result; } finally { unlockView(); } } } private void applyLocalIDMapping(CDOChangeSetData changeSetData, ApplyChangeSetResult result) { Map<CDOID, CDOID> idMappings = result.getIDMappings(); // Collect needed ID mappings for (CDOIDAndVersion key : changeSetData.getNewObjects()) { InternalCDORevision revision = (InternalCDORevision)key; if (revision.getBranch().isLocal()) { CDOID oldID = revision.getID(); CDOID newID = createIDForNewObject(null); idMappings.put(oldID, newID); revision.setID(newID); revision.setVersion(0); } } if (!idMappings.isEmpty()) { // Apply collected ID mappings CDOIDMapper idMapper = new CDOIDMapper(idMappings); idMapper.setAllowUnmappedTempIDs(true); for (CDOIDAndVersion key : changeSetData.getNewObjects()) { InternalCDORevision revision = (InternalCDORevision)key; revision.adjustReferences(idMapper); } for (CDORevisionKey key : changeSetData.getChangedObjects()) { InternalCDORevisionDelta revisionDelta = (InternalCDORevisionDelta)key; if (revisionDelta.adjustReferences(idMapper)) { result.getAdjustedObjects().add(revisionDelta.getID()); } } } } private void applyNewObjects(List<CDOIDAndVersion> newObjects, List<CDOIDAndVersion> result) { List<InternalCDOObject> objects = new ArrayList<InternalCDOObject>(); for (CDOIDAndVersion key : newObjects) { InternalCDORevision revision = (InternalCDORevision)key; CDOID id = revision.getID(); if (getObjectIfExists(id) == null) { revision = revision.copy(); revision.setBranchPoint(getBranchPoint()); revision.setVersion(0); InternalCDOObject object = newInstance(revision.getEClass()); object.cdoInternalSetView(this); object.cdoInternalSetRevision(revision); object.cdoInternalSetState(CDOState.NEW); objects.add(object); registerObject(object); result.add(revision); } } if (!objects.isEmpty()) { for (InternalCDOObject object : objects) { object.cdoInternalPostLoad(); registerAttached(object, true); } setDirty(true); } } private Set<CDOObject> applyDetachedObjects(List<CDOIDAndVersion> detachedObjects, List<CDOIDAndVersion> result) { Set<CDOObject> detachedSet = new HashSet<CDOObject>(); for (CDOIDAndVersion key : detachedObjects) { CDOID id = key.getID(); InternalCDOObject object = getObjectIfExists(id); if (object != null) { result.add(CDOIDUtil.createIDAndVersion(id, CDOBranchVersion.UNSPECIFIED_VERSION)); CDOStateMachine.INSTANCE.detach(object); detachedSet.add(object); setDirty(true); } } return detachedSet; } private Map<CDOID, InternalCDORevision> applyChangedObjects(List<CDORevisionKey> changedObjects, CDORevisionProvider resultBaseProvider, CDORevisionProvider targetProvider, boolean keepVersions, List<CDORevisionKey> result) throws ChangeSetOutdatedException { Map<CDOID, InternalCDORevision> oldRevisions = CDOIDUtil.createMap(); Map<CDOID, CDOObject> detachedObjects = lastSavepoint.getDetachedObjects(); Map<CDOID, CDOObject> dirtyObjects = lastSavepoint.getDirtyObjects(); Map<CDOID, CDORevisionDelta> revisionDeltas = lastSavepoint.getRevisionDeltas2(); for (CDORevisionKey key : changedObjects) { InternalCDORevisionDelta resultBaseGoalDelta = (InternalCDORevisionDelta)key; resultBaseGoalDelta.setTarget(null); CDOID id = resultBaseGoalDelta.getID(); InternalCDORevision resultBaseRevision = (InternalCDORevision)resultBaseProvider.getRevision(id); InternalCDOObject object = getObject(id); boolean revisionChanged = false; InternalCDORevision targetRevision = object.cdoRevision(); if (targetRevision == null) { targetRevision = (InternalCDORevision)targetProvider.getRevision(id); object.cdoInternalSetRevision(targetRevision); revisionChanged = true; } oldRevisions.put(id, targetRevision); InternalCDORevision goalRevision = resultBaseRevision.copy(); goalRevision.setBranchPoint(this); if (!keepVersions) { goalRevision.setVersion(targetRevision.getVersion()); } goalRevision.setRevised(UNSPECIFIED_DATE); resultBaseGoalDelta.applyTo(goalRevision); InternalCDORevisionDelta targetGoalDelta = goalRevision.compare(targetRevision); targetGoalDelta.setTarget(null); if (!targetGoalDelta.isEmpty()) { if (keepVersions && targetGoalDelta.getVersion() != resultBaseRevision.getVersion()) { throw new ChangeSetOutdatedException(); } revisionDeltas.put(id, targetGoalDelta); result.add(targetGoalDelta); // handle reattached objects. if (detachedObjects.containsKey(id)) { CDOStateMachine.INSTANCE.internalReattach(object, this); } object.cdoInternalSetRevision(goalRevision); object.cdoInternalSetState(CDOState.DIRTY); revisionChanged = true; dirtyObjects.put(id, object); setDirty(true); } if (revisionChanged) { object.cdoInternalPostLoad(); } } return oldRevisions; } private InternalCDOObject getObjectIfExists(CDOID id) { try { return getObject(id); } catch (ObjectNotFoundException ex) { return null; } } /* * Synchronized through InvalidationRunnable.run() */ @Override protected void handleConflicts(long lastUpdateTime, Map<CDOObject, Pair<CDORevision, CDORevisionDelta>> conflicts, List<CDORevisionDelta> deltas) { synchronized (getViewMonitor()) { lockView(); try { CDOConflictResolver[] resolvers = options().getConflictResolvers(); if (resolvers.length == 0) { return; } if (conflicts == null) { for (CDOConflictResolver resolver : resolvers) { if (resolver instanceof CDOConflictResolver.NonConflictAware) { ((CDOConflictResolver.NonConflictAware)resolver).handleNonConflict(lastUpdateTime); } } return; } // Remember original state to be able to restore it after an exception List<CDOState> states = new ArrayList<CDOState>(conflicts.size()); List<CDORevision> revisions = new ArrayList<CDORevision>(conflicts.size()); for (CDOObject conflict : conflicts.keySet()) { states.add(conflict.cdoState()); revisions.add(conflict.cdoRevision()); } int resolved = 0; try { Map<CDOObject, Pair<CDORevision, CDORevisionDelta>> remaining = new HashMap<CDOObject, Pair<CDORevision, CDORevisionDelta>>(conflicts); for (CDOConflictResolver resolver : resolvers) { if (resolver instanceof CDOConflictResolver2) { ((CDOConflictResolver2)resolver).resolveConflicts(Collections.unmodifiableMap(remaining), deltas); } else { resolver.resolveConflicts(Collections.unmodifiableSet(remaining.keySet())); } for (Iterator<CDOObject> it = remaining.keySet().iterator(); it.hasNext();) { CDOObject object = it.next(); if (!object.cdoConflict()) { ++resolved; it.remove(); } } } } catch (Exception ex) { // Restore original state Iterator<CDOState> state = states.iterator(); Iterator<CDORevision> revision = revisions.iterator(); for (CDOObject object : conflicts.keySet()) { ((InternalCDOObject)object).cdoInternalSetRevision(revision.next()); ((InternalCDOObject)object).cdoInternalSetState(state.next()); } throw WrappedException.wrap(ex); } conflict -= resolved; Map<CDOID, CDOObject> dirtyObjects = getDirtyObjects(); setDirty(!dirtyObjects.isEmpty()); } finally { unlockView(); } } } /** * @deprecated {@link #createIDForNewObject(EObject object)} is called since 4.1. */ @Deprecated public CDOIDTemp getNextTemporaryID() { throw new UnsupportedOperationException(); } public CDOID createIDForNewObject(EObject object) { return idGenerator.generateCDOID(object); } public CDOResourceFolder createResourceFolder(String path) { synchronized (getViewMonitor()) { lockView(); try { if (path.endsWith(CDOURIUtil.SEGMENT_SEPARATOR)) { path = path.substring(0, path.length() - 1); } CDOResourceFolder folder = EresourceFactory.eINSTANCE.createCDOResourceFolder(); int pos = path.lastIndexOf(CDOURIUtil.SEGMENT_SEPARATOR_CHAR); if (pos <= 0) { String name = path.substring(pos == 0 ? 1 : 0); folder.setName(name); getRootResource().getContents().add(folder); } else { String name = path.substring(pos + 1); folder.setName(name); path = path.substring(0, pos); CDOResourceNode parent = null; try { parent = getResourceNode(path); } catch (Exception ex) { parent = createResourceFolder(path); } if (parent instanceof CDOResourceFolder) { ((CDOResourceFolder)parent).getNodes().add(folder); } else { throw new CDOException("Parent is not a folder: " + parent); } } return folder; } finally { unlockView(); } } } public CDOResource createResource(String path) { checkActive(); synchronized (getViewMonitor()) { lockView(); try { URI uri = CDOURIUtil.createResourceURI(this, path); return (CDOResource)getResourceSet().createResource(uri); } finally { unlockView(); } } } public CDOResource getOrCreateResource(String path) { checkActive(); synchronized (getViewMonitor()) { lockView(); try { try { CDOID id = getResourceNodeID(path); if (!CDOIDUtil.isNull(id)) { return (CDOResource)getObject(id); } } catch (CDOResourceNodeNotFoundException ignore) { // Just create the missing resource. } return createResource(path); } finally { unlockView(); } } } public CDOTextResource createTextResource(String path) { synchronized (getViewMonitor()) { lockView(); try { CDOTextResource resource = EresourceFactory.eINSTANCE.createCDOTextResource(); createFileResource(path, resource); return resource; } finally { unlockView(); } } } public CDOTextResource getOrCreateTextResource(String path) { synchronized (getViewMonitor()) { lockView(); try { try { CDOID id = getResourceNodeID(path); if (!CDOIDUtil.isNull(id)) { return (CDOTextResource)getObject(id); } } catch (CDOResourceNodeNotFoundException ignore) { // Just create the missing resource. } return createTextResource(path); } finally { unlockView(); } } } public CDOBinaryResource createBinaryResource(String path) { synchronized (getViewMonitor()) { lockView(); try { CDOBinaryResource resource = EresourceFactory.eINSTANCE.createCDOBinaryResource(); createFileResource(path, resource); return resource; } finally { unlockView(); } } } public CDOBinaryResource getOrCreateBinaryResource(String path) { synchronized (getViewMonitor()) { lockView(); try { try { CDOID id = getResourceNodeID(path); if (!CDOIDUtil.isNull(id)) { return (CDOBinaryResource)getObject(id); } } catch (CDOResourceNodeNotFoundException ignore) { // Just create the missing resource } return createBinaryResource(path); } finally { unlockView(); } } } private void createFileResource(String path, CDOFileResource<?> resource) { int lastSlash = path.lastIndexOf('/'); if (lastSlash == -1) { resource.setName(path); getRootResource().getContents().add(resource); } else { resource.setName(path.substring(lastSlash + 1)); CDOResourceFolder folder = getOrCreateResourceFolder(path.substring(0, lastSlash)); folder.getNodes().add(resource); } } /** * @since 2.0 */ @Override public void attachResource(CDOResourceImpl resource) { synchronized (getViewMonitor()) { lockView(); try { if (resource.isExisting()) { super.attachResource(resource); } else { // ResourceSet.createResource(uri) was called!! attachNewResource(resource); } } finally { unlockView(); } } } private void attachNewResource(CDOResourceImpl resource) { URI uri = resource.getURI(); List<String> names = CDOURIUtil.analyzePath(uri); String resourceName = names.isEmpty() ? null : names.remove(names.size() - 1); CDOResourceFolder folder = getOrCreateResourceFolder(names); attachNewResourceNode(folder, resourceName, resource); } public CDOResourceFolder getOrCreateResourceFolder(String path) { checkActive(); synchronized (getViewMonitor()) { lockView(); try { try { CDOID id = getResourceNodeID(path); if (!CDOIDUtil.isNull(id)) { return (CDOResourceFolder)getObject(id); } } catch (CDOResourceNodeNotFoundException ignore) { // Just create the missing resource. } return createResourceFolder(path); } finally { unlockView(); } } } /** * @return never <code>null</code>; * @since 2.0 */ public CDOResourceFolder getOrCreateResourceFolder(List<String> names) { synchronized (getViewMonitor()) { lockView(); try { CDOResourceFolder folder = null; for (String name : names) { CDOResourceNode node; try { CDOID folderID = folder == null ? null : folder.cdoID(); node = getResourceNode(folderID, name); } catch (CDOResourceNodeNotFoundException ex) { node = EresourceFactory.eINSTANCE.createCDOResourceFolder(); attachNewResourceNode(folder, name, node); } if (node instanceof CDOResourceFolder) { folder = (CDOResourceFolder)node; } else { throw new CDOException(MessageFormat.format(Messages.getString("CDOTransactionImpl.0"), node)); //$NON-NLS-1$ } } return folder; } finally { unlockView(); } } } private void attachNewResourceNode(CDOResourceFolder folder, String name, CDOResourceNode newNode) { CDOResourceNodeImpl node = (CDOResourceNodeImpl)newNode; node.basicSetName(name, false); if (folder == null) { if (node.isRoot()) { CDOStateMachine.INSTANCE.attach(node, this); } else { getRootResource().getContents().add(node); } } else { node.basicSetFolder(folder, false); } } /** * @since 2.0 */ public void detach(CDOResourceImpl cdoResource) { synchronized (getViewMonitor()) { lockView(); try { CDOStateMachine.INSTANCE.detach(cdoResource); } finally { unlockView(); } } } /** * @since 4.1 */ public InternalCDOSavepoint getFirstSavepoint() { return firstSavepoint; } /** * @since 2.0 */ public InternalCDOSavepoint getLastSavepoint() { checkActive(); return lastSavepoint; } /** * @since 2.0 */ public CDOTransactionStrategy getTransactionStrategy() { synchronized (getViewMonitor()) { lockView(); try { if (transactionStrategy == null) { transactionStrategy = CDOTransactionStrategy.DEFAULT; transactionStrategy.setTarget(this); } return transactionStrategy; } finally { unlockView(); } } } /** * @since 2.0 */ public void setTransactionStrategy(CDOTransactionStrategy transactionStrategy) { synchronized (getViewMonitor()) { lockView(); try { if (this.transactionStrategy != null) { this.transactionStrategy.unsetTarget(this); } this.transactionStrategy = transactionStrategy; if (this.transactionStrategy != null) { this.transactionStrategy.setTarget(this); } } finally { unlockView(); } } } /** * @since 2.0 */ @Override protected CDOID getRootOrTopLevelResourceNodeID(String name) { synchronized (getViewMonitor()) { lockView(); try { if (dirty) { CDOResourceNode node = getRootResourceNode(name, getDirtyObjects().values()); if (node != null) { return node.cdoID(); } node = getRootResourceNode(name, getNewObjects().values()); if (node != null) { return node.cdoID(); } } CDOID id = super.getRootOrTopLevelResourceNodeID(name); if (isObjectDetached(id) || isObjectDirty(id)) { throw new CDOException(MessageFormat.format(Messages.getString("CDOTransactionImpl.1"), name)); //$NON-NLS-1$ } return id; } finally { unlockView(); } } } private CDOResourceNode getRootResourceNode(String name, Collection<? extends CDOObject> objects) { for (CDOObject object : objects) { if (object instanceof CDOResourceNode) { CDOResourceNode node = (CDOResourceNode)object; if (node.getFolder() == null && ObjectUtil.equals(name, node.getName())) { return node; } } } return null; } @Override protected InternalCDOObject getObjectUnsynced(CDOID id, boolean loadOnDemand) { if (isObjectNew(id) && isObjectDetached(id)) { throw new ObjectNotFoundException(id, this); } return super.getObjectUnsynced(id, loadOnDemand); } @Override public boolean isObjectNew(CDOID id) { return lastSavepoint.isNewObject(id); } private boolean isObjectDirty(CDOID id) { return lastSavepoint.getDirtyObject(id) != null; } private boolean isObjectDetached(CDOID id) { return lastSavepoint.getDetachedObject(id) != null; } /** * @since 2.0 */ public InternalCDOCommitContext createCommitContext() { synchronized (getViewMonitor()) { lockView(); try { return new CDOCommitContextImpl(this); } finally { unlockView(); } } } public CDOCommitInfo commit() throws CommitException { return commit(null); } /** * @since 2.0 */ public CDOCommitInfo commit(IProgressMonitor progressMonitor) throws CommitException { CDOConflictResolver[] conflictResolvers = options().getConflictResolvers(); if (conflictResolvers.length != 0) { try { checkActive(); // Reach out to conflict resolvers before synchronizing on this transaction to avoid deadlocks in UI thread. for (CDOConflictResolver resolver : conflictResolvers) { if (resolver instanceof CDOConflictResolver3) { if (!((CDOConflictResolver3)resolver).preCommit()) { return null; } } } } catch (Exception ex) { throw new CommitException(ex); } } CDOCommitInfo info = commitSynced(progressMonitor); if (info != null) { long timeStamp = info.getTimeStamp(); long timeout = options().getCommitInfoTimeout(); if (!waitForUpdate(timeStamp, timeout)) { throw new TimeoutRuntimeException("Did not receive an update: " + this); } } return info; } private CDOCommitInfo commitSynced(IProgressMonitor progressMonitor) throws DanglingIntegrityException, CommitException { synchronized (getViewMonitor()) { lockView(); try { InternalCDOSession session = getSession(); try { checkActive(); if (hasConflict()) { throw new LocalCommitConflictException(Messages.getString("CDOTransactionImpl.2")); //$NON-NLS-1$ } commitToken = (CommitToken)session.startLocalCommit(); CDOTransactionStrategy transactionStrategy = getTransactionStrategy(); CDOCommitInfo info = transactionStrategy.commit(this, progressMonitor); if (info != null) { lastCommitTime = info.getTimeStamp(); } return info; } catch (DanglingReferenceException ex) { throw new DanglingIntegrityException(ex); } catch (CommitException ex) { throw ex; } catch (Throwable t) { try { // The commit may have succeeded on the server, but after that network problems or timeouts have hit us. // Let's see if we can recover... CDOCommitInfo info = session.getSessionProtocol().resetTransaction(getViewID(), commitToken.getCommitNumber()); if (info != null) { lastCommitTime = info.getTimeStamp(); InvalidationData invalidationData = new InvalidationData(); invalidationData.setCommitInfo(info); invalidationData.setSender(this); invalidationData.setClearResourcePathCache(true); invalidationData.setSecurityImpact(CDOProtocol.CommitNotificationInfo.IMPACT_NONE); invalidationData.setNewPermissions(null); invalidationData.setLockChangeInfo(null); session.invalidate(invalidationData); // At this point the session (invalidator) is recovered. // Continue to rethrow the exception and let the client call rollback()... } } catch (Throwable ex) { if (TRACER.isEnabled()) { TRACER.trace(ex); } } Throwable cause = t.getCause(); if (cause instanceof MonitorCanceledException) { if (!(cause.getCause() instanceof TimeoutRuntimeException)) { throw new OperationCanceledException("CDOTransactionImpl.7");//$NON-NLS-1$ } } throw new CommitException(t); } finally { session.endLocalCommit(commitToken); commitToken = null; clearResourcePathCacheIfNecessary(null); } } finally { unlockView(); } } } public CommitToken getCommitToken() { return commitToken; } /** * @since 2.0 */ public void rollback() { checkActive(); synchronized (getViewMonitor()) { lockView(); try { CDOTransactionStrategy strategy = getTransactionStrategy(); strategy.rollback(this, firstSavepoint); cleanUp(null); } finally { unlockView(); } } } private void removeObject(CDOID id, final CDOObject object) { removeObject(id); if (object instanceof CDOResource) { InternalCDOViewSet viewSet = getViewSet(); viewSet.executeWithoutNotificationHandling(new Callable<Boolean>() { public Boolean call() throws Exception { EList<Resource> resources = getResourceSet().getResources(); resources.remove(object); return true; } }); } } private Set<CDOID> rollbackCompletely(CDOUserSavepoint savepoint) { Set<CDOID> idsOfNewObjectsWithDeltas = new HashSet<CDOID>(); Set<InternalCDOObject> newObjects = new HashSet<InternalCDOObject>(); // Start from the last savepoint and come back up to the active for (InternalCDOSavepoint itrSavepoint = lastSavepoint; itrSavepoint != null; itrSavepoint = itrSavepoint.getPreviousSavepoint()) { Set<Object> toBeDetached = new HashSet<Object>(); // Rollback new objects attached after the save point Map<CDOID, CDOObject> newObjectsMap = itrSavepoint.getNewObjects(); for (CDOID id : newObjectsMap.keySet()) { InternalCDOObject object = (InternalCDOObject)newObjectsMap.get(id); toBeDetached.add(id); toBeDetached.add(object); toBeDetached.add(object.cdoInternalInstance()); removeObject(id, object); newObjects.add(object); } // Rollback new objects re-attached after the save point Map<CDOID, CDOObject> reattachedObjectsMap = itrSavepoint.getReattachedObjects(); Set<CDOID> detachedIDs = itrSavepoint.getDetachedObjects().keySet(); for (CDOObject reattachedObject : reattachedObjectsMap.values()) { CDOID id = reattachedObject.cdoID(); if (!detachedIDs.contains(id)) { InternalCDOObject internal = (InternalCDOObject)reattachedObject; toBeDetached.add(id); toBeDetached.add(internal); toBeDetached.add(internal.cdoInternalInstance()); removeObject(id, internal); internal.cdoInternalSetView(null); internal.cdoInternalSetID(null); internal.cdoInternalSetState(CDOState.TRANSIENT); } } for (Object idOrObject : toBeDetached) { if (idOrObject instanceof CDOObjectImpl) { CDOObjectImpl impl = (CDOObjectImpl)idOrObject; InternalCDORevision revision = impl.cdoRevision(); Resource.Internal directResource = impl.eDirectResource(); EObject container = impl.eContainer(); if (!toBeDetached.contains(directResource) && !toBeDetached.contains(container)) { if (revision != null) { // Unset direct resource and container in the revision. // Later cdoInternalPostDetach() will migrate these values into the EObject. // See CDOStateMachine.RollbackTransition.execute(). revision.setResourceID(null); revision.setContainerID(null); revision.setContainingFeatureID(0); } else { // Unset direct resource and eContainer in the EObject. impl.cdoInternalSetResource(null); } } } else if (idOrObject instanceof CDOObjectWrapper) { CDOObjectWrapper wrapper = (CDOObjectWrapper)idOrObject; Resource.Internal directResource = wrapper.eDirectResource(); EObject container = wrapper.eContainer(); if (!toBeDetached.contains(directResource) && !toBeDetached.contains(container)) { wrapper.setInstanceResource(null); wrapper.setInstanceContainer(null, 0); } } } Map<CDOID, CDORevisionDelta> revisionDeltas = itrSavepoint.getRevisionDeltas2(); if (!revisionDeltas.isEmpty()) { for (CDORevisionDelta dirtyObject : revisionDeltas.values()) { CDOID id = dirtyObject.getID(); if (isObjectNew(id)) { idsOfNewObjectsWithDeltas.add(id); } } } // Rollback all detached objects Map<CDOID, CDOObject> detachedObjectsMap = itrSavepoint.getDetachedObjects(); if (!detachedObjectsMap.isEmpty()) { for (Entry<CDOID, CDOObject> detachedObjectEntry : detachedObjectsMap.entrySet()) { CDOID id = detachedObjectEntry.getKey(); if (isObjectNew(id)) { idsOfNewObjectsWithDeltas.add(id); } else { InternalCDOObject detachedObject = (InternalCDOObject)detachedObjectEntry.getValue(); InternalCDORevision cleanRev = cleanRevisions.get(detachedObject); cleanObject(detachedObject, cleanRev); } } } for (Entry<CDOID, CDOObject> entryDirtyObject : itrSavepoint.getDirtyObjects().entrySet()) { CDOID id = entryDirtyObject.getKey(); if (!isObjectNew(id)) { InternalCDOObject internalDirtyObject = (InternalCDOObject)entryDirtyObject.getValue(); // Bug 283985 (Re-attachment): Skip objects that were reattached, because // they were already reset to TRANSIENT earlier in this method if (!reattachedObjectsMap.values().contains(internalDirtyObject)) { CDOStateMachine.INSTANCE.rollback(internalDirtyObject, this); } } } if (savepoint == itrSavepoint) { break; } } for (InternalCDOObject internalCDOObject : newObjects) { if (FSMUtil.isNew(internalCDOObject)) { CDOStateMachine.INSTANCE.rollback(internalCDOObject, this); internalCDOObject.cdoInternalSetID(null); internalCDOObject.cdoInternalSetView(null); } } return idsOfNewObjectsWithDeltas; } private void loadSavepoint(CDOSavepoint savepoint, Set<CDOID> idsOfNewObjectWithDeltas) { Map<CDOID, CDOObject> dirtyObjects = getDirtyObjects(); Map<CDOID, CDOObject> newObjMaps = getNewObjects(); Map<CDOID, CDORevision> newBaseRevision = getBaseNewObjects(); Map<CDOID, CDOObject> detachedObjects = getDetachedObjects(); // Reload the objects (NEW) with their base. for (CDOID id : idsOfNewObjectWithDeltas) { if (detachedObjects.containsKey(id)) { continue; } InternalCDOObject object = (InternalCDOObject)newObjMaps.get(id); CDORevision revision = newBaseRevision.get(id); if (revision != null) { object.cdoInternalSetRevision(revision.copy()); object.cdoInternalSetView(this); object.cdoInternalSetState(CDOState.NEW); // Load the object from revision to EObject object.cdoInternalPostLoad(); if (super.getObject(object.cdoID(), false) == null) { registerObject(object); } } } // We need to register back new objects that are not removed anymore there. for (Entry<CDOID, CDOObject> entryNewObject : newObjMaps.entrySet()) { InternalCDOObject object = (InternalCDOObject)entryNewObject.getValue(); // Go back to the previous state cleanObject(object, object.cdoRevision()); object.cdoInternalSetState(CDOState.NEW); } for (Entry<CDOID, CDOObject> entryDirtyObject : dirtyObjects.entrySet()) { if (detachedObjects.containsKey(entryDirtyObject.getKey())) { continue; } // Rollback every persisted objects InternalCDOObject internalDirtyObject = (InternalCDOObject)entryDirtyObject.getValue(); cleanObject(internalDirtyObject, getRevision(entryDirtyObject.getKey(), true)); } CDOObjectMerger merger = new CDOObjectMerger(); for (InternalCDOSavepoint itrSavepoint = firstSavepoint; itrSavepoint != savepoint; itrSavepoint = itrSavepoint.getNextSavepoint()) { for (CDORevisionDelta delta : itrSavepoint.getRevisionDeltas2().values()) { CDOID id = delta.getID(); boolean isNew = isObjectNew(id); if (isNew && !idsOfNewObjectWithDeltas.contains(id) || detachedObjects.containsKey(id)) { continue; } Map<CDOID, CDOObject> map = isNew ? newObjMaps : dirtyObjects; InternalCDOObject object = (InternalCDOObject)map.get(id); // Change state of the objects merger.merge(object, delta); // Load the object from revision to EObject object.cdoInternalPostLoad(); } } dirty = savepoint.wasDirty(); } /** * @since 2.0 */ public void detachObject(InternalCDOObject object) { synchronized (getViewMonitor()) { lockView(); try { CDOTransactionHandler1[] handlers = getTransactionHandlers1(); for (int i = 0; i < handlers.length; i++) { CDOTransactionHandler1 handler = handlers[i]; handler.detachingObject(this, object); } // deregister object CDOID id = object.cdoID(); if (object.cdoState() == CDOState.NEW) { Map<CDOID, CDOObject> map = lastSavepoint.getNewObjects(); // Determine if we added object if (map.containsKey(id)) { map.remove(id); } else { lastSavepoint.getDetachedObjects().put(id, object); } // deregister object deregisterObject(object); } else { if (!cleanRevisions.containsKey(object)) { cleanRevisions.put(object, object.cdoRevision()); } lastSavepoint.getDetachedObjects().put(id, object); // Object may have been reattached previously, in which case it must // here be removed from the collection of reattached objects lastSavepoint.getReattachedObjects().remove(id); } getUnitManager().removeObject(object); } finally { unlockView(); } } } /** * @since 2.0 */ public void handleRollback(InternalCDOSavepoint savepoint) { if (savepoint == null) { throw new IllegalArgumentException(Messages.getString("CDOTransactionImpl.3")); //$NON-NLS-1$ } if (savepoint.getTransaction() != this) { throw new IllegalArgumentException(MessageFormat.format(Messages.getString("CDOTransactionImpl.4"), savepoint)); //$NON-NLS-1$ } if (!savepoint.isValid()) { throw new IllegalArgumentException(MessageFormat.format(Messages.getString("CDOTransactionImpl.6"), savepoint)); //$NON-NLS-1$ } if (TRACER.isEnabled()) { TRACER.trace("handleRollback()"); //$NON-NLS-1$ } synchronized (getViewMonitor()) { lockView(); try { // Remember current revisions Map<CDOObject, CDORevision> oldRevisions = new HashMap<CDOObject, CDORevision>(); for (CDOObject object : getDirtyObjects().values()) { CDORevision oldRevision = object.cdoRevision(); if (oldRevision != null) { oldRevisions.put(object, oldRevision); } } // Rollback objects Set<CDOID> idsOfNewObjectWithDeltas = rollbackCompletely(savepoint); lastSavepoint = savepoint; lastSavepoint.setNextSavepoint(null); lastSavepoint.clear(); // Load from first savepoint up to current savepoint loadSavepoint(lastSavepoint, idsOfNewObjectWithDeltas); if (lastSavepoint == firstSavepoint && options().isAutoReleaseLocksEnabled()) { CDORepositoryInfo repositoryInfo = getSession().getRepositoryInfo(); if (isDurableView() && repositoryInfo.getState() == CDOCommonRepository.State.ONLINE || repositoryInfo.getType() == CDOCommonRepository.Type.MASTER) { // Unlock all objects unlockObjects(null, null); } } // Send notifications for (Entry<CDOObject, CDORevision> entry : oldRevisions.entrySet()) { InternalCDOObject object = (InternalCDOObject)entry.getKey(); if (FSMUtil.isTransient(object)) { continue; } InternalCDORevision oldRevision = (InternalCDORevision)entry.getValue(); InternalCDORevision newRevision = object.cdoRevision(); if (newRevision == null) { newRevision = getRevision(oldRevision.getID(), true); object.cdoInternalSetRevision(newRevision); object.cdoInternalSetState(CDOState.CLEAN); } if (newRevision != null) { InternalCDORevisionDelta delta = newRevision.compare(oldRevision); if (!delta.isEmpty()) { Set<CDOObject> detachedObjects = Collections.emptySet(); CDONotificationBuilder builder = new CDONotificationBuilder(this); NotificationChain notification = builder.buildNotification(object, oldRevision, delta, detachedObjects); if (notification != null) { notification.dispatch(); } } } } IListener[] listeners = getListeners(); if (listeners != null) { fireEvent(new FinishedEvent(true), listeners); } CDOTransactionHandler2[] handlers = getTransactionHandlers2(); for (int i = 0; i < handlers.length; i++) { CDOTransactionHandler2 handler = handlers[i]; try { handler.rolledBackTransaction(this); } catch (RuntimeException ex) { OM.LOG.error(ex); } } } catch (RuntimeException ex) { throw ex; } catch (Exception ex) { throw new TransactionException(ex); } finally { unlockView(); } } } /** * @since 2.0 */ public InternalCDOSavepoint handleSetSavepoint() { synchronized (getViewMonitor()) { lockView(); try { addToBase(lastSavepoint.getNewObjects()); lastSavepoint = createSavepoint(lastSavepoint); return lastSavepoint; } finally { unlockView(); } } } private CDOSavepointImpl createSavepoint(InternalCDOSavepoint lastSavepoint) { return new CDOSavepointImpl(this, lastSavepoint); } /** * @since 2.0 */ public InternalCDOSavepoint setSavepoint() { checkActive(); synchronized (getViewMonitor()) { lockView(); try { return (InternalCDOSavepoint)getTransactionStrategy().setSavepoint(this); } finally { unlockView(); } } } public boolean hasMultipleSavepoints() { return lastSavepoint != firstSavepoint; } private void addToBase(Map<CDOID, CDOObject> objects) { for (CDOObject object : objects.values()) { // Load instance to revision ((InternalCDOObject)object).cdoInternalPreCommit(); lastSavepoint.getBaseNewObjects().put(object.cdoID(), object.cdoRevision().copy()); } } @Override protected String getClassName() { return "CDOTransaction"; //$NON-NLS-1$ } public void registerAttached(InternalCDOObject object, boolean isNew) { if (TRACER.isEnabled()) { TRACER.format("Registering new object {0}", object); //$NON-NLS-1$ } synchronized (getViewMonitor()) { lockView(); try { if (isNew) { registerNewPackage(object.eClass().getEPackage()); } CDOTransactionHandler1[] handlers = getTransactionHandlers1(); for (int i = 0; i < handlers.length; i++) { CDOTransactionHandler1 handler = handlers[i]; handler.attachingObject(this, object); } if (isNew) { registerNew(lastSavepoint.getNewObjects(), object); } } finally { unlockView(); } } } private void registerNewPackage(EPackage ePackage) { CDOPackageRegistry packageRegistry = getSession().getPackageRegistry(); if (!packageRegistry.containsKey(ePackage.getNsURI())) { packageRegistry.putEPackage(ePackage); } } private CDOOriginSizeProvider getOriginSizeProvider(InternalCDOObject object, CDOFeatureDelta featureDelta, InternalCDORevision cleanRevision) { EStructuralFeature feature = featureDelta.getFeature(); if (feature.isMany()) { if (cleanRevision == null) { cleanRevision = cleanRevisions.get(object); if (cleanRevision == null) { cleanRevision = object.cdoRevision(); } } CDOList list = cleanRevision.getList(feature); final int originSize = list.size(); return new CDOOriginSizeProvider() { public int getOriginSize() { return originSize; } }; } return null; } /** * Receives notification for new and dirty objects */ public void registerFeatureDelta(InternalCDOObject object, CDOFeatureDelta featureDelta) { synchronized (getViewMonitor()) { lockView(); try { registerFeatureDelta(object, featureDelta, null); } finally { unlockView(); } } } public void registerFeatureDelta(InternalCDOObject object, CDOFeatureDelta featureDelta, InternalCDORevision cleanRevision) { synchronized (getViewMonitor()) { lockView(); try { CDOID id = object.cdoID(); boolean needToSaveFeatureDelta = true; if (object.cdoState() == CDOState.NEW) { // Register Delta for new objects only if objectA doesn't belong to this savepoint if (getLastSavepoint().getPreviousSavepoint() == null || featureDelta == null) { needToSaveFeatureDelta = false; } else { Map<CDOID, CDOObject> map = getLastSavepoint().getNewObjects(); needToSaveFeatureDelta = !map.containsKey(id); } } if (needToSaveFeatureDelta) { Map<CDOID, CDORevisionDelta> revisionDeltas = lastSavepoint.getRevisionDeltas2(); InternalCDORevisionDelta revisionDelta = (InternalCDORevisionDelta)revisionDeltas.get(id); if (revisionDelta == null) { InternalCDORevision revision = object.cdoRevision(); revisionDelta = (InternalCDORevisionDelta)CDORevisionUtil.createDelta(revision); revisionDeltas.put(id, revisionDelta); } CDOOriginSizeProvider originSizeProvider = getOriginSizeProvider(object, featureDelta, cleanRevision); revisionDelta.addFeatureDelta(featureDelta, originSizeProvider); } CDOTransactionHandler1[] handlers = getTransactionHandlers1(); for (int i = 0; i < handlers.length; i++) { CDOTransactionHandler1 handler = handlers[i]; handler.modifyingObject(this, object, featureDelta); } } finally { unlockView(); } } } public void registerRevisionDelta(CDORevisionDelta revisionDelta) { synchronized (getViewMonitor()) { lockView(); try { Map<CDOID, CDORevisionDelta> revisionDeltas = lastSavepoint.getRevisionDeltas2(); CDOID id = revisionDelta.getID(); if (!revisionDeltas.containsKey(id)) { revisionDeltas.put(id, revisionDelta); } } finally { unlockView(); } } } public void registerDirty(InternalCDOObject object, CDOFeatureDelta featureDelta) { registerDirty(object, featureDelta, null); } public void registerDirty(InternalCDOObject object, CDOFeatureDelta featureDelta, InternalCDORevision cleanRevision) { if (TRACER.isEnabled()) { TRACER.format("Registering dirty object {0}", object); //$NON-NLS-1$ } if (featureDelta != null) { registerFeatureDelta(object, featureDelta, cleanRevision); } registerNew(lastSavepoint.getDirtyObjects(), object); } /** * TODO Simon: Should this method go to CDOSavePointImpl? */ @SuppressWarnings({ "rawtypes", "unchecked" }) private void registerNew(Map map, InternalCDOObject object) { Object old = map.put(object.cdoID(), object); if (old != null) { throw new IllegalStateException(MessageFormat.format(Messages.getString("CDOTransactionImpl.10"), object)); //$NON-NLS-1$ } setDirty(true); } public List<CDOPackageUnit> analyzeNewPackages() { synchronized (getViewMonitor()) { lockView(); try { CDOPackageRegistry packageRegistry = getSession().getPackageRegistry(); Set<EPackage> usedPackages = new HashSet<EPackage>(); Set<EPackage> usedNewPackages = new HashSet<EPackage>(); for (CDOObject object : getNewObjects().values()) { EPackage ePackage = object.eClass().getEPackage(); if (usedPackages.add(ePackage)) { EPackage topLevelPackage = EMFUtil.getTopLevelPackage(ePackage); if (ePackage == topLevelPackage || usedPackages.add(topLevelPackage)) { // if (!CDOModelUtil.isSystemPackage(topLevelPackage)) { CDOPackageUnit packageUnit = packageRegistry.getPackageUnit(topLevelPackage); if (packageUnit.getState() == CDOPackageUnit.State.NEW) { usedNewPackages.add(topLevelPackage); } } } } } if (usedNewPackages.size() > 0) { Set<CDOPackageUnit> result = new HashSet<CDOPackageUnit>(); for (EPackage usedNewPackage : analyzeNewPackages(usedNewPackages, packageRegistry)) { CDOPackageUnit packageUnit = packageRegistry.getPackageUnit(usedNewPackage); result.add(packageUnit); } return new ArrayList<CDOPackageUnit>(result); } return Collections.emptyList(); } finally { unlockView(); } } } private static List<EPackage> analyzeNewPackages(Collection<EPackage> usedTopLevelPackages, CDOPackageRegistry packageRegistry) { // Determine which of the corresponding EPackages are new List<EPackage> newPackages = new ArrayList<EPackage>(); IPackageClosure closure = new CompletePackageClosure(); usedTopLevelPackages = closure.calculate(usedTopLevelPackages); for (EPackage usedPackage : usedTopLevelPackages) { // if (!CDOModelUtil.isSystemPackage(usedPackage)) { CDOPackageUnit packageUnit = packageRegistry.getPackageUnit(usedPackage); if (packageUnit == null) { throw new CDOException(MessageFormat.format(Messages.getString("CDOTransactionImpl.11"), usedPackage)); //$NON-NLS-1$ } if (packageUnit.getState() == CDOPackageUnit.State.NEW) { newPackages.add(usedPackage); } } } return newPackages; } private void cleanUp(CDOCommitContext commitContext) { if (commitContext == null || !commitContext.isPartialCommit()) { if (commitContext != null) { for (CDOObject object : commitContext.getDetachedObjects().values()) { cleanUpLockState(object); } } lastSavepoint = firstSavepoint; firstSavepoint.clear(); firstSavepoint.setNextSavepoint(null); cleanRevisions.clear(); dirty = false; conflict = 0; idGenerator.reset(); } else { collapseSavepoints(commitContext); for (CDOObject object : commitContext.getDetachedObjects().values()) { cleanRevisions.remove(object); cleanUpLockState(object); } for (CDOObject object : commitContext.getDirtyObjects().values()) { cleanRevisions.remove(object); } } // Reset partial-commit filter committables = null; } private void cleanUpLockState(CDOObject object) { InternalCDOLockState lockState = (InternalCDOLockState)removeLockState(object); if (lockState != null) { lockState.dispose(); } } private void collapseSavepoints(CDOCommitContext commitContext) { InternalCDOSavepoint newSavepoint = createSavepoint(null); copyUncommitted(lastSavepoint.getAllNewObjects(), commitContext.getNewObjects(), newSavepoint.getNewObjects()); copyUncommitted(lastSavepoint.getAllDirtyObjects(), commitContext.getDirtyObjects(), newSavepoint.getDirtyObjects()); copyUncommitted(lastSavepoint.getAllRevisionDeltas(), commitContext.getRevisionDeltas(), newSavepoint.getRevisionDeltas2()); copyUncommitted(lastSavepoint.getAllDetachedObjects(), commitContext.getDetachedObjects(), newSavepoint.getDetachedObjects()); lastSavepoint = newSavepoint; firstSavepoint = lastSavepoint; } private <T> void copyUncommitted(Map<CDOID, T> oldSavepointMap, Map<CDOID, T> commitContextMap, Map<CDOID, T> newSavepointMap) { for (Entry<CDOID, T> entry : oldSavepointMap.entrySet()) { if (!commitContextMap.containsKey(entry.getKey())) { newSavepointMap.put(entry.getKey(), entry.getValue()); } } } public CDOSavepoint[] exportChanges(OutputStream stream) throws IOException { synchronized (getViewMonitor()) { lockView(); try { CDODataOutput out = new CDODataOutputImpl(new ExtendedDataOutputStream(stream)) { @Override public CDOIDProvider getIDProvider() { return CDOTransactionImpl.this; } @Override public CDOPackageRegistry getPackageRegistry() { return getSession().getPackageRegistry(); } @Override public CDORevisionUnchunker getRevisionUnchunker() { return getSession(); } }; List<CDOSavepoint> savepoints = new ArrayList<CDOSavepoint>(); int totalNewObjects = 0; InternalCDOSavepoint savepoint = firstSavepoint; while (savepoint != null) { Collection<CDOObject> newObjects = savepoint.getNewObjects().values(); totalNewObjects += newObjects.size(); savepoint = savepoint.getNextSavepoint(); } out.writeInt(totalNewObjects); savepoint = firstSavepoint; while (savepoint != null) { Collection<CDOObject> newObjects = savepoint.getNewObjects().values(); Collection<CDORevisionDelta> revisionDeltas = savepoint.getRevisionDeltas2().values(); if (newObjects.isEmpty() && revisionDeltas.isEmpty()) { savepoint = savepoint.getNextSavepoint(); continue; } savepoints.add(savepoint); out.writeBoolean(true); out.writeInt(newObjects.size()); for (CDOObject newObject : newObjects) { out.writeCDORevision(newObject.cdoRevision(), CDORevision.UNCHUNKED); } out.writeInt(revisionDeltas.size()); for (CDORevisionDelta revisionDelta : revisionDeltas) { out.writeCDORevisionDelta(revisionDelta); } savepoint = savepoint.getNextSavepoint(); } out.writeBoolean(false); return savepoints.toArray(new CDOSavepoint[savepoints.size()]); } finally { unlockView(); } } } public CDOSavepoint[] importChanges(InputStream stream, boolean reconstructSavepoints) throws IOException { synchronized (getViewMonitor()) { lockView(); try { List<CDOSavepoint> savepoints = new ArrayList<CDOSavepoint>(); if (stream.available() > 0) { CDODataInput in = new CDODataInputImpl(new ExtendedDataInputStream(stream)) { public CDOPackageRegistry getPackageRegistry() { return getSession().getPackageRegistry(); } @Override protected CDOBranchManager getBranchManager() { return getSession().getBranchManager(); } @Override protected CDOCommitInfoManager getCommitInfoManager() { return getSession().getCommitInfoManager(); } @Override protected CDORevisionFactory getRevisionFactory() { return getSession().getRevisionManager().getFactory(); } @Override protected CDOLobStore getLobStore() { return getSession().getLobStore(); } @Override protected CDOListFactory getListFactory() { return CDOListWithElementProxiesImpl.FACTORY; } }; // Increase the internal tempID counter to prevent ID collisions during mapping int totalNewObjects = in.readInt(); for (int i = 0; i < totalNewObjects; i++) { createIDForNewObject(null); } Map<CDOID, CDOID> idMappings = CDOIDUtil.createMap(); while (in.readBoolean()) { if (reconstructSavepoints) { InternalCDOSavepoint savepoint = setSavepoint(); savepoints.add(savepoint); } // Import revisions and deltas List<InternalCDORevision> revisions = new ArrayList<InternalCDORevision>(); importNewRevisions(in, revisions, idMappings); List<InternalCDORevisionDelta> revisionDeltas = importRevisionDeltas(in); if (!idMappings.isEmpty()) { // Re-map temp IDs CDOIDMapper idMapper = new CDOIDMapper(idMappings); for (InternalCDORevision revision : revisions) { revision.adjustReferences(idMapper); } for (InternalCDORevisionDelta delta : revisionDeltas) { delta.adjustReferences(idMapper); } } if (!revisions.isEmpty()) { // Create new objects List<InternalCDOObject> newObjects = new ArrayList<InternalCDOObject>(); for (InternalCDORevision revision : revisions) { InternalCDOObject object = newInstance(revision); registerObject(object); registerAttached(object, true); newObjects.add(object); } // Post-load new objects (important for legacy objects!) for (InternalCDOObject object : newObjects) { object.cdoInternalPostLoad(); } } // Apply deltas CDOObjectMerger merger = new CDOObjectMerger(); for (InternalCDORevisionDelta delta : revisionDeltas) { InternalCDOObject object = getObject(delta.getID()); int oldVersion = object.cdoRevision().getVersion(); merger.merge(object, delta); registerRevisionDelta(delta); registerDirty(object, null); if (delta.getVersion() < oldVersion) { setConflict(object); } } } } return savepoints.toArray(new CDOSavepoint[savepoints.size()]); } finally { unlockView(); } } } private void importNewRevisions(CDODataInput in, List<InternalCDORevision> revisions, Map<CDOID, CDOID> idMappings) throws IOException { int size = in.readInt(); for (int i = 0; i < size; i++) { InternalCDORevision revision = (InternalCDORevision)in.readCDORevision(false); CDOID oldID = revision.getID(); if (oldID.isTemporary()) { CDOID newID = createIDForNewObject(null); idMappings.put(oldID, newID); revision.setID(newID); } revisions.add(revision); } } private List<InternalCDORevisionDelta> importRevisionDeltas(CDODataInput in) throws IOException { int size = in.readInt(); List<InternalCDORevisionDelta> deltas = new ArrayList<InternalCDORevisionDelta>(size); for (int i = 0; i < size; i++) { InternalCDORevisionDelta delta = (InternalCDORevisionDelta)in.readCDORevisionDelta(); deltas.add(delta); } return deltas; } private InternalCDOObject newInstance(InternalCDORevision revision) { InternalCDOObject object = newInstance(revision.getEClass()); object.cdoInternalSetRevision(revision); object.cdoInternalSetView(this); object.cdoInternalSetState(CDOState.NEW); return object; } public Map<CDOID, CDOObject> getDirtyObjects() { checkActive(); synchronized (getViewMonitor()) { lockView(); try { return lastSavepoint.getAllDirtyObjects(); } finally { unlockView(); } } } public Map<CDOID, CDOObject> getNewObjects() { checkActive(); synchronized (getViewMonitor()) { lockView(); try { return lastSavepoint.getAllNewObjects(); } finally { unlockView(); } } } /** * @since 2.0 */ public Map<CDOID, CDORevision> getBaseNewObjects() { checkActive(); synchronized (getViewMonitor()) { lockView(); try { return lastSavepoint.getAllBaseNewObjects(); } finally { unlockView(); } } } public Map<CDOID, CDORevisionDelta> getRevisionDeltas() { checkActive(); synchronized (getViewMonitor()) { lockView(); try { return lastSavepoint.getAllRevisionDeltas(); } finally { unlockView(); } } } /** * @since 2.0 */ public Map<CDOID, CDOObject> getDetachedObjects() { checkActive(); synchronized (getViewMonitor()) { lockView(); try { return lastSavepoint.getAllDetachedObjects(); } finally { unlockView(); } } } @Override protected CloseableIterator<CDOResourceNode> queryResourcesUnsynced(CDOResourceFolder folder, final String name, final boolean exactMatch) { if (!isDirty()) { return super.queryResourcesUnsynced(folder, name, exactMatch); } CDOState state; CDOList list = null; if (folder == null) { CDOResource rootResource = getRootResource(); state = rootResource.cdoState(); if (state.isLocal()) { InternalCDORevision revision = (InternalCDORevision)rootResource.cdoRevision(); if (revision != null) { list = revision.getList(EresourcePackage.Literals.CDO_RESOURCE__CONTENTS); } } } else { state = folder.cdoState(); if (state == CDOState.TRANSIENT) { throw new CDOException("Folder " + folder + " is transient"); } if (state.isLocal()) { InternalCDORevision revision = (InternalCDORevision)folder.cdoRevision(); if (revision != null) { list = revision.getList(EresourcePackage.Literals.CDO_RESOURCE_FOLDER__NODES); } } } if (state.isRemote()) { Set<CDOID> validIDs = null; List<CDOResourceNode> addedNodes = null; if (list != null) { for (Object element : list) { if (element instanceof CDOResourceNode) { CDOResourceNode node = (CDOResourceNode)element; if (addedNodes == null) { addedNodes = new ArrayList<CDOResourceNode>(); } addedNodes.add(node); } else { CDOID id = (CDOID)element; if (isObjectNew(id)) { CDOResourceNode node = (CDOResourceNode)getObject(id); addedNodes.add(node); } else { if (validIDs == null) { validIDs = new HashSet<CDOID>(); } validIDs.add(id); } } } } final Set<CDOID> finalValidIDs = validIDs; final List<CDOResourceNode> finalAddedNodes = addedNodes; final CloseableIterator<CDOResourceNode> delegate = super.queryResourcesUnsynced(folder, name, exactMatch); return new AbstractCloseableIterator<CDOResourceNode>() { private Iterator<CDOResourceNode> addedNodesIterator = finalAddedNodes == null ? null : finalAddedNodes.iterator(); @Override protected Object computeNextElement() { if (addedNodesIterator != null) { if (isClosed()) { return END_OF_DATA; } while (addedNodesIterator.hasNext()) { CDOResourceNode node = addedNodesIterator.next(); // Locally added node is not matched by the server; match it now. if (isResourceMatch(node.getName(), name, exactMatch)) { return node; } } addedNodesIterator = null; } if (finalValidIDs != null) { while (delegate.hasNext()) { CDOResourceNode node = delegate.next(); if (finalValidIDs.contains(node.cdoID())) { if (node.cdoState().isLocal()) { // Locally changed node is not matched by the server; match it now. if (isResourceMatch(node.getName(), name, exactMatch)) { return node; } } else { // Locally unchanged node is matched by the server already; just return it. return node; } } } } return END_OF_DATA; } @Override public void close() { delegate.close(); } @Override public boolean isClosed() { return delegate.isClosed(); } }; } final CDOList finalList = list; return new AbstractCloseableIterator<CDOResourceNode>() { private Iterator<Object> listIterator = finalList == null ? null : finalList.iterator(); @Override protected Object computeNextElement() { if (listIterator != null) { while (listIterator.hasNext()) { Object element = listIterator.next(); CDOResourceNode node = getResourceNode(element); // Locally added node is not matched by the server; match it now. if (isResourceMatch(node.getName(), name, exactMatch)) { return node; } } listIterator = null; } return END_OF_DATA; } private CDOResourceNode getResourceNode(Object element) { if (element instanceof CDOResourceNode) { return (CDOResourceNode)element; } return (CDOResourceNode)getObject((CDOID)element); } @Override public void close() { listIterator = null; } @Override public boolean isClosed() { return listIterator == null; } }; } @Override protected <T extends EObject> CloseableIterator<T> queryInstancesUnsynced(final EClass type, final boolean exact) { if (!isDirty()) { return super.queryInstancesUnsynced(type, exact); } final Map<CDOID, CDOObject> newObjects = lastSavepoint.getAllNewObjects(); final CloseableIterator<T> delegate = super.queryInstancesUnsynced(type, exact); return new AbstractCloseableIterator<T>() { private Iterator<CDOObject> newObjectsIterator = newObjects.isEmpty() ? null : newObjects.values().iterator(); @Override protected Object computeNextElement() { if (newObjectsIterator != null) { if (isClosed()) { return END_OF_DATA; } while (newObjectsIterator.hasNext()) { EObject newObject = CDOUtil.getEObject(newObjectsIterator.next()); // Locally added node is not matched by the server; match it now. if (exact ? type == newObject.eClass() : type.isInstance(newObject)) { return newObject; } } newObjectsIterator = null; } while (delegate.hasNext()) { T object = delegate.next(); CDOObject cdoObject = CDOUtil.getCDOObject(object); System.out.println(cdoObject); if (!FSMUtil.isTransient(cdoObject)) { return object; } } return END_OF_DATA; } @Override public void close() { delegate.close(); } @Override public boolean isClosed() { return delegate.isClosed(); } }; } @Override protected CloseableIterator<CDOObjectReference> queryXRefsUnsynced(Set<CDOObject> targetObjects, EReference... sourceReferences) { if (!isDirty()) { return super.queryXRefsUnsynced(targetObjects, sourceReferences); } final Set<CDOID> targetIDs = new HashSet<CDOID>(); final Set<EReference> relevantReferences = toSet(sourceReferences); final CDOQuery query = createXRefsQuery(false, targetIDs, targetObjects, sourceReferences); if (query.getQueryString() != null) { final Set<CDOID> localIDs = new HashSet<CDOID>(); final List<CDOObjectReference> localXRefs = queryXRefsLocal(localIDs, targetIDs, relevantReferences); final CloseableIterator<CDOObjectReference> delegate = query.getResultAsync(CDOObjectReference.class); return new AbstractCloseableIterator<CDOObjectReference>() { private Iterator<CDOObjectReference> localXRefsIterator = localXRefs == null ? null : localXRefs.iterator(); @Override protected Object computeNextElement() { if (localXRefsIterator != null) { if (isClosed()) { return END_OF_DATA; } if (localXRefsIterator.hasNext()) { return localXRefsIterator.next(); } localXRefsIterator = null; } while (delegate.hasNext()) { CDOObjectReference ref = delegate.next(); if (!localIDs.contains(ref.getSourceID())) { return ref; } } return END_OF_DATA; } @Override public void close() { delegate.close(); } @Override public boolean isClosed() { return delegate.isClosed(); } }; } List<CDOObjectReference> localXRefs = queryXRefsLocal(null, targetIDs, relevantReferences); if (localXRefs != null) { return new DelegatingCloseableIterator<CDOObjectReference>(localXRefs.iterator()); } return AbstractCloseableIterator.emptyCloseable(); } private List<CDOObjectReference> queryXRefsLocal(Set<CDOID> localIDs, Set<CDOID> targetIDs, Set<EReference> relevantReferences) { List<CDOObjectReference> refs = null; Map<CDOID, CDOObject> newObjects = lastSavepoint.getAllNewObjects(); Map<CDOID, CDOObject> dirtyObjects = lastSavepoint.getAllDirtyObjects(); if (localIDs != null) { localIDs.addAll(newObjects.keySet()); localIDs.addAll(dirtyObjects.keySet()); localIDs.addAll(lastSavepoint.getAllDetachedObjects().keySet()); } @SuppressWarnings("unchecked") Iterator<CDOObject> it = new ComposedIterator<CDOObject>( // newObjects.values().iterator(), // dirtyObjects.values().iterator()); while (it.hasNext()) { CDOObject object = it.next(); InternalCDORevision revision = (InternalCDORevision)object.cdoRevision(); for (EReference reference : revision.getClassInfo().getAllPersistentReferences()) { if (!reference.isContainer() && !reference.isContainment() && (relevantReferences == null || relevantReferences.contains(reference))) { if (reference.isMany()) { CDOList list = revision.getList(reference); int index = 0; for (Object value : list) { refs = addXRefLocal(refs, targetIDs, object, reference, index, value); ++index; } } else { Object value = revision.getValue(reference); refs = addXRefLocal(refs, targetIDs, object, reference, 0, value); } } } } return refs; } private List<CDOObjectReference> addXRefLocal(List<CDOObjectReference> refs, Set<CDOID> targetIDs, CDOObject object, EReference reference, int index, Object value) { if (value != null) { CDOID targetID = value instanceof CDOID ? (CDOID)value : getXRefID(CDOUtil.getCDOObject((EObject)value)); if (!CDOIDUtil.isNull(targetID) && targetIDs.contains(targetID)) { CDOIDReference delegateRef = new CDOIDReference(targetID, object.cdoID(), reference, index); CDOObjectReference ref = new CDOObjectReferenceImpl(this, delegateRef); if (refs == null) { refs = new ArrayList<CDOObjectReference>(); } refs.add(ref); } } return refs; } private CDOID getXRefID(CDOObject object) { CDOID id = object.cdoID(); if (id == null) { CDORevisionKey key = cleanRevisions.get(object); if (key != null) { id = key.getID(); } } return id; } @Override protected CDOID getXRefTargetID(CDOObject target) { CDORevisionKey key = cleanRevisions.get(target); if (key != null) { return key.getID(); } return super.getXRefTargetID(target); } @Override protected CDOID getID(InternalCDOObject object, boolean onlyPersistedID) { synchronized (getViewMonitor()) { lockView(); try { CDOID id = super.getID(object, onlyPersistedID); if (id != null) { return id; } // Don't perform the trickery that follows later in this method, if we are being called // indirectly through provideCDOID. This occurs when deltas or revisions are // being written out to a stream; in which case null must be returned (for transients) so that // the caller will detect a dangling reference if (providingCDOID.get() == Boolean.TRUE) { return null; } // The super implementation returns null for a transient (unattached) object; // but in a transaction, a transient object may have been attached previously. // So we consult the cleanRevisions if that's the case. CDORevisionKey revisionKey = cleanRevisions.get(object); if (revisionKey != null) { CDOID revisionID = revisionKey.getID(); if (isObjectDetached(revisionID)) { return revisionID; } } return null; } finally { unlockView(); } } } @Override public CDOID provideCDOID(Object idOrObject) { synchronized (getViewMonitor()) { lockView(); try { try { providingCDOID.set(Boolean.TRUE); return super.provideCDOID(idOrObject); } finally { providingCDOID.remove(); } } finally { unlockView(); } } } @Override public CDOQueryImpl createQuery(String language, String queryString, Object context) { return createQuery(language, queryString, context, false); } public CDOQueryImpl createQuery(String language, String queryString, boolean considerDirtyState) { return createQuery(language, queryString, null, considerDirtyState); } public CDOQueryImpl createQuery(String language, String queryString, Object context, boolean considerDirtyState) { synchronized (getViewMonitor()) { lockView(); try { CDOQueryImpl query = super.createQuery(language, queryString, context); if (considerDirtyState && isDirty()) { query.setChangeSetData(getChangeSetData()); } return query; } finally { unlockView(); } } } @Override protected void doActivate() throws Exception { super.doActivate(); InternalCDOSession session = getSession(); if (session.getRepositoryInfo().getIDGenerationLocation() == CDOCommonRepository.IDGenerationLocation.STORE) { idGenerator = new TempIDGenerator(); } else { idGenerator = session.getIDGenerator(); if (idGenerator == null) { idGenerator = CDOIDGenerator.UUID; } } } /** * @since 2.0 */ @Override protected void doDeactivate() throws Exception { providingCDOID.remove(); options().disposeConflictResolvers(); lastSavepoint = null; firstSavepoint = null; transactionStrategy = null; idGenerator = null; super.doDeactivate(); } /** * Bug 298561: This override removes references to remotely detached objects that are present in any DIRTY or NEW * objects. * * @since 3.0 */ @Override protected Map<CDOObject, Pair<CDORevision, CDORevisionDelta>> invalidate( // List<CDORevisionKey> allChangedObjects, // List<CDOIDAndVersion> allDetachedObjects, // List<CDORevisionDelta> deltas, // Map<CDOObject, CDORevisionDelta> revisionDeltas, // Set<CDOObject> detachedObjects, // Map<CDOID, InternalCDORevision> oldRevisions) { if (!allDetachedObjects.isEmpty()) { // Remove stale references from locally changed or new objects to remotely detached objects Set<CDOObject> remotelyDetachedObjects = getObjects(allDetachedObjects); removeCrossReferences(remotelyDetachedObjects, getDirtyObjects().values()); removeCrossReferences(remotelyDetachedObjects, getNewObjects().values()); invalidateNewChildrenOfRemotelyDetached(remotelyDetachedObjects); } // Bug 290032 - Sticky views InternalCDOSession session = getSession(); if (session.isSticky()) { session.clearCommittedSinceLastRefresh(); } Map<CDOObject, Pair<CDORevision, CDORevisionDelta>> conflicts = super.invalidate( // allChangedObjects, // allDetachedObjects, // deltas, // revisionDeltas, // detachedObjects, // oldRevisions); if (!allChangedObjects.isEmpty()) { // Remove stale references from remotely changed objects to locally detached objects Set<CDOObject> remotelyChangedObjects = getObjects(allChangedObjects); removeCrossReferences(getDetachedObjects().values(), remotelyChangedObjects); } return conflicts; } private void invalidateNewChildrenOfRemotelyDetached(Set<CDOObject> remotelyDetachedObjects) { // Test EObject.eInternalContainer()/eDirectResource() implicit references to have them return null in legacy for // objects in NEW state whose a direct/indirect parent has been remotelly detached for (CDOObject referencer : getNewObjects().values()) { if (CDOUtil.isLegacyObject(referencer)) { InternalEObject referencerInternalEObject = (InternalEObject)referencer; InternalEObject eContainer = referencerInternalEObject.eInternalContainer(); if (eContainer != null) { CDOObject containerCDOObject = CDOUtil.getCDOObject(eContainer); if (remotelyDetachedObjects.contains(containerCDOObject)) { CDOStateMachine.INSTANCE.invalidate((InternalCDOObject)referencer, referencer.cdoRevision()); } } Resource.Internal eDirectResource = referencerInternalEObject.eDirectResource(); if (eDirectResource instanceof CDOResource) { CDOObject cdoResource = (CDOObject)eDirectResource; if (remotelyDetachedObjects.contains(cdoResource)) { CDOStateMachine.INSTANCE.invalidate((InternalCDOObject)referencer, referencer.cdoRevision()); } } } } } private Set<CDOObject> getObjects(Collection<? extends CDOIdentifiable> identifiables) { Set<CDOObject> result = new HashSet<CDOObject>(); for (CDOIdentifiable identifiable : identifiables) { CDOID id = identifiable.getID(); InternalCDOObject object = getObject(id, false); if (object != null) { result.add(object); } } return result; } private void removeCrossReferences(Collection<CDOObject> possibleTargets, Collection<CDOObject> referencers) { CDOStaleReferenceCleaner staleReferenceCleaner = options().getStaleReferenceCleaner(); Collection<Pair<EStructuralFeature.Setting, EObject>> staleReferencesToClean = new LinkedList<Pair<EStructuralFeature.Setting, EObject>>(); for (CDOObject referencer : referencers) { InternalCDOObject internalReferencer = (InternalCDOObject)referencer; InternalCDOClassInfo referencerClassInfo = internalReferencer.cdoClassInfo(); EContentsEList.FeatureIterator<EObject> it = getChangeableCrossReferences(referencer); while (it.hasNext()) { EObject referencedObject = it.next(); CDOObject referencedCDOObject = CDOUtil.getCDOObject(referencedObject); if (possibleTargets.contains(referencedCDOObject)) { EReference reference = (EReference)it.feature(); // In the case of DIRTY, we must investigate further: Is the referencer dirty // because a reference to the referencedObject was added? Only in this case // should we remove it. If this is not the case (i.e. it is dirty in a different // way), we skip it. (If the reference is not persistent, then this exception // doesn't apply: it must be removed for sure.) if (referencer.cdoState() == CDOState.DIRTY && referencerClassInfo.isPersistent(reference)) { InternalCDORevision cleanRevision = cleanRevisions.get(referencer); if (reference.isMany()) { CDOList list = cleanRevision.getList(reference); if (list != null) { for (Object value : list) { if (value == referencedCDOObject.cdoID() || value == referencedObject) { continue; } } } } else { Object value = cleanRevision.getValue(reference); if (value == referencedCDOObject.cdoID() || value == referencedObject) { continue; } } } EStructuralFeature.Setting setting = internalReferencer.eSetting(reference); staleReferencesToClean.add(Pair.create(setting, referencedObject)); } } } if (!staleReferencesToClean.isEmpty()) { staleReferenceCleaner.cleanStaleReferences(staleReferencesToClean); } } private EContentsEList.FeatureIterator<EObject> getChangeableCrossReferences(EObject object) { EClassImpl.FeatureSubsetSupplier features = (EClassImpl.FeatureSubsetSupplier)object.eClass().getEAllStructuralFeatures(); EStructuralFeature[] crossReferences = features.crossReferences(); if (crossReferences != null) { List<EStructuralFeature> changeableReferences = new ArrayList<EStructuralFeature>(); for (int i = 0; i < crossReferences.length; i++) { EStructuralFeature reference = crossReferences[i]; // Filter out derived references if (reference.isDerived()) { continue; } // Filter out unchangeable references if (!reference.isChangeable()) { continue; } changeableReferences.add(reference); } if (!changeableReferences.isEmpty()) { EStructuralFeature[] collectedStructuralFeatures = changeableReferences.toArray(new EStructuralFeature[changeableReferences.size()]); return new EContentsEList.ResolvingFeatureIteratorImpl<EObject>(object, collectedStructuralFeatures); } } return (EContentsEList.FeatureIterator<EObject>)ECrossReferenceEList.<EObject> emptyContentsEList().iterator(); } public long getLastCommitTime() { synchronized (getViewMonitor()) { lockView(); try { return lastCommitTime; } finally { unlockView(); } } } public String getCommitComment() { synchronized (getViewMonitor()) { lockView(); try { return commitComment; } finally { unlockView(); } } } public void setCommitComment(String comment) { synchronized (getViewMonitor()) { lockView(); try { commitComment = comment; } finally { unlockView(); } } } public CDOBranchPoint getCommitMergeSource() { synchronized (getViewMonitor()) { lockView(); try { return commitMergeSource; } finally { unlockView(); } } } public void setCommitMergeSource(CDOBranchPoint mergeSource) { synchronized (getViewMonitor()) { lockView(); try { commitMergeSource = mergeSource; } finally { unlockView(); } } } public void setCommittables(Set<? extends EObject> committables) { synchronized (getViewMonitor()) { lockView(); try { this.committables = committables; } finally { unlockView(); } } } public Set<? extends EObject> getCommittables() { synchronized (getViewMonitor()) { lockView(); try { return committables; } finally { unlockView(); } } } public Map<InternalCDOObject, InternalCDORevision> getCleanRevisions() { synchronized (getViewMonitor()) { lockView(); try { return cleanRevisions; } finally { unlockView(); } } } @Override protected InternalCDORevision getViewedRevision(InternalCDOObject object) { InternalCDORevision rev = super.getViewedRevision(object); // Bug 336590: If we have a clean revision for this object, return that instead if (rev != null) { InternalCDORevision cleanRev = cleanRevisions.get(object); if (cleanRev != null) { return cleanRev; } } return rev; } @Override protected InternalCDORevision getRevision(CDOObject object) { if (object.cdoState() == CDOState.TRANSIENT) { InternalCDORevision revision = cleanRevisions.get(object); if (revision == null) { throw new IllegalStateException("No revision for transient object " + object); } return revision; } return super.getRevision(object); } @Override protected InternalCDOLockState createUpdatedLockStateForNewObject(CDOObject object, IRWLockManager.LockType lockType, boolean on) { CheckUtil.checkState(FSMUtil.isNew(object), "Object is not in NEW state"); CheckUtil.checkArg(lockType, "lockType"); InternalCDOLockState lockState = (InternalCDOLockState)getLockState(object); if (lockState == null) { CheckUtil.checkArg(on == true, "on != true"); Object lockTarget = getLockTarget(object); lockState = (InternalCDOLockState)CDOLockUtil.createLockState(lockTarget); } else { lockState = (InternalCDOLockState)CDOLockUtil.copyLockState(lockState); } CDOLockOwner lockOwner = CDOLockUtil.createLockOwner(this); if (on) { switch (lockType) { case READ: lockState.addReadLockOwner(lockOwner); break; case WRITE: lockState.setWriteLockOwner(lockOwner); break; case OPTION: lockState.setWriteOptionOwner(lockOwner); break; default: throw new IllegalArgumentException("Unknown lock type " + lockType); } } else { switch (lockType) { case READ: lockState.removeReadLockOwner(lockOwner); break; case WRITE: lockState.setWriteLockOwner(null); break; case OPTION: lockState.setWriteOptionOwner(null); break; default: throw new IllegalArgumentException("Unknown lock type " + lockType); } } return lockState; } @Override protected List<CDOLockState> createUnlockedLockStatesForAllNewObjects() { List<CDOLockState> locksOnNewObjects = new LinkedList<CDOLockState>(); for (CDOObject object : getNewObjects().values()) { Object lockTarget = getLockTarget(object); CDOLockState lockState = CDOLockUtil.createLockState(lockTarget); locksOnNewObjects.add(lockState); } return locksOnNewObjects; } public static boolean isResourceMatch(String nodeName, String name, boolean exactMatch) { boolean useEquals = exactMatch || nodeName == null || name == null; return useEquals ? ObjectUtil.equals(nodeName, name) : nodeName.startsWith(name); } public static void resurrectObject(CDOObject object, CDOID id) { if (object.cdoState() != CDOState.NEW) { throw new IllegalStateException("Object is not new: " + object); } CDOID oldID = object.cdoID(); if (oldID == id) { return; } InternalCDORevision revision = (InternalCDORevision)object.cdoRevision(false); revision.setID(id); InternalCDOTransaction transaction = (InternalCDOTransaction)object.cdoView(); transaction.remapObject(oldID); Map<CDOID, CDOObject> newObjects = transaction.getLastSavepoint().getNewObjects(); newObjects.remove(oldID); newObjects.put(id, object); CDORevision detachedRevision = getDetachedRevision(transaction, id); if (detachedRevision != null) { revision.setVersion(detachedRevision.getVersion()); } } private static CDORevision getDetachedRevision(InternalCDOTransaction transaction, CDOID id) { SyntheticCDORevision[] synthetics = new SyntheticCDORevision[1]; InternalCDORevisionManager revisionManager = transaction.getSession().getRevisionManager(); InternalCDORevision result = revisionManager.getRevision(id, transaction, CDORevision.UNCHUNKED, CDORevision.DEPTH_NONE, true, synthetics); if (result != null) { throw new IllegalStateException("An object with the same id already exists on this branch"); } return synthetics[0]; } private static <T> Set<T> toSet(T... objects) { Set<T> result = null; if (objects.length != 0) { result = new HashSet<T>(); for (T object : objects) { result.add(object); } } return result; } /** * @author Eike Stepper */ private final class CleanRevisionsMap extends HashMap<InternalCDOObject, InternalCDORevision> { private static final long serialVersionUID = 1L; public CleanRevisionsMap() { } @Override public InternalCDORevision get(Object key) { if (key instanceof EObject) { CDOObject cdoObject = CDOUtil.getCDOObject((EObject)key); InternalCDORevision revision = super.get(cdoObject); if (revision != null) { getSession().resolveAllElementProxies(revision); } return revision; } return null; } @Override public InternalCDORevision remove(Object key) { if (key instanceof EObject) { CDOObject cdoObject = CDOUtil.getCDOObject((EObject)key); return super.remove(cdoObject); } return null; } @Override public boolean containsKey(Object key) { if (key instanceof EObject) { CDOObject cdoObject = CDOUtil.getCDOObject((EObject)key); return super.containsKey(cdoObject); } return false; } @Override public Set<InternalCDOObject> keySet() { throw new UnsupportedOperationException(); } } /** * Generates {@link CDOIDTemp temporary} ID values. * * @author Eike Stepper */ private static final class TempIDGenerator implements CDOIDGenerator { private AtomicInteger lastTemporaryID = new AtomicInteger(); public TempIDGenerator() { } public CDOID generateCDOID(EObject object) { return CDOIDUtil.createTempObject(lastTemporaryID.incrementAndGet()); } public void reset() { lastTemporaryID.set(0); } } /** * @author Simon McDuff */ private final class CDOCommitContextImpl implements InternalCDOCommitContext { private InternalCDOTransaction transaction; /** * Tracks whether this commit is *actually* partial or not. (Having tx.committables != null does not in itself mean * that the commit will be partial, because the committables could cover all dirty/new/detached objects. But this * boolean gets set to reflect whether the commit will really commit less than all dirty/new/detached objects.) */ private boolean isPartialCommit; private CDOCommitData commitData; private Map<CDOID, CDOObject> newObjects; private Map<CDOID, CDOObject> detachedObjects; private Map<CDOID, CDORevisionDelta> revisionDeltas; private Map<CDOID, CDOObject> dirtyObjects; private Map<ByteArrayWrapper, CDOLob<?>> lobs = new HashMap<ByteArrayWrapper, CDOLob<?>>(); private List<CDOLockState> locksOnNewObjects = new ArrayList<CDOLockState>(); private List<CDOID> idsToUnlock = new ArrayList<CDOID>(); public CDOCommitContextImpl(InternalCDOTransaction transaction) { this.transaction = transaction; calculateCommitData(); } private void calculateCommitData() { OptionsImpl options = (OptionsImpl)transaction.options(); List<CDOPackageUnit> newPackageUnits = analyzeNewPackages(); newObjects = filterCommittables(transaction.getNewObjects()); List<CDOIDAndVersion> revisions = new ArrayList<CDOIDAndVersion>(newObjects.size()); for (CDOObject newObject : newObjects.values()) { revisions.add(newObject.cdoRevision()); CDOLockState lockState = getLockState(newObject); if (lockState != null) { if (!options.isEffectiveAutoReleaseLock(newObject)) { locksOnNewObjects.add(lockState); } } } revisionDeltas = filterCommittables(transaction.getRevisionDeltas()); List<CDORevisionKey> deltas = new ArrayList<CDORevisionKey>(revisionDeltas.size()); for (CDORevisionDelta delta : revisionDeltas.values()) { deltas.add(delta); } detachedObjects = filterCommittables(transaction.getDetachedObjects()); List<CDOIDAndVersion> detached = new ArrayList<CDOIDAndVersion>(detachedObjects.size()); for (Entry<CDOID, CDOObject> entry : detachedObjects.entrySet()) { CDOObject object = entry.getValue(); InternalCDORevision cleanRevision = cleanRevisions.get(object); if (cleanRevision == null) { // Can happen after merged detachments CDORevision revision = object.cdoRevision(); detached.add(CDOIDUtil.createIDAndVersion(revision)); } else if (cleanRevision.getBranch() == getBranch()) { detached.add(CDOIDUtil.createIDAndVersion(cleanRevision)); } else { detached.add(cleanRevision); } } dirtyObjects = filterCommittables(transaction.getDirtyObjects()); for (CDOObject lockedObject : getLockStates().keySet()) { if (!FSMUtil.isTransient(lockedObject)) { if (options.isEffectiveAutoReleaseLock(lockedObject)) { idsToUnlock.add(lockedObject.cdoID()); } } } commitData = CDOCommitInfoUtil.createCommitData(newPackageUnits, revisions, deltas, detached); } private <T> Map<CDOID, T> filterCommittables(Map<CDOID, T> map) { if (committables == null) { // No partial commit filter -- nothing to do return map; } Map<CDOID, T> newMap = CDOIDUtil.createMap(); for (Entry<CDOID, T> entry : map.entrySet()) { CDOID id = entry.getKey(); CDOObject o = getObject(id); if (committables.contains(o)) { newMap.put(id, entry.getValue()); } else { isPartialCommit = true; } } return newMap; } public String getUserID() { return transaction.getSession().getUserID(); } public int getViewID() { return transaction.getViewID(); } public CDOBranch getBranch() { return transaction.getBranch(); } public InternalCDOTransaction getTransaction() { return transaction; } public boolean isPartialCommit() { return isPartialCommit; } public String getCommitComment() { return transaction.getCommitComment(); } public CDOBranchPoint getCommitMergeSource() { return transaction.getCommitMergeSource(); } public CDOCommitData getCommitData() { return commitData; } public Map<CDOID, CDOObject> getDirtyObjects() { return dirtyObjects; } public Map<CDOID, CDOObject> getNewObjects() { return newObjects; } public List<CDOPackageUnit> getNewPackageUnits() { return commitData.getNewPackageUnits(); } public Map<CDOID, CDOObject> getDetachedObjects() { return detachedObjects; } public Map<CDOID, CDORevisionDelta> getRevisionDeltas() { return revisionDeltas; } public Collection<CDOLob<?>> getLobs() { return lobs.values(); } @Deprecated public boolean isAutoReleaseLocks() { return transaction.options().isAutoReleaseLocksEnabled(); } public Collection<CDOLockState> getLocksOnNewObjects() { return locksOnNewObjects; } public List<CDOID> getIDsToUnlock() { return idsToUnlock; } public void preCommit() { if (isDirty()) { if (TRACER.isEnabled()) { TRACER.trace("commit()"); //$NON-NLS-1$ } CDOTransactionHandler2[] handlers = getTransactionHandlers2(); if (handlers.length != 0) { final boolean[] modifiedAgain = { false }; CDOTransactionHandler1 modifiedAgainHandler = new CDODefaultTransactionHandler1() { @Override public void modifyingObject(CDOTransaction transaction, CDOObject object, CDOFeatureDelta featureChange) { modifiedAgain[0] = true; } }; addTransactionHandler(modifiedAgainHandler); try { for (int i = 0; i < handlers.length; i++) { modifiedAgain[0] = false; CDOTransactionHandler2 handler = handlers[i]; handler.committingTransaction(getTransaction(), this); if (modifiedAgain[0]) { calculateCommitData(); } } } finally { removeTransactionHandler(modifiedAgainHandler); } } try { // TODO (CD) It might be wise to always do the checks, // instead of only for partial commits if (isPartialCommit) { new CommitIntegrityCheck(this, CommitIntegrityCheck.Style.EXCEPTION_FAST).check(); } preCommit(getNewObjects(), lobs); preCommit(getDirtyObjects(), lobs); if (!lobs.isEmpty()) { CDOSessionProtocol sessionProtocol = getSession().getSessionProtocol(); List<byte[]> alreadyKnown = sessionProtocol.queryLobs(ByteArrayWrapper.toByteArray(lobs.keySet())); for (byte[] id : alreadyKnown) { lobs.remove(new ByteArrayWrapper(id)); } } } catch (RuntimeException ex) { throw ex; } catch (Exception ex) { throw new TransactionException(ex); } } } public void postCommit(CommitTransactionResult result) { try { InternalCDOSession session = getSession(); long timeStamp = result.getTimeStamp(); boolean clearResourcePathCache = result.isClearResourcePathCache(); if (result.getRollbackMessage() != null) { InternalCDOCommitInfoManager commitInfoManager = session.getCommitInfoManager(); CDOCommitInfo commitInfo = new FailureCommitInfo(commitInfoManager, timeStamp, result.getPreviousTimeStamp()); InvalidationData invalidationData = new InvalidationData(); invalidationData.setCommitInfo(commitInfo); invalidationData.setSender(transaction); invalidationData.setClearResourcePathCache(clearResourcePathCache); invalidationData.setSecurityImpact(CDOProtocol.CommitNotificationInfo.IMPACT_NONE); invalidationData.setNewPermissions(null); invalidationData.setLockChangeInfo(null); session.invalidate(invalidationData); return; } CDOBranch oldBranch = getBranch(); CDOBranch branch = result.getBranch(); boolean branchChanged = branch != getBranch(); if (branchChanged) { basicSetBranchPoint(branch.getHead()); } for (CDOPackageUnit newPackageUnit : getNewPackageUnits()) { ((InternalCDOPackageUnit)newPackageUnit).setState(CDOPackageUnit.State.LOADED); } Map<CDOID, CDOObject> newObjects = getNewObjects(); postCommit(newObjects, result); Map<CDOID, CDOObject> dirtyObjects = getDirtyObjects(); postCommit(dirtyObjects, result); for (CDORevisionDelta delta : getRevisionDeltas().values()) { ((InternalCDORevisionDelta)delta).adjustReferences(result.getReferenceAdjuster()); } for (CDOID id : getDetachedObjects().keySet()) { removeObject(id); } CDOLockChangeInfo lockChangeInfo = makeUnlockChangeInfo(result); CDOCommitInfo commitInfo = makeCommitInfo(timeStamp, result.getPreviousTimeStamp()); if (!commitInfo.isEmpty()) { InvalidationData invalidationData = new InvalidationData(); invalidationData.setCommitInfo(commitInfo); invalidationData.setSender(transaction); invalidationData.setClearResourcePathCache(clearResourcePathCache); invalidationData.setSecurityImpact(result.getSecurityImpact()); invalidationData.setNewPermissions(result.getNewPermissions()); invalidationData.setLockChangeInfo(lockChangeInfo); session.invalidate(invalidationData); } // Bug 290032 - Sticky views if (session.isSticky()) { CDOBranchPoint commitBranchPoint = CDOBranchUtil.copyBranchPoint(result); // Note: keyset() does not work because ID mappings are not applied there! for (CDOObject object : newObjects.values()) { session.setCommittedSinceLastRefresh(object.cdoID(), commitBranchPoint); } for (CDOID id : dirtyObjects.keySet()) { session.setCommittedSinceLastRefresh(id, commitBranchPoint); } for (CDOID id : getDetachedObjects().keySet()) { session.setCommittedSinceLastRefresh(id, commitBranchPoint); } } CDOTransactionHandler2[] handlers = getTransactionHandlers2(); for (int i = 0; i < handlers.length; i++) { CDOTransactionHandler2 handler = handlers[i]; if (handler instanceof CDOTransactionHandler3) { CDOTransactionHandler3 handler3 = (CDOTransactionHandler3)handler; handler3.committedTransaction(transaction, this, commitInfo); } else { handler.committedTransaction(transaction, this); } } getChangeSubscriptionManager().committedTransaction(transaction, this); getAdapterManager().committedTransaction(transaction, this); cleanUp(this); IListener[] listeners = getListeners(); if (listeners != null) { if (branchChanged) { fireViewTargetChangedEvent(oldBranch.getHead(), listeners); } Map<CDOID, CDOID> idMappings = result.getIDMappings(); fireEvent(new FinishedEvent(idMappings), listeners); } if (lockChangeInfo != null && isActive()) { fireLocksChangedEvent(CDOTransactionImpl.this, lockChangeInfo); } } catch (RuntimeException ex) { throw ex; } catch (Exception ex) { throw new TransactionException(ex); } } private CDOCommitInfo makeCommitInfo(long timeStamp, long previousTimeStamp) { InternalCDOSession session = getSession(); CDOBranch branch = getBranch(); String userID = session.getUserID(); String comment = getCommitComment(); CDOBranchPoint mergeSource = getCommitMergeSource(); InternalCDOCommitInfoManager commitInfoManager = session.getCommitInfoManager(); return commitInfoManager.createCommitInfo(branch, timeStamp, previousTimeStamp, userID, comment, mergeSource, commitData); } private CDOLockChangeInfo makeUnlockChangeInfo(CommitTransactionResult result) { Map<CDOObject, CDOLockState> lockStates = getLockStates(); if (lockStates.isEmpty()) { return null; } List<CDOLockState> objectsToUnlock = new ArrayList<CDOLockState>(); for (Map.Entry<CDOObject, CDOLockState> entry : lockStates.entrySet()) { CDOObject object = entry.getKey(); if (options().isEffectiveAutoReleaseLock(object)) { InternalCDOLockState lockState = (InternalCDOLockState)entry.getValue(); lockState.updateFrom(InternalCDOLockState.UNLOCKED); objectsToUnlock.add(lockState); } } CDOLockState[] newLockStates = objectsToUnlock.toArray(new CDOLockState[objectsToUnlock.size()]); return makeLockChangeInfo(CDOLockChangeInfo.Operation.UNLOCK, null, result.getTimeStamp(), newLockStates); } private void preCommit(Map<CDOID, CDOObject> objects, Map<ByteArrayWrapper, CDOLob<?>> lobs) { if (!objects.isEmpty()) { for (CDOObject object : objects.values()) { collectLobs((InternalCDORevision)object.cdoRevision(), lobs); ((InternalCDOObject)object).cdoInternalPreCommit(); } } } private void collectLobs(InternalCDORevision revision, Map<ByteArrayWrapper, CDOLob<?>> lobs) { EStructuralFeature[] features = revision.getClassInfo().getAllPersistentFeatures(); for (int i = 0; i < features.length; i++) { EStructuralFeature feature = features[i]; if (CDOModelUtil.isLob(feature.getEType())) { CDOLob<?> lob = (CDOLob<?>)revision.getValue(feature); if (lob != null) { lobs.put(new ByteArrayWrapper(lob.getID()), lob); } } } } private void postCommit(Map<CDOID, CDOObject> objects, CommitTransactionResult result) { if (!objects.isEmpty()) { CDOStateMachine.INSTANCE.commit(objects, result); } } } /** * @author Eike Stepper */ private final class StartedEvent extends Event implements CDOTransactionStartedEvent { private static final long serialVersionUID = 1L; private StartedEvent() { } @Override public String toString() { return MessageFormat.format("CDOTransactionStartedEvent[source={0}]", getSource()); //$NON-NLS-1$ } } /** * @author Eike Stepper */ private final class FinishedEvent extends Event implements CDOTransactionFinishedEvent { private static final long serialVersionUID = 1L; private final Cause cause; private final Map<CDOID, CDOID> idMappings; private FinishedEvent(Map<CDOID, CDOID> idMappings) { cause = Cause.COMMITTED; this.idMappings = idMappings; } private FinishedEvent(boolean rolledBack) { cause = rolledBack ? Cause.ROLLED_BACK : Cause.UNDONE; idMappings = Collections.emptyMap(); } @Deprecated public Type getType() { switch (cause) { case COMMITTED: return Type.COMMITTED; case ROLLED_BACK: case UNDONE: return Type.ROLLED_BACK; default: throw new IllegalStateException("Illegal cause: " + cause); } } public Cause getCause() { return cause; } public Map<CDOID, CDOID> getIDMappings() { return idMappings; } @Override public String toString() { return MessageFormat.format("CDOTransactionFinishedEvent[source={0}, cause={1}, idMappings={2}]", getSource(), //$NON-NLS-1$ getCause(), idMappings == null ? 0 : idMappings.size()); } } /** * @author Eike Stepper */ private final class ConflictEvent extends Event implements CDOTransactionConflictEvent { private static final long serialVersionUID = 1L; private InternalCDOObject conflictingObject; private boolean firstConflict; public ConflictEvent(InternalCDOObject conflictingObject, boolean firstConflict) { this.conflictingObject = conflictingObject; this.firstConflict = firstConflict; } public InternalCDOObject getConflictingObject() { return conflictingObject; } public boolean isFirstConflict() { return firstConflict; } @Override public String toString() { return MessageFormat.format("CDOTransactionConflictEvent[source={0}, conflictingObject={1}, firstConflict={2}]", //$NON-NLS-1$ getSource(), getConflictingObject(), isFirstConflict()); } } /** * @author Eike Stepper * @since 2.0 */ protected final class OptionsImpl extends CDOViewImpl.OptionsImpl implements CDOTransaction.Options { private CDOUndoDetector undoDetector = DEFAULT_UNDO_DETECTOR; private final List<CDOConflictResolver> conflictResolvers = new ArrayList<CDOConflictResolver>(); private CDOStaleReferenceCleaner staleReferenceCleaner = CDOStaleReferenceCleaner.DEFAULT; private boolean autoReleaseLocksEnabled = true; private final Map<EObject, Boolean> autoReleaseLocksExemptions = new WeakHashMap<EObject, Boolean>(); private long commitInfoTimeout = DEFAULT_COMMIT_INFO_TIMEOUT; public OptionsImpl() { } public CDOUndoDetector getUndoDetector() { return undoDetector; } public void setUndoDetector(CDOUndoDetector undoDetector) { checkActive(); if (undoDetector == null) { undoDetector = DEFAULT_UNDO_DETECTOR; } IEvent event = null; synchronized (getViewMonitor()) { lockView(); try { if (this.undoDetector != undoDetector) { this.undoDetector = undoDetector; event = new UndoDetectorEventImpl(); } } finally { unlockView(); } } fireEvent(event); } @Override public CDOTransactionImpl getContainer() { return (CDOTransactionImpl)super.getContainer(); } public CDOConflictResolver[] getConflictResolvers() { synchronized (getViewMonitor()) { lockView(); try { return conflictResolvers.toArray(new CDOConflictResolver[conflictResolvers.size()]); } finally { unlockView(); } } } public void setConflictResolvers(CDOConflictResolver[] resolvers) { checkActive(); synchronized (getViewMonitor()) { lockView(); try { for (CDOConflictResolver resolver : conflictResolvers) { resolver.setTransaction(null); } conflictResolvers.clear(); for (CDOConflictResolver resolver : resolvers) { validateResolver(resolver); conflictResolvers.add(resolver); } } finally { unlockView(); } } fireEvent(new ConflictResolversEventImpl()); } public void addConflictResolver(CDOConflictResolver resolver) { checkActive(); IEvent event = null; synchronized (getViewMonitor()) { lockView(); try { validateResolver(resolver); conflictResolvers.add(resolver); event = new ConflictResolversEventImpl(); } finally { unlockView(); } } fireEvent(event); } public void removeConflictResolver(CDOConflictResolver resolver) { checkActive(); IEvent event = null; synchronized (getViewMonitor()) { lockView(); try { if (conflictResolvers.remove(resolver)) { resolver.setTransaction(null); event = new ConflictResolversEventImpl(); } } finally { unlockView(); } } fireEvent(event); } public CDOStaleReferenceCleaner getStaleReferenceCleaner() { return staleReferenceCleaner; } public void setStaleReferenceCleaner(CDOStaleReferenceCleaner staleReferenceCleaner) { checkActive(); if (staleReferenceCleaner == null) { staleReferenceCleaner = CDOStaleReferenceCleaner.DEFAULT; } IEvent event = null; synchronized (getViewMonitor()) { lockView(); try { if (this.staleReferenceCleaner != staleReferenceCleaner) { this.staleReferenceCleaner = staleReferenceCleaner; event = new StaleReferenceCleanerEventImpl(); } } finally { unlockView(); } } fireEvent(event); } public void disposeConflictResolvers() { try { // Do not call getConflictResolvers() because that method may block! CDOConflictResolver[] array = conflictResolvers.toArray(new CDOConflictResolver[conflictResolvers.size()]); for (CDOConflictResolver resolver : array) { try { resolver.setTransaction(null); } catch (Exception ignore) { } } } catch (Exception ignore) { } } private void validateResolver(CDOConflictResolver resolver) { if (resolver.getTransaction() != null) { throw new IllegalArgumentException(Messages.getString("CDOTransactionImpl.17")); //$NON-NLS-1$ } resolver.setTransaction(CDOTransactionImpl.this); } public boolean isAutoReleaseLocksEnabled() { return autoReleaseLocksEnabled; } public void setAutoReleaseLocksEnabled(boolean on) { checkActive(); IEvent event = null; synchronized (getViewMonitor()) { lockView(); try { if (autoReleaseLocksEnabled != on) { autoReleaseLocksEnabled = on; event = new AutoReleaseLocksEnabledEventImpl(); } } finally { unlockView(); } } fireEvent(event); } public Set<? extends EObject> getAutoReleaseLocksExemptions() { synchronized (getViewMonitor()) { lockView(); try { return new HashSet<EObject>(autoReleaseLocksExemptions.keySet()); } finally { unlockView(); } } } public boolean isAutoReleaseLocksExemption(EObject object) { synchronized (getViewMonitor()) { lockView(); try { return autoReleaseLocksExemptions.get(CDOUtil.getCDOObject(object)) == Boolean.TRUE; } finally { unlockView(); } } } public void clearAutoReleaseLocksExemptions() { checkActive(); IEvent event = null; synchronized (getViewMonitor()) { lockView(); try { if (!autoReleaseLocksExemptions.isEmpty()) { autoReleaseLocksExemptions.clear(); event = new AutoReleaseLocksExemptionsEventImpl(); } } finally { unlockView(); } } fireEvent(event); } public void addAutoReleaseLocksExemptions(boolean recursive, EObject... objects) { checkActive(); IEvent event = null; synchronized (getViewMonitor()) { lockView(); try { for (EObject object : objects) { if (autoReleaseLocksExemptions.put(CDOUtil.getCDOObject(object), Boolean.TRUE) == null) { event = new AutoReleaseLocksExemptionsEventImpl(); } if (recursive) { for (TreeIterator<EObject> it = object.eAllContents(); it.hasNext();) { EObject child = it.next(); if (autoReleaseLocksExemptions.put(CDOUtil.getCDOObject(child), Boolean.TRUE) == null && event == null) { event = new AutoReleaseLocksExemptionsEventImpl(); } } } } } finally { unlockView(); } } fireEvent(event); } public void removeAutoReleaseLocksExemptions(boolean recursive, EObject... objects) { checkActive(); IEvent event = null; synchronized (getViewMonitor()) { lockView(); try { for (EObject object : objects) { if (autoReleaseLocksExemptions.remove(CDOUtil.getCDOObject(object)) != null) { event = new AutoReleaseLocksExemptionsEventImpl(); } if (recursive) { for (TreeIterator<EObject> it = object.eAllContents(); it.hasNext();) { EObject child = it.next(); if (autoReleaseLocksExemptions.remove(CDOUtil.getCDOObject(child)) != null && event == null) { event = new AutoReleaseLocksExemptionsEventImpl(); } } } } } finally { unlockView(); } } fireEvent(event); } public boolean isEffectiveAutoReleaseLock(CDOObject newObject) { boolean effectiveAutoReleaseLock = autoReleaseLocksEnabled; if (autoReleaseLocksExemptions.containsKey(newObject)) { effectiveAutoReleaseLock = !effectiveAutoReleaseLock; } return effectiveAutoReleaseLock; } public long getCommitInfoTimeout() { return commitInfoTimeout; } public void setCommitInfoTimeout(long commitInfoTimeout) { checkActive(); IEvent event = null; synchronized (getViewMonitor()) { lockView(); try { if (this.commitInfoTimeout != commitInfoTimeout) { this.commitInfoTimeout = commitInfoTimeout; event = new CommitInfoTimeoutImpl(); } } finally { unlockView(); } } fireEvent(event); } /** * @author Eike Stepper */ private final class UndoDetectorEventImpl extends OptionsEvent implements UndoDetectorEvent { private static final long serialVersionUID = 1L; public UndoDetectorEventImpl() { super(OptionsImpl.this); } } /** * @author Eike Stepper */ private final class ConflictResolversEventImpl extends OptionsEvent implements ConflictResolversEvent { private static final long serialVersionUID = 1L; public ConflictResolversEventImpl() { super(OptionsImpl.this); } } /** * @author Eike Stepper */ private final class StaleReferenceCleanerEventImpl extends OptionsEvent implements StaleReferenceCleanerEvent { private static final long serialVersionUID = 1L; public StaleReferenceCleanerEventImpl() { super(OptionsImpl.this); } } /** * @author Eike Stepper */ private final class AutoReleaseLocksEnabledEventImpl extends OptionsEvent implements AutoReleaseLocksEnabledEvent { private static final long serialVersionUID = 1L; public AutoReleaseLocksEnabledEventImpl() { super(OptionsImpl.this); } } /** * @author Eike Stepper */ private final class AutoReleaseLocksExemptionsEventImpl extends OptionsEvent implements AutoReleaseLocksExemptionsEvent { private static final long serialVersionUID = 1L; public AutoReleaseLocksExemptionsEventImpl() { super(OptionsImpl.this); } } /** * @author Eike Stepper */ private final class CommitInfoTimeoutImpl extends OptionsEvent implements CommitInfoTimeout { private static final long serialVersionUID = 1L; public CommitInfoTimeoutImpl() { super(OptionsImpl.this); } } } }