diff options
Diffstat (limited to 'plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction')
8 files changed, 3061 insertions, 0 deletions
diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOAbstractSavepoint.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOAbstractSavepoint.java new file mode 100644 index 0000000000..da1bb7e5c3 --- /dev/null +++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOAbstractSavepoint.java @@ -0,0 +1,87 @@ +/*************************************************************************** + * Copyright (c) 2004 - 2008 Eike Stepper, Germany. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Simon McDuff - initial API and implementation + * Eike Stepper - maintenance + **************************************************************************/ +package org.eclipse.emf.internal.cdo.transaction; + +import org.eclipse.emf.cdo.transaction.CDOSavepoint; +import org.eclipse.emf.cdo.transaction.CDOUserTransaction; + +/** + * @author Simon McDuff + * @since 2.0 + */ +public abstract class CDOAbstractSavepoint implements CDOSavepoint +{ + private CDOUserTransaction userTransaction; + + private CDOAbstractSavepoint previousSavepoint; + + private CDOAbstractSavepoint nextSavepoint; + + public CDOAbstractSavepoint(CDOUserTransaction transaction, CDOAbstractSavepoint lastSavepoint) + { + userTransaction = transaction; + previousSavepoint = lastSavepoint; + if (previousSavepoint != null) + { + previousSavepoint.setNextSavepoint(this); + } + } + + public void setPreviousSavepoint(CDOAbstractSavepoint previousSavepoint) + { + this.previousSavepoint = previousSavepoint; + } + + public void setNextSavepoint(CDOAbstractSavepoint nextSavepoint) + { + this.nextSavepoint = nextSavepoint; + } + + public CDOSavepoint getNextSavepoint() + { + return nextSavepoint; + } + + public CDOSavepoint getPreviousSavepoint() + { + return previousSavepoint; + } + + public CDOAbstractSavepoint getFirstSavePoint() + { + return previousSavepoint != null ? previousSavepoint.getFirstSavePoint() : this; + } + + public CDOUserTransaction getUserTransaction() + { + return userTransaction; + } + + public boolean isValid() + { + CDOSavepoint lastSavepoint = getUserTransaction().getLastSavepoint(); + for (CDOSavepoint savepoint = lastSavepoint; savepoint != null; savepoint = savepoint.getPreviousSavepoint()) + { + if (savepoint == this) + { + return true; + } + } + + return false; + } + + public void rollback() + { + getUserTransaction().rollback(this); + } +} diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOSavepointImpl.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOSavepointImpl.java new file mode 100644 index 0000000000..f1edf03e9f --- /dev/null +++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOSavepointImpl.java @@ -0,0 +1,356 @@ +/*************************************************************************** + * Copyright (c) 2004 - 2008 Eike Stepper, Germany. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Simon McDuff - initial API and implementation + * Eike Stepper - maintenance + * Simon McDuff - http://bugs.eclipse.org/204890 + **************************************************************************/ +package org.eclipse.emf.internal.cdo.transaction; + +import org.eclipse.emf.cdo.CDOObject; +import org.eclipse.emf.cdo.common.id.CDOID; +import org.eclipse.emf.cdo.common.revision.CDORevision; +import org.eclipse.emf.cdo.common.revision.delta.CDOFeatureDelta; +import org.eclipse.emf.cdo.common.revision.delta.CDORevisionDelta; +import org.eclipse.emf.cdo.common.revision.delta.CDORevisionDeltaUtil; +import org.eclipse.emf.cdo.eresource.CDOResource; +import org.eclipse.emf.cdo.internal.common.revision.delta.CDORevisionDeltaImpl; +import org.eclipse.emf.cdo.internal.common.revision.delta.InternalCDOFeatureDelta; + +import org.eclipse.net4j.util.collection.MultiMap; + +import org.eclipse.emf.spi.cdo.InternalCDOTransaction; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * @author Simon McDuff + * @since 2.0 + */ +public class CDOSavepointImpl extends CDOAbstractSavepoint +{ + private Map<CDOID, CDOResource> newResources = new HashMap<CDOID, CDOResource>(); + + private Map<CDOID, CDOObject> newObjects = new HashMap<CDOID, CDOObject>(); + + private Map<CDOID, CDORevision> baseNewObjects = new HashMap<CDOID, CDORevision>(); + + private Map<CDOID, CDOObject> dirtyObjects = new HashMap<CDOID, CDOObject>(); + + private Map<CDOID, CDOObject> detachedObjects = new HashMap<CDOID, CDOObject>() + { + private static final long serialVersionUID = 1L; + + @Override + public CDOObject put(CDOID key, CDOObject object) + { + sharedDetachedObjects.add(key); + dirtyObjects.remove(key); + baseNewObjects.remove(key); + newObjects.remove(key); + newResources.remove(key); + revisionDeltas.remove(key); + return super.put(key, object); + } + }; + + private ConcurrentMap<CDOID, CDORevisionDelta> revisionDeltas = new ConcurrentHashMap<CDOID, CDORevisionDelta>(); + + /** + * Contains all persistent CDOIDs that were removed. The same instance is shared between all save points that belong + * to the same transaction. + */ + private Set<CDOID> sharedDetachedObjects; + + private boolean isDirty; + + public CDOSavepointImpl(InternalCDOTransaction transaction, CDOSavepointImpl lastSavepoint) + { + super(transaction, lastSavepoint); + isDirty = transaction.isDirty(); + if (getPreviousSavepoint() == null) + { + sharedDetachedObjects = new HashSet<CDOID>(); + } + else + { + sharedDetachedObjects = lastSavepoint.getSharedDetachedObjects(); + } + } + + public void clear() + { + newResources.clear(); + newObjects.clear(); + dirtyObjects.clear(); + revisionDeltas.clear(); + baseNewObjects.clear(); + detachedObjects.clear(); + } + + public boolean isDirty() + { + return isDirty; + } + + public Map<CDOID, CDOResource> getNewResources() + { + return newResources; + } + + public Map<CDOID, CDOObject> getNewObjects() + { + return newObjects; + } + + public Map<CDOID, CDOObject> getDetachedObjects() + { + return detachedObjects; + } + + public Map<CDOID, CDOObject> getDirtyObjects() + { + return dirtyObjects; + } + + public Set<CDOID> getSharedDetachedObjects() + { + return sharedDetachedObjects; + } + + public ConcurrentMap<CDOID, CDORevisionDelta> getRevisionDeltas() + { + return revisionDeltas; + } + + public Map<CDOID, CDORevision> getBaseNewObjects() + { + return baseNewObjects; + } + + /** + * Return the list of new objects from this point. + */ + public Map<CDOID, CDOObject> getAllDirtyObjects() + { + if (getPreviousSavepoint() == null) + { + return getDirtyObjects(); + } + + MultiMap.ListBased<CDOID, CDOObject> dirtyObjects = new MultiMap.ListBased<CDOID, CDOObject>(); + for (CDOSavepointImpl savepoint = this; savepoint != null; savepoint = savepoint.getPreviousSavepoint()) + { + dirtyObjects.getDelegates().add(savepoint.getDirtyObjects()); + } + + return dirtyObjects; + } + + /** + * Return the list of new objects from this point without objects that are removed. + */ + public Map<CDOID, CDOObject> getAllNewObjects() + { + if (getPreviousSavepoint() == null) + { + return Collections.unmodifiableMap(getNewObjects()); + } + + if (getSharedDetachedObjects().size() == 0) + { + MultiMap.ListBased<CDOID, CDOObject> newObjects = new MultiMap.ListBased<CDOID, CDOObject>(); + for (CDOSavepointImpl savepoint = this; savepoint != null; savepoint = savepoint.getPreviousSavepoint()) + { + newObjects.getDelegates().add(savepoint.getNewObjects()); + } + + return newObjects; + } + + Map<CDOID, CDOObject> newObjects = new HashMap<CDOID, CDOObject>(); + for (CDOSavepointImpl savepoint = this; savepoint != null; savepoint = savepoint.getPreviousSavepoint()) + { + for (Entry<CDOID, CDOObject> entry : savepoint.getNewObjects().entrySet()) + { + if (!getSharedDetachedObjects().contains(entry.getKey())) + { + newObjects.put(entry.getKey(), entry.getValue()); + } + } + } + + return newObjects; + } + + /** + * Return the list of new resources from this point without objects that are removed. + */ + public Map<CDOID, CDOResource> getAllNewResources() + { + if (getPreviousSavepoint() == null) + { + return Collections.unmodifiableMap(getNewResources()); + } + + if (getSharedDetachedObjects().size() == 0) + { + MultiMap.ListBased<CDOID, CDOResource> newResources = new MultiMap.ListBased<CDOID, CDOResource>(); + for (CDOSavepointImpl savepoint = this; savepoint != null; savepoint = savepoint.getPreviousSavepoint()) + { + newResources.getDelegates().add(savepoint.getNewResources()); + } + + return newResources; + } + + Map<CDOID, CDOResource> newResources = new HashMap<CDOID, CDOResource>(); + for (CDOSavepointImpl savepoint = this; savepoint != null; savepoint = savepoint.getPreviousSavepoint()) + { + for (Entry<CDOID, CDOResource> entry : savepoint.getNewResources().entrySet()) + { + if (!getSharedDetachedObjects().contains(entry.getKey())) + { + newResources.put(entry.getKey(), entry.getValue()); + } + } + } + + return newResources; + } + + /** + * @since 2.0 + */ + public Map<CDOID, CDORevision> getAllBaseNewObjects() + { + if (getPreviousSavepoint() == null) + { + return Collections.unmodifiableMap(getBaseNewObjects()); + } + + MultiMap.ListBased<CDOID, CDORevision> newObjects = new MultiMap.ListBased<CDOID, CDORevision>(); + for (CDOSavepointImpl savepoint = this; savepoint != null; savepoint = savepoint.getPreviousSavepoint()) + { + newObjects.getDelegates().add(savepoint.getBaseNewObjects()); + } + + return newObjects; + } + + /** + * Return the list of all deltas without objects that are removed. + */ + public Map<CDOID, CDORevisionDelta> getAllRevisionDeltas() + { + if (getPreviousSavepoint() == null) + { + return Collections.unmodifiableMap(getRevisionDeltas()); + } + + // We need to combined the result for all delta in different Savepoint + Map<CDOID, CDORevisionDelta> revisionDeltas = new HashMap<CDOID, CDORevisionDelta>(); + for (CDOSavepointImpl savepoint = (CDOSavepointImpl)getFirstSavePoint(); savepoint != null; savepoint = savepoint + .getNextSavepoint()) + { + for (Entry<CDOID, CDORevisionDelta> entry : savepoint.getRevisionDeltas().entrySet()) + { + // Skipping temporary + if (entry.getKey().isTemporary() || getSharedDetachedObjects().contains(entry.getKey())) + { + continue; + } + + CDORevisionDeltaImpl revisionDelta = (CDORevisionDeltaImpl)revisionDeltas.get(entry.getKey()); + if (revisionDelta == null) + { + revisionDeltas.put(entry.getKey(), CDORevisionDeltaUtil.copy(entry.getValue())); + } + else + { + + for (CDOFeatureDelta delta : entry.getValue().getFeatureDeltas()) + { + revisionDelta.addFeatureDelta(((InternalCDOFeatureDelta)delta).copy()); + } + } + } + } + + return Collections.unmodifiableMap(revisionDeltas); + } + + public Map<CDOID, CDOObject> getAllDetachedObjects() + { + if (getPreviousSavepoint() == null) + { + return Collections.unmodifiableMap(getDetachedObjects()); + } + + Map<CDOID, CDOObject> detachedObjects = new HashMap<CDOID, CDOObject>(); + for (CDOSavepointImpl savepoint = this; savepoint != null; savepoint = savepoint.getPreviousSavepoint()) + { + for (Entry<CDOID, CDOObject> entry : savepoint.getDetachedObjects().entrySet()) + { + if (!entry.getKey().isTemporary()) + { + detachedObjects.put(entry.getKey(), entry.getValue()); + } + } + } + + return detachedObjects; + } + + public void recalculateSharedDetachedObjects() + { + sharedDetachedObjects.clear(); + for (CDOSavepointImpl savepoint = this; savepoint != null; savepoint = savepoint.getPreviousSavepoint()) + { + for (CDOID id : savepoint.getDetachedObjects().keySet()) + { + sharedDetachedObjects.add(id); + } + } + } + + @Override + public CDOSavepointImpl getPreviousSavepoint() + { + return (CDOSavepointImpl)super.getPreviousSavepoint(); + } + + @Override + public CDOSavepointImpl getNextSavepoint() + { + return (CDOSavepointImpl)super.getNextSavepoint(); + } + + public void setPreviousSavepoint(CDOSavepointImpl previousSavepoint) + { + super.setPreviousSavepoint(previousSavepoint); + } + + public void setNextSavepoint(CDOSavepointImpl nextSavepoint) + { + super.setNextSavepoint(nextSavepoint); + } + + @Override + public void rollback() + { + getUserTransaction().rollback(this); + } +} diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOSingleTransactionStrategy.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOSingleTransactionStrategy.java new file mode 100644 index 0000000000..5f31a51ca1 --- /dev/null +++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOSingleTransactionStrategy.java @@ -0,0 +1,96 @@ +/*************************************************************************** + * Copyright (c) 2004 - 2008 Eike Stepper, Germany. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Simon McDuff - initial API and implementation + **************************************************************************/ +package org.eclipse.emf.internal.cdo.transaction; + +import org.eclipse.emf.cdo.transaction.CDOSavepoint; + +import org.eclipse.emf.internal.cdo.bundle.OM; +import org.eclipse.emf.internal.cdo.protocol.CDOClientProtocol; +import org.eclipse.emf.internal.cdo.protocol.CommitTransactionRequest; +import org.eclipse.emf.internal.cdo.protocol.CommitTransactionResult; + +import org.eclipse.net4j.util.om.monitor.EclipseMonitor; +import org.eclipse.net4j.util.om.trace.ContextTracer; +import org.eclipse.net4j.util.transaction.TransactionException; + +import org.eclipse.emf.spi.cdo.CDOTransactionStrategy; +import org.eclipse.emf.spi.cdo.InternalCDOCommitContext; +import org.eclipse.emf.spi.cdo.InternalCDOTransaction; + +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * @author Simon McDuff + * @since 2.0 + */ +public class CDOSingleTransactionStrategy implements CDOTransactionStrategy +{ + public static final CDOSingleTransactionStrategy INSTANCE = new CDOSingleTransactionStrategy(); + + private static final ContextTracer TRACER = new ContextTracer(OM.DEBUG_TRANSCTION, CDOSingleTransactionStrategy.class); + + public CDOSingleTransactionStrategy() + { + } + + public void commit(InternalCDOTransaction transaction, IProgressMonitor progressMonitor) throws Exception + { + InternalCDOCommitContext commitContext = transaction.createCommitContext(); + if (TRACER.isEnabled()) + { + TRACER.format("CDOCommitContext.preCommit"); + } + + commitContext.preCommit(); + CommitTransactionResult result = null; + if (commitContext.getTransaction().isDirty()) + { + CDOClientProtocol protocol = (CDOClientProtocol)transaction.getSession().getProtocol(); + CommitTransactionRequest request = new CommitTransactionRequest(protocol, commitContext); + if (TRACER.isEnabled()) + { + TRACER.format("Sending commit request"); + } + + result = request.send(new EclipseMonitor(progressMonitor)); + String rollbackMessage = result.getRollbackMessage(); + if (rollbackMessage != null) + { + throw new TransactionException(rollbackMessage); + } + } + if (TRACER.isEnabled()) + { + TRACER.format("CDOCommitContext.postCommit"); + } + commitContext.postCommit(result); + } + + public void rollback(InternalCDOTransaction transaction, CDOSavepoint savepoint) + { + transaction.handleRollback(savepoint); + } + + public CDOSavepoint setSavepoint(InternalCDOTransaction transaction) + { + return transaction.handleSetSavepoint(); + } + + public void setTarget(InternalCDOTransaction transaction) + { + // Do nothing + } + + public void unsetTarget(InternalCDOTransaction transaction) + { + // Do nothing + } +} diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOTimeStampContextImpl.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOTimeStampContextImpl.java new file mode 100644 index 0000000000..ee2568606e --- /dev/null +++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOTimeStampContextImpl.java @@ -0,0 +1,64 @@ +/*************************************************************************** + * Copyright (c) 2004 - 2008 Eike Stepper, Germany. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Simon McDuff - initial API and implementation + * Eike Stepper - maintenance + **************************************************************************/ +package org.eclipse.emf.internal.cdo.transaction; + +import org.eclipse.emf.cdo.common.id.CDOID; +import org.eclipse.emf.cdo.common.id.CDOIDAndVersion; +import org.eclipse.emf.cdo.transaction.CDOTimeStampContext; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +/** + * @author Simon McDuff + * @since 2.0 + */ +public class CDOTimeStampContextImpl implements CDOTimeStampContext +{ + private long timeStamp; + + private Set<CDOIDAndVersion> dirtyObjects = new HashSet<CDOIDAndVersion>(); + + private Collection<CDOID> detachedObjects = new ArrayList<CDOID>(); + + public CDOTimeStampContextImpl(long timeStamp) + { + this.timeStamp = timeStamp; + } + + public long getTimeStamp() + { + return timeStamp; + } + + public Set<CDOIDAndVersion> getDirtyObjects() + { + return dirtyObjects; + } + + public void setDirtyObjects(Set<CDOIDAndVersion> dirtyObjects) + { + this.dirtyObjects = dirtyObjects; + } + + public Collection<CDOID> getDetachedObjects() + { + return detachedObjects; + } + + public void setDetachedObjects(Collection<CDOID> detachedObjects) + { + this.detachedObjects = detachedObjects; + } +} diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOTransactionImpl.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOTransactionImpl.java new file mode 100644 index 0000000000..3ba95c7a30 --- /dev/null +++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOTransactionImpl.java @@ -0,0 +1,1613 @@ +/*************************************************************************** + * Copyright (c) 2004 - 2008 Eike Stepper, Germany. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Eike Stepper - initial API and implementation + * Simon McDuff - maintenance + * Victor Roldan Betancort - maintenance + **************************************************************************/ +package org.eclipse.emf.internal.cdo.transaction; + +import org.eclipse.emf.cdo.CDOObject; +import org.eclipse.emf.cdo.CDOState; +import org.eclipse.emf.cdo.common.id.CDOID; +import org.eclipse.emf.cdo.common.id.CDOIDAndVersion; +import org.eclipse.emf.cdo.common.id.CDOIDTemp; +import org.eclipse.emf.cdo.common.id.CDOIDUtil; +import org.eclipse.emf.cdo.common.model.CDOPackage; +import org.eclipse.emf.cdo.common.revision.CDORevision; +import org.eclipse.emf.cdo.common.revision.delta.CDOFeatureDelta; +import org.eclipse.emf.cdo.common.revision.delta.CDORevisionDelta; +import org.eclipse.emf.cdo.common.revision.delta.CDORevisionDeltaUtil; +import org.eclipse.emf.cdo.common.util.CDOException; +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.EresourceFactory; +import org.eclipse.emf.cdo.eresource.impl.CDOResourceImpl; +import org.eclipse.emf.cdo.eresource.impl.CDOResourceNodeImpl; +import org.eclipse.emf.cdo.spi.common.model.InternalCDOPackage; +import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevisionDelta; +import org.eclipse.emf.cdo.transaction.CDOSavepoint; +import org.eclipse.emf.cdo.transaction.CDOTransaction; +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.CDOTransactionStartedEvent; +import org.eclipse.emf.cdo.util.CDOURIUtil; +import org.eclipse.emf.cdo.view.CDOConflictResolver; +import org.eclipse.emf.cdo.view.CDOViewResourcesEvent; + +import org.eclipse.emf.internal.cdo.CDOObjectMerger; +import org.eclipse.emf.internal.cdo.CDOStateMachine; +import org.eclipse.emf.internal.cdo.bundle.OM; +import org.eclipse.emf.internal.cdo.protocol.CommitTransactionResult; +import org.eclipse.emf.internal.cdo.session.CDOSessionPackageManagerImpl; +import org.eclipse.emf.internal.cdo.util.CompletePackageClosure; +import org.eclipse.emf.internal.cdo.util.FSMUtil; +import org.eclipse.emf.internal.cdo.util.IPackageClosure; +import org.eclipse.emf.internal.cdo.util.ModelUtil; +import org.eclipse.emf.internal.cdo.view.CDOViewImpl; + +import org.eclipse.net4j.util.ImplementationError; +import org.eclipse.net4j.util.ObjectUtil; +import org.eclipse.net4j.util.WrappedException; +import org.eclipse.net4j.util.event.Notifier; +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.util.URI; +import org.eclipse.emf.ecore.EPackage; +import org.eclipse.emf.spi.cdo.CDOTransactionStrategy; +import org.eclipse.emf.spi.cdo.InternalCDOCommitContext; +import org.eclipse.emf.spi.cdo.InternalCDOObject; +import org.eclipse.emf.spi.cdo.InternalCDOSession; +import org.eclipse.emf.spi.cdo.InternalCDOTransaction; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.NullProgressMonitor; + +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; + +/** + * @author Eike Stepper + */ +public class CDOTransactionImpl extends CDOViewImpl implements InternalCDOTransaction +{ + private static final ContextTracer TRACER = new ContextTracer(OM.DEBUG_TRANSCTION, CDOTransactionImpl.class); + + /** + * TODO Optimize by storing an array. See {@link Notifier}. + */ + private List<CDOTransactionHandler> handlers = new ArrayList<CDOTransactionHandler>(0); + + private CDOSavepointImpl lastSavepoint = new CDOSavepointImpl(this, null); + + private CDOSavepointImpl firstSavepoint = lastSavepoint; + + private boolean dirty; + + private int conflict; + + private long lastCommitTime = CDORevision.UNSPECIFIED_DATE; + + private int lastTemporaryID; + + private CDOTransactionStrategy transactionStrategy; + + /** + * @since 2.0 + */ + public CDOTransactionImpl() + { + } + + /** + * @since 2.0 + */ + @Override + public OptionsImpl options() + { + return (OptionsImpl)super.options(); + } + + /** + * @since 2.0 + */ + @Override + protected OptionsImpl initOptions() + { + return new OptionsImpl(); + } + + @Override + public Type getViewType() + { + return Type.TRANSACTION; + } + + public void addHandler(CDOTransactionHandler handler) + { + synchronized (handlers) + { + if (!handlers.contains(handler)) + { + handlers.add(handler); + } + } + } + + public void removeHandler(CDOTransactionHandler handler) + { + synchronized (handlers) + { + handlers.remove(handler); + } + } + + public CDOTransactionHandler[] getHandlers() + { + synchronized (handlers) + { + return handlers.toArray(new CDOTransactionHandler[handlers.size()]); + } + } + + @Override + public boolean isDirty() + { + if (isClosed()) + { + return false; + } + + return dirty; + } + + @Override + public boolean hasConflict() + { + checkActive(); + return conflict != 0; + } + + public void setConflict(InternalCDOObject object) + { + ConflictEvent event = new ConflictEvent(object, conflict == 0); + ++conflict; + fireEvent(event); + } + + /** + * @since 2.0 + */ + public Set<CDOObject> getConflicts() + { + 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; + } + + /** + * @since 2.0 + */ + public void resolveConflicts(CDOConflictResolver... resolvers) + { + Set<CDOObject> conflicts = getConflicts(); + handleConflicts(conflicts, resolvers); + } + + public void handleConflicts(Set<CDOObject> conflicts) + { + handleConflicts(conflicts, options().getConflictResolvers()); + } + + private void handleConflicts(Set<CDOObject> conflicts, CDOConflictResolver[] resolvers) + { + if (resolvers.length == 0) + { + 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) + { + states.add(conflict.cdoState()); + revisions.add(conflict.cdoRevision()); + } + + int resolved = 0; + + try + { + Set<CDOObject> remaining = new HashSet<CDOObject>(conflicts); + for (CDOConflictResolver resolver : resolvers) + { + resolver.resolveConflicts(Collections.unmodifiableSet(remaining)); + for (Iterator<CDOObject> it = remaining.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) + { + ((InternalCDOObject)object).cdoInternalSetState(state.next()); + ((InternalCDOObject)object).cdoInternalSetRevision(revision.next()); + } + + throw WrappedException.wrap(ex); + } + + conflict -= resolved; + } + + /** + * @since 2.0 + */ + public long getLastCommitTime() + { + return lastCommitTime; + } + + public CDOIDTemp getNextTemporaryID() + { + return CDOIDUtil.createTempObject(++lastTemporaryID); + } + + /** + * @since 2.0 + */ + @Override + protected CDOResourceImpl createRootResource() + { + return (CDOResourceImpl)getOrCreateResource(CDOResourceNode.ROOT_PATH); + } + + public CDOResource createResource(String path) + { + checkActive(); + URI uri = CDOURIUtil.createResourceURI(this, path); + return (CDOResource)getResourceSet().createResource(uri); + } + + public CDOResource getOrCreateResource(String path) + { + checkActive(); + + try + { + CDOID id = getResourceID(path); + if (!CDOIDUtil.isNull(id)) + { + return (CDOResource)getObject(id); + } + } + catch (Exception ignore) + { + // Just create the missing resource + } + + return createResource(path); + } + + /** + * @since 2.0 + */ + @Override + public void attachResource(CDOResourceImpl resource) + { + if (resource.isExisting()) + { + super.attachResource(resource); + } + else + { + // ResourceSet.createResource(uri) was called!! + attachNewResource(resource); + } + } + + 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); + } + + /** + * @return never <code>null</code>; + * @since 2.0 + */ + public CDOResourceFolder getOrCreateResourceFolder(List<String> names) + { + CDOResourceFolder folder = null; + for (String name : names) + { + CDOResourceNode node; + + try + { + CDOID folderID = folder == null ? null : folder.cdoID(); + node = getResourceNode(folderID, name); + } + catch (CDOException ex) + { + node = EresourceFactory.eINSTANCE.createCDOResourceFolder(); + attachNewResourceNode(folder, name, node); + } + + if (node instanceof CDOResourceFolder) + { + folder = (CDOResourceFolder)node; + } + else + { + throw new CDOException("Not a ResourceFolder: " + node); + } + } + + return folder; + } + + 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) + { + CDOStateMachine.INSTANCE.detach(cdoResource); + fireEvent(new ResourcesEvent(cdoResource.getPath(), ResourcesEvent.Kind.REMOVED)); + } + + /** + * @since 2.0 + */ + public CDOSavepointImpl getLastSavepoint() + { + checkActive(); + return lastSavepoint; + } + + /** + * @since 2.0 + */ + public CDOTransactionStrategy getTransactionStrategy() + { + if (transactionStrategy == null) + { + transactionStrategy = CDOTransactionStrategy.DEFAULT; + transactionStrategy.setTarget(this); + } + + return transactionStrategy; + } + + /** + * @since 2.0 + */ + public void setTransactionStrategy(CDOTransactionStrategy transactionStrategy) + { + if (this.transactionStrategy != null) + { + this.transactionStrategy.unsetTarget(this); + } + + this.transactionStrategy = transactionStrategy; + if (this.transactionStrategy != null) + { + this.transactionStrategy.setTarget(this); + } + } + + /** + * @since 2.0 + */ + @Override + protected CDOID getRootOrTopLevelResourceNodeID(String name) + { + 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(); + } + + node = getRootResourceNode(name, getNewResources().values()); + if (node != null) + { + return node.cdoID(); + } + } + + CDOID id = super.getRootOrTopLevelResourceNodeID(name); + if (getLastSavepoint().getAllDetachedObjects().containsKey(id) || getDirtyObjects().containsKey(id)) + { + throw new CDOException("Root resource node " + name + " doesn't exist"); + } + + return id; + } + + 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; + } + + /** + * @since 2.0 + */ + @Override + public InternalCDOObject getObject(CDOID id, boolean loadOnDemand) + { + checkActive(); + if (CDOIDUtil.isNull(id)) + { + return null; + } + + if (id.isTemporary() && isDetached(id)) + { + FSMUtil.validate(id, null); + } + + return super.getObject(id, loadOnDemand); + } + + private boolean isDetached(CDOID id) + { + return lastSavepoint.getSharedDetachedObjects().contains(id); + } + + /** + * @since 2.0 + */ + public InternalCDOCommitContext createCommitContext() + { + return new CDOCommitContextImpl(); + } + + /** + * @since 2.0 + */ + public void commit(IProgressMonitor progressMonitor) throws TransactionException + { + checkActive(); + if (hasConflict()) + { + throw new TransactionException("This transaction has conflicts"); + } + + if (progressMonitor == null) + { + progressMonitor = new NullProgressMonitor(); + } + + try + { + getTransactionStrategy().commit(this, progressMonitor); + } + catch (TransactionException ex) + { + throw ex; + } + catch (Exception ex) + { + throw new TransactionException(ex); + } + } + + public void commit() throws TransactionException + { + commit(null); + } + + /** + * @since 2.0 + */ + public void rollback() + { + checkActive(); + rollback(firstSavepoint); + cleanUp(); + } + + private void removeObjects(Collection<? extends CDOObject> objects) + { + if (!objects.isEmpty()) + { + for (CDOObject object : objects) + { + ((InternalCDOObject)object).cdoInternalSetState(CDOState.TRANSIENT); + removeObject(object.cdoID()); + + if (object instanceof CDOResource) + { + getResourceSet().getResources().remove(object); + } + + ((InternalCDOObject)object).cdoInternalSetID(null); + ((InternalCDOObject)object).cdoInternalSetRevision(null); + ((InternalCDOObject)object).cdoInternalSetView(null); + } + } + } + + private Set<CDOID> rollbackCompletely(CDOSavepoint savepoint) + { + Set<CDOID> idsOfNewObjectWithDeltas = new HashSet<CDOID>(); + + // Start from the last savepoint and come back up to the active + for (CDOSavepointImpl itrSavepoint = lastSavepoint; itrSavepoint != null; itrSavepoint = itrSavepoint + .getPreviousSavepoint()) + { + // Rollback new objects created after the save point + removeObjects(itrSavepoint.getNewResources().values()); + removeObjects(itrSavepoint.getNewObjects().values()); + + Map<CDOID, CDORevisionDelta> revisionDeltas = itrSavepoint.getRevisionDeltas(); + if (!revisionDeltas.isEmpty()) + { + for (CDORevisionDelta dirtyObject : revisionDeltas.values()) + { + if (dirtyObject.getID().isTemporary()) + { + idsOfNewObjectWithDeltas.add(dirtyObject.getID()); + } + } + } + + // Rollback all persisted objects + Map<CDOID, CDOObject> detachedObjects = itrSavepoint.getDetachedObjects(); + if (!detachedObjects.isEmpty()) + { + for (Entry<CDOID, CDOObject> entryDirty : detachedObjects.entrySet()) + { + if (entryDirty.getKey().isTemporary()) + { + idsOfNewObjectWithDeltas.add(entryDirty.getKey()); + } + else + { + InternalCDOObject internalDirtyObject = (InternalCDOObject)entryDirty.getValue(); + cleanObject(internalDirtyObject, getRevision(entryDirty.getKey(), true)); + } + } + } + + for (Entry<CDOID, CDOObject> entryDirtyObject : itrSavepoint.getDirtyObjects().entrySet()) + { + // Rollback all persisted objects + if (!entryDirtyObject.getKey().isTemporary()) + { + InternalCDOObject internalDirtyObject = (InternalCDOObject)entryDirtyObject.getValue(); + CDOStateMachine.INSTANCE.rollback(internalDirtyObject); + } + } + + if (savepoint == itrSavepoint) + { + break; + } + } + + return idsOfNewObjectWithDeltas; + } + + private void loadSavepoint(CDOSavepoint savepoint, Set<CDOID> idsOfNewObjectWithDeltas) + { + lastSavepoint.recalculateSharedDetachedObjects(); + + Map<CDOID, CDOObject> dirtyObjects = getDirtyObjects(); + Map<CDOID, CDOObject> newObjMaps = getNewObjects(); + Map<CDOID, CDOResource> newResources = getNewResources(); + 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); + if (object == null) + { + object = (InternalCDOObject)newResources.get(id); + } + + CDORevision revision = newBaseRevision.get(id); + if (revision != null) + { + object.cdoInternalSetRevision(revision.copy()); + object.cdoInternalSetView(this); + object.cdoInternalSetID(revision.getID()); + object.cdoInternalSetState(CDOState.NEW); + + // Load the object from revision to EObject + object.cdoInternalPostLoad(); + } + } + + // 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)); + } + + for (CDOSavepointImpl itrSavepoint = firstSavepoint; itrSavepoint != savepoint; itrSavepoint = itrSavepoint + .getNextSavepoint()) + { + CDOObjectMerger merger = new CDOObjectMerger(); + for (CDORevisionDelta delta : itrSavepoint.getRevisionDeltas().values()) + { + if (delta.getID().isTemporary() && !idsOfNewObjectWithDeltas.contains(delta.getID()) + || detachedObjects.containsKey(delta.getID())) + { + continue; + } + + Map<CDOID, CDOObject> map = delta.getID().isTemporary() ? newObjMaps : dirtyObjects; + InternalCDOObject object = (InternalCDOObject)map.get(delta.getID()); + if (object == null) + { + object = (InternalCDOObject)newResources.get(delta.getID()); + } + + // Change state of the objects + merger.merge(object, delta); + + // Load the object from revision to EObject + object.cdoInternalPostLoad(); + } + } + + dirty = ((CDOSavepointImpl)savepoint).isDirty(); + } + + /** + * @since 2.0 + */ + public void detachObject(InternalCDOObject object) + { + for (CDOTransactionHandler handler : getHandlers()) + { + handler.detachingObject(this, object); + } + + // deregister object + if (object.cdoState() == CDOState.NEW) + { + Map<CDOID, ? extends CDOObject> map = object instanceof CDOResource ? getLastSavepoint().getNewResources() + : getLastSavepoint().getNewObjects(); + + // Determine if we added object + if (map.containsKey(object.cdoID())) + { + // deregister object + deregisterObject(object); + map.remove(object.cdoID()); + } + else + { + getLastSavepoint().getDetachedObjects().put(object.cdoID(), object); + } + } + else + { + getLastSavepoint().getDetachedObjects().put(object.cdoID(), object); + } + + if (!dirty) + { + dirty = true; + fireEvent(new StartedEvent()); + } + } + + /** + * @since 2.0 + */ + public void rollback(CDOSavepoint savepoint) + { + checkActive(); + getTransactionStrategy().rollback(this, savepoint); + } + + /** + * @since 2.0 + */ + public void handleRollback(CDOSavepoint savepoint) + { + if (savepoint == null) + { + throw new IllegalArgumentException("Save point is null"); + } + + if (savepoint.getUserTransaction() != this) + { + throw new IllegalArgumentException("Save point to rollback doesn't belong to this transaction: " + savepoint); + } + + if (TRACER.isEnabled()) + { + TRACER.trace("handleRollback()"); + } + + try + { + if (!savepoint.isValid()) + { + throw new IllegalArgumentException("Savepoint isn't valid : " + savepoint); + } + + // Rollback objects + Set<CDOID> idsOfNewObjectWithDeltas = rollbackCompletely(savepoint); + + lastSavepoint = (CDOSavepointImpl)savepoint; + // Make savepoint active. Erase savepoint that could have be after + lastSavepoint.setNextSavepoint(null); + lastSavepoint.clear(); + + // Load from first savepoint up to current savepoint + loadSavepoint(lastSavepoint, idsOfNewObjectWithDeltas); + + if (lastSavepoint == firstSavepoint && options().isAutoReleaseLocksEnabled()) + { + // Unlock all objects + unlockObjects(null, null); + } + + Map<CDOIDTemp, CDOID> idMappings = Collections.emptyMap(); + fireEvent(new FinishedEvent(CDOTransactionFinishedEvent.Type.ROLLED_BACK, idMappings)); + for (CDOTransactionHandler handler : getHandlers()) + { + try + { + handler.rolledBackTransaction(this); + } + catch (RuntimeException ex) + { + OM.LOG.error(ex); + } + } + } + catch (RuntimeException ex) + { + throw ex; + } + catch (Exception ex) + { + throw new TransactionException(ex); + } + } + + /** + * @since 2.0 + */ + public CDOSavepoint handleSetSavepoint() + { + // Take a copy of all new objects for the current save point + addToBase(lastSavepoint.getNewObjects()); + addToBase(lastSavepoint.getNewResources()); + + lastSavepoint = new CDOSavepointImpl(this, lastSavepoint); + return lastSavepoint; + } + + /** + * @since 2.0 + */ + public CDOSavepoint setSavepoint() + { + checkActive(); + return getTransactionStrategy().setSavepoint(this); + } + + private void addToBase(Map<CDOID, ? extends CDOObject> objects) + { + for (CDOObject object : objects.values()) + { + // Load instance to revision + ((InternalCDOObject)object).cdoInternalPreCommit(); + lastSavepoint.getBaseNewObjects().put(object.cdoID(), object.cdoRevision().copy()); + } + } + + @Override + public String toString() + { + return MessageFormat.format("CDOTransaction({0})", getViewID()); + } + + public void registerNew(InternalCDOObject object) + { + if (TRACER.isEnabled()) + { + TRACER.format("Registering new object {0}", object); + } + + for (CDOTransactionHandler handler : getHandlers()) + { + handler.attachingObject(this, object); + } + + if (object instanceof CDOResourceImpl) + { + registerNew(lastSavepoint.getNewResources(), object); + } + else + { + registerNew(lastSavepoint.getNewObjects(), object); + } + } + + /** + * Receives notification for new and dirty objects + */ + public void registerFeatureDelta(InternalCDOObject object, CDOFeatureDelta featureDelta) + { + 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, ? extends CDOObject> map = object instanceof CDOResource ? getLastSavepoint().getNewResources() + : getLastSavepoint().getNewObjects(); + needToSaveFeatureDelta = !map.containsKey(object.cdoID()); + } + } + + if (needToSaveFeatureDelta) + { + CDORevisionDelta revisionDelta = lastSavepoint.getRevisionDeltas().get(object.cdoID()); + if (revisionDelta == null) + { + revisionDelta = CDORevisionDeltaUtil.create(object.cdoRevision()); + lastSavepoint.getRevisionDeltas().put(object.cdoID(), revisionDelta); + } + + ((InternalCDORevisionDelta)revisionDelta).addFeatureDelta(featureDelta); + } + + for (CDOTransactionHandler handler : getHandlers()) + { + handler.modifyingObject(this, object, featureDelta); + } + } + + public void registerRevisionDelta(CDORevisionDelta revisionDelta) + { + lastSavepoint.getRevisionDeltas().putIfAbsent(revisionDelta.getID(), revisionDelta); + } + + public void registerDirty(InternalCDOObject object, CDOFeatureDelta featureDelta) + { + if (TRACER.isEnabled()) + { + TRACER.format("Registering dirty object {0}", object); + } + + if (featureDelta != null) + { + registerFeatureDelta(object, featureDelta); + } + + registerNew(lastSavepoint.getDirtyObjects(), object); + } + + /** + * TODO Simon: Should this method go to CDOSavePointImpl? + */ + @SuppressWarnings("unchecked") + private void registerNew(Map map, InternalCDOObject object) + { + Object old = map.put(object.cdoID(), object); + if (old != null) + { + throw new ImplementationError("Duplicate ID: " + object); + } + + if (!dirty) + { + dirty = true; + fireEvent(new StartedEvent()); + } + } + + @SuppressWarnings("unchecked") + private List<CDOPackage> analyzeNewPackages() + { + CDOSessionPackageManagerImpl packageManager = (CDOSessionPackageManagerImpl)getSession().getPackageManager(); + 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 = ModelUtil.getTopLevelPackage(ePackage); + if (ePackage == topLevelPackage || usedPackages.add(topLevelPackage)) + { + CDOPackage cdoPackage = ModelUtil.getCDOPackage(topLevelPackage, packageManager); + if (!cdoPackage.isPersistent() && !cdoPackage.isSystem()) + { + usedNewPackages.add(topLevelPackage); + } + } + } + } + + if (usedNewPackages.size() > 0) + { + return analyzeNewPackages(usedNewPackages, packageManager); + } + + return Collections.EMPTY_LIST; + } + + private static List<CDOPackage> analyzeNewPackages(Collection<EPackage> usedTopLevelPackages, + CDOSessionPackageManagerImpl packageManager) + { + // Determine which of the corresdonding CDOPackages are new + List<CDOPackage> newPackages = new ArrayList<CDOPackage>(); + + IPackageClosure closure = new CompletePackageClosure(); + usedTopLevelPackages = closure.calculate(usedTopLevelPackages); + + for (EPackage usedPackage : usedTopLevelPackages) + { + CDOPackage cdoPackage = ModelUtil.getCDOPackage(usedPackage, packageManager); + if (cdoPackage == null) + { + throw new IllegalStateException("Missing CDO package: " + usedPackage.getNsURI()); + } + + if (!(cdoPackage.isPersistent() || cdoPackage.isSystem())) + { + newPackages.add(cdoPackage); + } + } + + return newPackages; + } + + private void cleanUp() + { + lastSavepoint = firstSavepoint; + firstSavepoint.clear(); + firstSavepoint.setNextSavepoint(null); + firstSavepoint.getSharedDetachedObjects().clear(); + dirty = false; + conflict = 0; + lastTemporaryID = 0; + } + + public Map<CDOID, CDOObject> getDirtyObjects() + { + checkActive(); + return lastSavepoint.getAllDirtyObjects(); + } + + public Map<CDOID, CDOObject> getNewObjects() + { + checkActive(); + return lastSavepoint.getAllNewObjects(); + } + + public Map<CDOID, CDOResource> getNewResources() + { + checkActive(); + return lastSavepoint.getAllNewResources(); + } + + /** + * @since 2.0 + */ + public Map<CDOID, CDORevision> getBaseNewObjects() + { + checkActive(); + return lastSavepoint.getAllBaseNewObjects(); + } + + public Map<CDOID, CDORevisionDelta> getRevisionDeltas() + { + checkActive(); + return lastSavepoint.getAllRevisionDeltas(); + } + + /** + * @since 2.0 + */ + public Map<CDOID, CDOObject> getDetachedObjects() + { + checkActive(); + return lastSavepoint.getAllDetachedObjects(); + } + + /** + * @since 2.0 + */ + @Override + protected void doDeactivate() throws Exception + { + options().disposeConflictResolvers(); + lastSavepoint = null; + firstSavepoint = null; + transactionStrategy = null; + super.doDeactivate(); + } + + /** + * @author Simon McDuff + */ + private class CDOCommitContextImpl implements InternalCDOCommitContext + { + private Map<CDOID, CDOResource> newResources; + + private Map<CDOID, CDOObject> newObjects; + + private Map<CDOID, CDOObject> dirtyObjects; + + private Map<CDOID, CDORevisionDelta> revisionDeltas; + + private Map<CDOID, CDOObject> detachedObjects; + + private List<CDOPackage> newPackages; + + public CDOCommitContextImpl() + { + CDOTransactionImpl transaction = getTransaction(); + newResources = transaction.getNewResources(); + newObjects = transaction.getNewObjects(); + dirtyObjects = transaction.getDirtyObjects(); + detachedObjects = transaction.getDetachedObjects(); + revisionDeltas = transaction.getRevisionDeltas(); + newPackages = transaction.analyzeNewPackages(); + } + + public CDOTransactionImpl getTransaction() + { + return CDOTransactionImpl.this; + } + + public Map<CDOID, CDOObject> getDirtyObjects() + { + return dirtyObjects; + } + + public Map<CDOID, CDOObject> getNewObjects() + { + return newObjects; + } + + public List<CDOPackage> getNewPackages() + { + return newPackages; + } + + public Map<CDOID, CDOResource> getNewResources() + { + return newResources; + } + + public Map<CDOID, CDOObject> getDetachedObjects() + { + return detachedObjects; + } + + public Map<CDOID, CDORevisionDelta> getRevisionDeltas() + { + return revisionDeltas; + } + + public void preCommit() + { + if (isDirty()) + { + if (TRACER.isEnabled()) + { + TRACER.trace("commit()"); + } + + for (CDOTransactionHandler handler : getHandlers()) + { + handler.committingTransaction(getTransaction(), this); + } + + try + { + preCommit(getNewResources()); + preCommit(getNewObjects()); + preCommit(getDirtyObjects()); + } + catch (RuntimeException ex) + { + throw ex; + } + catch (Exception ex) + { + throw new TransactionException(ex); + } + } + } + + public void postCommit(CommitTransactionResult result) + { + if (isDirty()) + { + try + { + Collection<CDORevisionDelta> deltas = getRevisionDeltas().values(); + + postCommit(getNewResources(), result); + postCommit(getNewObjects(), result); + postCommit(getDirtyObjects(), result); + for (Entry<CDOID, CDOObject> entry : getDetachedObjects().entrySet()) + { + removeObject(entry.getKey()); + } + + InternalCDOSession session = getSession(); + for (CDOPackage newPackage : newPackages) + { + ((InternalCDOPackage)newPackage).setPersistent(true); + } + + long timeStamp = result.getTimeStamp(); + + Map<CDOID, CDOObject> dirtyObjects = getDirtyObjects(); + Set<CDOIDAndVersion> dirtyIDs = new HashSet<CDOIDAndVersion>(); + for (CDOObject dirtyObject : dirtyObjects.values()) + { + CDORevision revision = dirtyObject.cdoRevision(); + CDOIDAndVersion dirtyID = CDOIDUtil.createIDAndVersion(revision.getID(), revision.getVersion() - 1); + dirtyIDs.add(dirtyID); + } + + if (!dirtyIDs.isEmpty() || !getDetachedObjects().isEmpty()) + { + Set<CDOID> detachedIDs = new HashSet<CDOID>(getDetachedObjects().keySet()); + Collection<CDORevisionDelta> deltasCopy = new ArrayList<CDORevisionDelta>(deltas); + + // Adjust references in the deltas. Could be used in ChangeSubscription from others CDOView + for (CDORevisionDelta dirtyObjectDelta : deltasCopy) + { + ((InternalCDORevisionDelta)dirtyObjectDelta).adjustReferences(result.getReferenceAdjuster()); + } + + session.handleCommitNotification(timeStamp, dirtyIDs, detachedIDs, deltasCopy, getTransaction()); + } + + lastCommitTime = timeStamp; + for (CDOTransactionHandler handler : getHandlers()) + { + handler.committedTransaction(getTransaction(), this); + } + + getChangeSubscriptionManager().committedTransaction(getTransaction(), this); + getAdapterManager().committedTransaction(getTransaction(), this); + + cleanUp(); + Map<CDOIDTemp, CDOID> idMappings = result.getIDMappings(); + fireEvent(new FinishedEvent(CDOTransactionFinishedEvent.Type.COMMITTED, idMappings)); + } + catch (RuntimeException ex) + { + throw ex; + } + catch (Exception ex) + { + throw new TransactionException(ex); + } + } + else + { + // Removes locks even if no one touch the transaction + if (options().isAutoReleaseLocksEnabled()) + { + unlockObjects(null, null); + } + } + } + + @SuppressWarnings("unchecked") + private void preCommit(Map objects) + { + if (!objects.isEmpty()) + { + for (Object object : objects.values()) + { + ((InternalCDOObject)object).cdoInternalPreCommit(); + } + } + } + + @SuppressWarnings("unchecked") + private void postCommit(Map objects, CommitTransactionResult result) + { + if (!objects.isEmpty()) + { + for (Object object : objects.values()) + { + CDOStateMachine.INSTANCE.commit((InternalCDOObject)object, 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()); + } + } + + /** + * @author Eike Stepper + */ + private final class FinishedEvent extends Event implements CDOTransactionFinishedEvent + { + private static final long serialVersionUID = 1L; + + private Type type; + + private Map<CDOIDTemp, CDOID> idMappings; + + private FinishedEvent(Type type, Map<CDOIDTemp, CDOID> idMappings) + { + this.type = type; + this.idMappings = idMappings; + } + + public Type getType() + { + return type; + } + + public Map<CDOIDTemp, CDOID> getIDMappings() + { + return idMappings; + } + + @Override + public String toString() + { + return MessageFormat.format("CDOTransactionFinishedEvent[source={0}, type={1}, idMappings={2}]", getSource(), + getType(), 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}]", + getSource(), getConflictingObject(), isFirstConflict()); + } + } + + /** + * @author Eike Stepper + */ + private final class ResourcesEvent extends Event implements CDOViewResourcesEvent + { + private static final long serialVersionUID = 1L; + + private String resourcePath; + + private Kind kind; + + public ResourcesEvent(String resourcePath, Kind kind) + { + this.resourcePath = resourcePath; + this.kind = kind; + } + + public String getResourcePath() + { + return resourcePath; + } + + public Kind getKind() + { + return kind; + } + + @Override + public String toString() + { + return MessageFormat.format("CDOViewResourcesEvent[source={0}, {1}={2}]", getSource(), resourcePath, kind); + } + } + + /** + * @author Eike Stepper + * @since 2.0 + */ + protected final class OptionsImpl extends CDOViewImpl.OptionsImpl implements CDOTransaction.Options + { + private List<CDOConflictResolver> conflictResolvers = new ArrayList<CDOConflictResolver>(); + + private boolean autoReleaseLocksEnabled = true; + + public OptionsImpl() + { + } + + public CDOConflictResolver[] getConflictResolvers() + { + synchronized (conflictResolvers) + { + return conflictResolvers.toArray(new CDOConflictResolver[conflictResolvers.size()]); + } + } + + public void setConflictResolvers(CDOConflictResolver[] resolvers) + { + synchronized (conflictResolvers) + { + for (CDOConflictResolver resolver : conflictResolvers) + { + resolver.setTransaction(null); + } + + conflictResolvers.clear(); + for (CDOConflictResolver resolver : resolvers) + { + validateResolver(resolver); + conflictResolvers.add(resolver); + } + } + + fireEvent(new ConflictResolversEventImpl()); + } + + public void addConflictResolver(CDOConflictResolver resolver) + { + boolean changed = false; + synchronized (conflictResolvers) + { + if (!conflictResolvers.contains(resolver)) + { + validateResolver(resolver); + conflictResolvers.add(resolver); + changed = true; + } + } + + if (changed) + { + fireEvent(new ConflictResolversEventImpl()); + } + } + + public void removeConflictResolver(CDOConflictResolver resolver) + { + boolean changed = false; + synchronized (conflictResolvers) + { + changed = conflictResolvers.remove(resolver); + } + + if (changed) + { + resolver.setTransaction(null); + fireEvent(new ConflictResolversEventImpl()); + } + } + + public void disposeConflictResolvers() + { + try + { + for (CDOConflictResolver resolver : options().getConflictResolvers()) + { + try + { + resolver.setTransaction(null); + } + catch (Exception ignore) + { + } + } + } + catch (Exception ignore) + { + } + } + + private void validateResolver(CDOConflictResolver resolver) + { + if (resolver.getTransaction() != null) + { + throw new IllegalArgumentException("New conflict resolver is already associated with a transaction"); + } + + resolver.setTransaction(CDOTransactionImpl.this); + } + + public boolean isAutoReleaseLocksEnabled() + { + return autoReleaseLocksEnabled; + } + + public void setAutoReleaseLocksEnabled(boolean on) + { + if (autoReleaseLocksEnabled != on) + { + autoReleaseLocksEnabled = on; + fireEvent(new AutoReleaseLockEventImpl()); + } + } + + /** + * @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 AutoReleaseLockEventImpl extends OptionsEvent implements AutoReleaseLockEvent + { + private static final long serialVersionUID = 1L; + + public AutoReleaseLockEventImpl() + { + super(OptionsImpl.this); + } + } + } +} diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOXASavepoint.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOXASavepoint.java new file mode 100644 index 0000000000..8d8d319ac3 --- /dev/null +++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOXASavepoint.java @@ -0,0 +1,41 @@ +/*************************************************************************** + * Copyright (c) 2004 - 2008 Eike Stepper, Germany. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Simon McDuff - initial API and implementation + **************************************************************************/ +package org.eclipse.emf.internal.cdo.transaction; + +import org.eclipse.emf.cdo.transaction.CDOSavepoint; +import org.eclipse.emf.cdo.transaction.CDOUserTransaction; + +import java.util.List; + +/** + * @author Simon McDuff + * @since 2.0 + */ +public class CDOXASavepoint extends CDOAbstractSavepoint +{ + private List<CDOSavepoint> savepoints; + + public CDOXASavepoint(CDOUserTransaction transaction, CDOAbstractSavepoint lastSavepoint) + { + super(transaction, lastSavepoint); + + } + + public List<CDOSavepoint> getSavepoints() + { + return savepoints; + } + + public void setSavepoints(List<CDOSavepoint> savepoints) + { + this.savepoints = savepoints; + } +} diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOXATransactionCommitContext.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOXATransactionCommitContext.java new file mode 100644 index 0000000000..c397c4bb60 --- /dev/null +++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOXATransactionCommitContext.java @@ -0,0 +1,196 @@ +/*************************************************************************** + * Copyright (c) 2004 - 2008 Eike Stepper, Germany. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Simon McDuff - initial API and implementation + **************************************************************************/ +package org.eclipse.emf.internal.cdo.transaction; + +import org.eclipse.emf.cdo.CDOObject; +import org.eclipse.emf.cdo.common.id.CDOID; +import org.eclipse.emf.cdo.common.id.CDOIDProvider; +import org.eclipse.emf.cdo.common.model.CDOPackage; +import org.eclipse.emf.cdo.common.revision.CDOReferenceAdjuster; +import org.eclipse.emf.cdo.common.revision.delta.CDORevisionDelta; +import org.eclipse.emf.cdo.eresource.CDOResource; +import org.eclipse.emf.cdo.internal.common.id.CDOIDExternalTempImpl; +import org.eclipse.emf.cdo.util.CDOUtil; + +import org.eclipse.emf.internal.cdo.protocol.CommitTransactionResult; +import org.eclipse.emf.internal.cdo.transaction.CDOXATransactionImpl.CDOXAState; + +import org.eclipse.net4j.util.ImplementationError; + +import org.eclipse.emf.ecore.InternalEObject; +import org.eclipse.emf.spi.cdo.InternalCDOCommitContext; +import org.eclipse.emf.spi.cdo.InternalCDOObject; +import org.eclipse.emf.spi.cdo.InternalCDOTransaction; + +import org.eclipse.core.runtime.IProgressMonitor; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; + +/** + * @author Simon McDuff + * @since 2.0 + */ +public class CDOXATransactionCommitContext implements Callable<Object>, CDOIDProvider, InternalCDOCommitContext +{ + private CDOXATransactionImpl transactionManager; + + private IProgressMonitor progressMonitor; + + private CDOXAState state; + + private CommitTransactionResult result; + + private InternalCDOCommitContext delegateCommitContext; + + private Map<CDOIDExternalTempImpl, InternalCDOTransaction> requestedIDs = new HashMap<CDOIDExternalTempImpl, InternalCDOTransaction>(); + + private Map<InternalCDOObject, CDOIDExternalTempImpl> objectToID = new HashMap<InternalCDOObject, CDOIDExternalTempImpl>(); + + public CDOXATransactionCommitContext(CDOXATransactionImpl manager, InternalCDOCommitContext commitContext) + { + transactionManager = manager; + delegateCommitContext = commitContext; + } + + public CDOXATransactionImpl getTransactionManager() + { + return transactionManager; + } + + public void setProgressMonitor(IProgressMonitor progressMonitor) + { + this.progressMonitor = progressMonitor; + } + + public CDOXAState getState() + { + return state; + } + + public void setState(CDOXAState state) + { + this.state = state; + } + + public CommitTransactionResult getResult() + { + return result; + } + + public void setResult(CommitTransactionResult result) + { + this.result = result; + } + + public InternalCDOTransaction getTransaction() + { + return delegateCommitContext.getTransaction(); + } + + public Map<CDOIDExternalTempImpl, InternalCDOTransaction> getRequestedIDs() + { + return requestedIDs; + } + + public Map<CDOID, CDOObject> getDirtyObjects() + { + return delegateCommitContext.getDirtyObjects(); + } + + public Map<CDOID, CDOObject> getNewObjects() + { + return delegateCommitContext.getNewObjects(); + } + + public List<CDOPackage> getNewPackages() + { + return delegateCommitContext.getNewPackages(); + } + + public Map<CDOID, CDOResource> getNewResources() + { + return delegateCommitContext.getNewResources(); + } + + public Map<CDOID, CDOObject> getDetachedObjects() + { + return delegateCommitContext.getDetachedObjects(); + } + + public Map<CDOID, CDORevisionDelta> getRevisionDeltas() + { + return delegateCommitContext.getRevisionDeltas(); + } + + public Object call() throws Exception + { + state.handle(this, progressMonitor); + return true; + } + + public CDOID provideCDOID(Object idOrObject) + { + CDOID id = getTransaction().provideCDOID(idOrObject); + + if (id instanceof CDOIDExternalTempImpl) + { + if (idOrObject instanceof InternalEObject) + { + CDOIDExternalTempImpl proxyTemp = (CDOIDExternalTempImpl)id; + if (!requestedIDs.containsKey(proxyTemp)) + { + InternalCDOObject cdoObject = (InternalCDOObject)CDOUtil.getCDOObject((InternalEObject)idOrObject); + InternalCDOTransaction cdoTransaction = (InternalCDOTransaction)cdoObject.cdoView(); + getTransactionManager().add(cdoTransaction, proxyTemp); + requestedIDs.put(proxyTemp, cdoTransaction); + objectToID.put(cdoObject, proxyTemp); + } + } + else + { + throw new ImplementationError("Object should be an EObject " + idOrObject); + } + } + + return id; + } + + public void preCommit() + { + delegateCommitContext.preCommit(); + } + + public void postCommit(CommitTransactionResult result) + { + if (result != null) + { + final CDOReferenceAdjuster defaultReferenceAdjuster = result.getReferenceAdjuster(); + result.setReferenceAdjuster(new CDOReferenceAdjuster() + { + public Object adjustReference(Object id) + { + CDOIDExternalTempImpl externalID = objectToID.get(id); + if (externalID != null) + { + id = externalID; + } + + return defaultReferenceAdjuster.adjustReference(id); + } + }); + } + + delegateCommitContext.postCommit(result); + } +}; diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOXATransactionImpl.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOXATransactionImpl.java new file mode 100644 index 0000000000..c8fbb2e709 --- /dev/null +++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOXATransactionImpl.java @@ -0,0 +1,608 @@ +/*************************************************************************** + * Copyright (c) 2004 - 2008 Eike Stepper, Germany. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Simon McDuff - initial API and implementation + **************************************************************************/ +package org.eclipse.emf.internal.cdo.transaction; + +import org.eclipse.emf.cdo.common.id.CDOID; +import org.eclipse.emf.cdo.transaction.CDOSavepoint; +import org.eclipse.emf.cdo.transaction.CDOTransaction; +import org.eclipse.emf.cdo.transaction.CDOXATransaction; +import org.eclipse.emf.cdo.util.CDOUtil; +import org.eclipse.emf.cdo.view.CDOView; +import org.eclipse.emf.cdo.view.CDOViewSet; + +import org.eclipse.emf.internal.cdo.protocol.CDOClientProtocol; +import org.eclipse.emf.internal.cdo.protocol.CommitTransactionCancelRequest; +import org.eclipse.emf.internal.cdo.protocol.CommitTransactionPhase1Request; +import org.eclipse.emf.internal.cdo.protocol.CommitTransactionPhase2Request; +import org.eclipse.emf.internal.cdo.protocol.CommitTransactionPhase3Request; +import org.eclipse.emf.internal.cdo.protocol.CommitTransactionResult; + +import org.eclipse.net4j.util.CheckUtil; +import org.eclipse.net4j.util.om.monitor.EclipseMonitor; +import org.eclipse.net4j.util.om.monitor.EclipseMonitor.SynchonizedSubProgressMonitor; +import org.eclipse.net4j.util.transaction.TransactionException; + +import org.eclipse.emf.common.notify.Adapter; +import org.eclipse.emf.common.notify.Notification; +import org.eclipse.emf.common.notify.Notifier; +import org.eclipse.emf.spi.cdo.CDOTransactionStrategy; +import org.eclipse.emf.spi.cdo.InternalCDOCommitContext; +import org.eclipse.emf.spi.cdo.InternalCDOTransaction; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.SubProgressMonitor; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * Three-phase commit. + * <p> + * Phase 1 does the following for each CDOTransaction:<br> + * - preCommit <br> + * - Accumulate external temporary ID.<br> + * - request the commit to the server.<br> + * - The server registers the commit context and returns the final ID for each temporary ID. + * <p> + * Phase 2 does the following for each CDOTransaction:<br> + * - Transfer to the server a list of mapping of temporary externalID and final external ID that we accumulate + * previously<br> + * - Returns to the client only when commit process is ready to flush to disk (commit). <br> + * <p> + * Phase 3 does the following for each CDOTransaction:<br> + * - Make modifications permanent.<br> + * - PostCommit. + * <p> + * If an exception occurred during phase 1 or phase 2, the commit will be cancelled for all {@link CDOTransaction} + * include in the XA transaction. If an exception occurred during phase 3, the commit will be cancelled only for the + * {@link CDOTransaction} where the error happened. + * <p> + * All {@link CDOTransaction} includes in the commit process need to have finish their phase before moving to the next + * phase. For one phase, every {@link CDOTransaction} could have their own thread. It depends of the ExecutorService. + * <p> + * + * @author Simon McDuff + * @since 2.0 + */ +public class CDOXATransactionImpl implements CDOXATransaction +{ + private List<InternalCDOTransaction> transactions = new ArrayList<InternalCDOTransaction>(); + + private boolean allRequestEnabled = true; + + private ExecutorService executorService = Executors.newFixedThreadPool(10); + + private Map<InternalCDOTransaction, CDOXATransactionCommitContext> activeContext = new HashMap<InternalCDOTransaction, CDOXATransactionCommitContext>(); + + private Map<InternalCDOTransaction, Set<CDOID>> requestedCDOID = new HashMap<InternalCDOTransaction, Set<CDOID>>(); + + private CDOXASavepoint lastSavepoint = new CDOXASavepoint(this, null); + + private CDOXASavepoint firstSavepoint = lastSavepoint; + + private CDOTransactionStrategy transactionStrategy = new CDOXATransactionStrategyImpl(); + + private CDOXAInternalAdapter internalAdapter = new CDOXAInternalAdapter(); + + public CDOXATransactionImpl() + { + } + + public boolean isAllowRequestFromTransactionEnabled() + { + return allRequestEnabled; + } + + public void setAllowRequestFromTransactionEnabled(boolean allRequest) + { + allRequestEnabled = allRequest; + } + + public void add(InternalCDOTransaction transaction) + { + transaction.setTransactionStrategy(transactionStrategy); + } + + public void remove(InternalCDOTransaction transaction) + { + transaction.setTransactionStrategy(null); + } + + public synchronized void add(CDOViewSet viewSet) + { + CDOXATransaction transSet = CDOUtil.getXATransaction(viewSet); + if (transSet != null) + { + throw new IllegalArgumentException("XATransaction is already attached to this viewSet"); + } + + viewSet.eAdapters().add(internalAdapter); + + for (InternalCDOTransaction transaction : getTransactions(viewSet)) + { + add(transaction); + } + } + + public synchronized void remove(CDOViewSet viewSet) + { + CDOXATransaction transSet = CDOUtil.getXATransaction(viewSet); + if (transSet != this) + { + throw new IllegalArgumentException("ViewSet isn't attached to this XATransaction"); + } + + for (InternalCDOTransaction transaction : getTransactions(viewSet)) + { + remove(transaction); + } + + viewSet.eAdapters().remove(internalAdapter); + }; + + public void add(InternalCDOTransaction view, CDOID object) + { + synchronized (requestedCDOID) + { + Set<CDOID> ids = requestedCDOID.get(view); + if (ids == null) + { + ids = new HashSet<CDOID>(); + requestedCDOID.put(view, ids); + } + + ids.add(object); + } + } + + public CDOID[] get(InternalCDOTransaction transaction) + { + Set<CDOID> ids = requestedCDOID.get(transaction); + return ids.toArray(new CDOID[ids.size()]); + } + + public CDOXATransactionCommitContext getCommitContext(CDOTransaction transaction) + { + return activeContext.get(transaction); + } + + private void send(Collection<CDOXATransactionCommitContext> xaContexts, final IProgressMonitor progressMonitor) + throws InterruptedException, ExecutionException + { + progressMonitor.beginTask("", xaContexts.size()); + + try + { + List<Future<Object>> futures = new ArrayList<Future<Object>>(); + for (CDOXATransactionCommitContext xaContext : xaContexts) + { + xaContext.setProgressMonitor(new SynchonizedSubProgressMonitor(progressMonitor, 1)); + futures.add(executorService.submit(xaContext)); + } + + int nbProcessDone; + do + { + nbProcessDone = 0; + for (Future<Object> future : futures) + { + try + { + future.get(1, TimeUnit.MILLISECONDS); + nbProcessDone++; + } + catch (TimeoutException ex) + { + } + } + } while (xaContexts.size() != nbProcessDone); + } + finally + { + progressMonitor.done(); + for (CDOXATransactionCommitContext xaContext : xaContexts) + { + xaContext.setProgressMonitor(null); + } + } + } + + private void cleanUp() + { + activeContext.clear(); + requestedCDOID.clear(); + } + + private List<InternalCDOTransaction> getTransactions(CDOViewSet viewSet) + { + List<InternalCDOTransaction> transactions = new ArrayList<InternalCDOTransaction>(); + for (CDOView view : viewSet.getViews()) + { + if (view instanceof InternalCDOTransaction) + { + transactions.add((InternalCDOTransaction)view); + } + } + + return transactions; + } + + public void commit() throws TransactionException + { + commit(new NullProgressMonitor()); + } + + public void commit(IProgressMonitor progressMonitor) throws TransactionException + { + CheckUtil.checkArg(progressMonitor, "progressMonitor"); + progressMonitor.beginTask("Committing XA transaction", 3); + int phase = 0; + + for (InternalCDOTransaction transaction : transactions) + { + InternalCDOCommitContext context = transaction.createCommitContext(); + CDOXATransactionCommitContext xaContext = new CDOXATransactionCommitContext(this, context); + xaContext.setState(CDOXAPhase1State.INSTANCE); + activeContext.put(transaction, xaContext); + } + + try + { + // We need to complete 3 phases + while (phase < 3) + { + send(activeContext.values(), new SubProgressMonitor(progressMonitor, 1)); + ++phase; + } + } + catch (Exception ex) + { + if (phase < 2) + { + // Phase 0 and 1 are the only two phases we can cancel. + for (CDOXATransactionCommitContext transaction : activeContext.values()) + { + transaction.setState(CDOXACancel.INSTANCE); + } + + try + { + send(activeContext.values(), new SubProgressMonitor(progressMonitor, 2 - phase)); + } + catch (InterruptedException ignore) + { + } + catch (ExecutionException ignore) + { + } + } + + throw new TransactionException(ex); + } + finally + { + cleanUp(); + progressMonitor.done(); + } + } + + public CDOXASavepoint getLastSavepoint() + { + return lastSavepoint; + } + + public void rollback() + { + rollback(firstSavepoint); + } + + public void rollback(CDOSavepoint savepoint) + { + if (savepoint == null) + { + throw new IllegalArgumentException("Save point is null"); + } + + if (savepoint.getUserTransaction() != this) + { + throw new IllegalArgumentException("Save point to rollback doesn't belong to this transaction: " + savepoint); + } + + if (!savepoint.isValid()) + { + throw new IllegalArgumentException("Savepoint isn't valid : " + savepoint); + } + + CDOXASavepoint savepointSet = (CDOXASavepoint)savepoint; + List<CDOSavepoint> savepoints = savepointSet.getSavepoints(); + if (savepoints == null) + { + savepoints = getListSavepoints(); + } + + for (CDOSavepoint indexSavePoint : savepoints) + { + InternalCDOTransaction transaction = (InternalCDOTransaction)indexSavePoint.getUserTransaction(); + CDOSingleTransactionStrategy.INSTANCE.rollback(transaction, indexSavePoint); + } + + lastSavepoint = savepointSet; + lastSavepoint.setNextSavepoint(null); + lastSavepoint.setSavepoints(null); + } + + public CDOSavepoint setSavepoint() + { + List<CDOSavepoint> savepoints = getListSavepoints(); + for (CDOSavepoint savepoint : savepoints) + { + InternalCDOTransaction transaction = (InternalCDOTransaction)savepoint.getUserTransaction(); + CDOSingleTransactionStrategy.INSTANCE.setSavepoint(transaction); + } + + getLastSavepoint().setSavepoints(savepoints); + lastSavepoint = new CDOXASavepoint(this, getLastSavepoint()); + return lastSavepoint; + } + + private List<CDOSavepoint> getListSavepoints() + { + synchronized (transactions) + { + List<CDOSavepoint> savepoints = new ArrayList<CDOSavepoint>(); + for (InternalCDOTransaction transaction : transactions) + { + savepoints.add(transaction.getLastSavepoint()); + } + + return savepoints; + } + } + + /** + * @author Simon McDuff + */ + private final class CDOXATransactionStrategyImpl implements CDOTransactionStrategy + { + public CDOXATransactionStrategyImpl() + { + } + + public void setTarget(InternalCDOTransaction transaction) + { + synchronized (transactions) + { + transactions.add(transaction); + } + } + + public void unsetTarget(InternalCDOTransaction transaction) + { + synchronized (transactions) + { + transactions.remove(transaction); + } + } + + private void checkAccess() + { + if (!allRequestEnabled) + { + throw new IllegalStateException("Commit from CDOTransaction is not allowed."); + } + } + + public void commit(InternalCDOTransaction transactionCommit, IProgressMonitor progressMonitor) throws Exception + { + checkAccess(); + CDOXATransactionImpl.this.commit(progressMonitor); + } + + public void rollback(InternalCDOTransaction transaction, CDOSavepoint savepoint) + { + checkAccess(); + CDOXATransactionImpl.this.rollback(savepoint); + } + + public CDOSavepoint setSavepoint(InternalCDOTransaction transaction) + { + checkAccess(); + return CDOXATransactionImpl.this.setSavepoint(); + } + } + + /** + * @author Simon McDuff + */ + public static abstract class CDOXAState + { + public static final CDOXAState DONE = new CDOXAState() + { + @Override + protected void handle(CDOXATransactionCommitContext xaContext, IProgressMonitor progressMonitor) throws Exception + { + progressMonitor.done(); + } + }; + + protected void check_result(CommitTransactionResult result) + { + if (result != null && result.getRollbackMessage() != null) + { + throw new TransactionException(result.getRollbackMessage()); + } + } + + protected abstract void handle(CDOXATransactionCommitContext xaContext, IProgressMonitor progressMonitor) + throws Exception; + }; + + /** + * @author Simon McDuff + */ + public static class CDOXAPhase1State extends CDOXAState + { + public static final CDOXAPhase1State INSTANCE = new CDOXAPhase1State(); + + @Override + protected void handle(CDOXATransactionCommitContext xaContext, IProgressMonitor progressMonitor) throws Exception + { + xaContext.preCommit(); + CommitTransactionResult result = null; + if (xaContext.getTransaction().isDirty()) + { + // Phase 1 + CDOClientProtocol protocol = (CDOClientProtocol)xaContext.getTransaction().getSession().getProtocol(); + CommitTransactionPhase1Request request = new CommitTransactionPhase1Request(protocol, xaContext); + result = request.send(new EclipseMonitor(progressMonitor)); + check_result(result); + } + xaContext.setResult(result); + xaContext.setState(CDOXAPhase2State.INSTANCE); + } + }; + + /** + * @author Simon McDuff + */ + public static class CDOXAPhase2State extends CDOXAState + { + public static final CDOXAPhase2State INSTANCE = new CDOXAPhase2State(); + + public CDOXAPhase2State() + { + } + + @Override + protected void handle(CDOXATransactionCommitContext xaContext, IProgressMonitor progressMonitor) throws Exception + { + if (xaContext.getTransaction().isDirty()) + { + // Phase 2 + CDOClientProtocol protocol = (CDOClientProtocol)xaContext.getTransaction().getSession().getProtocol(); + CommitTransactionPhase2Request request = new CommitTransactionPhase2Request(protocol, xaContext); + CommitTransactionResult result = request.send(new EclipseMonitor(progressMonitor)); + check_result(result); + } + xaContext.setState(CDOXAPhase3State.INSTANCE); + } + }; + + /** + * @author Simon McDuff + */ + public static class CDOXAPhase3State extends CDOXAState + { + public static final CDOXAPhase3State INSTANCE = new CDOXAPhase3State(); + + public CDOXAPhase3State() + { + } + + @Override + protected void handle(CDOXATransactionCommitContext xaContext, IProgressMonitor progressMonitor) throws Exception + { + // Phase 2 + if (xaContext.getTransaction().isDirty()) + { + CDOClientProtocol protocol = (CDOClientProtocol)xaContext.getTransaction().getSession().getProtocol(); + CommitTransactionPhase3Request request = new CommitTransactionPhase3Request(protocol, xaContext); + CommitTransactionResult result = request.send(new EclipseMonitor(progressMonitor)); + check_result(result); + } + xaContext.postCommit(xaContext.getResult()); + xaContext.setState(null); + } + }; + + /** + * @author Simon McDuff + */ + public static class CDOXACancel extends CDOXAState + { + public static final CDOXACancel INSTANCE = new CDOXACancel(); + + public CDOXACancel() + { + } + + @Override + protected void handle(CDOXATransactionCommitContext xaContext, IProgressMonitor progressMonitor) throws Exception + { + // Phase 2 + CDOClientProtocol protocol = (CDOClientProtocol)xaContext.getTransaction().getSession().getProtocol(); + CommitTransactionCancelRequest request = new CommitTransactionCancelRequest(protocol, xaContext); + CommitTransactionResult result = request.send(new EclipseMonitor(progressMonitor)); + check_result(result); + } + }; + + /** + * @author Simon McDuff + */ + public class CDOXAInternalAdapter implements Adapter + { + public CDOXAInternalAdapter() + { + } + + public CDOXATransactionImpl getCDOXA() + { + return CDOXATransactionImpl.this; + } + + public Notifier getTarget() + { + return null; + } + + public boolean isAdapterForType(Object type) + { + return false; + } + + public void notifyChanged(Notification notification) + { + switch (notification.getEventType()) + { + case Notification.ADD: + if (notification.getNewValue() instanceof InternalCDOTransaction) + { + CDOXATransactionImpl.this.add((InternalCDOTransaction)notification.getNewValue()); + } + break; + + case Notification.REMOVE: + if (notification.getOldValue() instanceof InternalCDOTransaction) + { + CDOXATransactionImpl.this.remove((InternalCDOTransaction)notification.getNewValue()); + } + break; + } + } + + public void setTarget(Notifier newTarget) + { + } + } +} |