/** * Copyright (c) 2004 - 2009 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 */ 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.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.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.messages.Messages; import org.eclipse.emf.cdo.spi.common.model.InternalCDOPackageUnit; import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevisionDelta; import org.eclipse.emf.cdo.transaction.CDOConflictResolver; 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.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.util.CompletePackageClosure; import org.eclipse.emf.internal.cdo.util.FSMUtil; import org.eclipse.emf.internal.cdo.util.IPackageClosure; 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.InternalCDOObject; import org.eclipse.emf.spi.cdo.InternalCDOSession; import org.eclipse.emf.spi.cdo.InternalCDOTransaction; import org.eclipse.emf.spi.cdo.CDOSessionProtocol.CommitTransactionResult; 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; import java.util.concurrent.locks.ReentrantLock; /** * @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 handlers = new ArrayList(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 createOptions() { 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 getConflicts() { Set conflicts = new HashSet(); 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 conflicts = getConflicts(); handleConflicts(conflicts, resolvers); } public void handleConflicts(Set conflicts) { handleConflicts(conflicts, options().getConflictResolvers()); } private void handleConflicts(Set conflicts, CDOConflictResolver[] resolvers) { if (resolvers.length == 0) { return; } // Remember original state to be able to restore it after an exception List states = new ArrayList(conflicts.size()); List revisions = new ArrayList(conflicts.size()); for (CDOObject conflict : conflicts) { states.add(conflict.cdoState()); revisions.add(conflict.cdoRevision()); } int resolved = 0; try { Set remaining = new HashSet(conflicts); for (CDOConflictResolver resolver : resolvers) { resolver.resolveConflicts(Collections.unmodifiableSet(remaining)); for (Iterator it = remaining.iterator(); it.hasNext();) { CDOObject object = it.next(); if (!object.cdoConflict()) { ++resolved; it.remove(); } } } } catch (Exception ex) { // Restore original state Iterator state = states.iterator(); Iterator 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 = getResourceNodeID(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 names = CDOURIUtil.analyzePath(uri); String resourceName = names.isEmpty() ? null : names.remove(names.size() - 1); CDOResourceFolder folder = getOrCreateResourceFolder(names); attachNewResourceNode(folder, resourceName, resource); } /** * @return never null; * @since 2.0 */ public CDOResourceFolder getOrCreateResourceFolder(List 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(MessageFormat.format(Messages.getString("CDOTransactionImpl.0"), node)); //$NON-NLS-1$ } } 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(MessageFormat.format(Messages.getString("CDOTransactionImpl.1"), name)); //$NON-NLS-1$ } return id; } private CDOResourceNode getRootResourceNode(String name, Collection 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(Messages.getString("CDOTransactionImpl.2")); //$NON-NLS-1$ } 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 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 rollbackCompletely(CDOSavepoint savepoint) { Set idsOfNewObjectWithDeltas = new HashSet(); // 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 revisionDeltas = itrSavepoint.getRevisionDeltas(); if (!revisionDeltas.isEmpty()) { for (CDORevisionDelta dirtyObject : revisionDeltas.values()) { if (dirtyObject.getID().isTemporary()) { idsOfNewObjectWithDeltas.add(dirtyObject.getID()); } } } // Rollback all persisted objects Map detachedObjects = itrSavepoint.getDetachedObjects(); if (!detachedObjects.isEmpty()) { for (Entry 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 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 idsOfNewObjectWithDeltas) { lastSavepoint.recalculateSharedDetachedObjects(); Map dirtyObjects = getDirtyObjects(); Map newObjMaps = getNewObjects(); Map newResources = getNewResources(); Map newBaseRevision = getBaseNewObjects(); Map 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 entryNewObject : newObjMaps.entrySet()) { InternalCDOObject object = (InternalCDOObject)entryNewObject.getValue(); // Go back to the previous state cleanObject(object, object.cdoRevision()); object.cdoInternalSetState(CDOState.NEW); } for (Entry 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 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 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(Messages.getString("CDOTransactionImpl.3")); //$NON-NLS-1$ } if (savepoint.getUserTransaction() != this) { throw new IllegalArgumentException(MessageFormat.format(Messages.getString("CDOTransactionImpl.4"), savepoint)); //$NON-NLS-1$ } if (TRACER.isEnabled()) { TRACER.trace("handleRollback()"); //$NON-NLS-1$ } try { if (!savepoint.isValid()) { throw new IllegalArgumentException(MessageFormat.format(Messages.getString("CDOTransactionImpl.6"), savepoint)); //$NON-NLS-1$ } // Use the state lock since rollback mechanism is playing with EObject and CDORevisions. We do not want to // receives notifications during that process! neither the user should callload any objects. ReentrantLock viewLock = getStateLock(); viewLock.lock(); try { // Rollback objects Set 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); } } finally { viewLock.unlock(); } Map 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 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()); //$NON-NLS-1$ } public void registerNew(InternalCDOObject object) { if (TRACER.isEnabled()) { TRACER.format("Registering new object {0}", object); //$NON-NLS-1$ } registerNewPackage(object.eClass().getEPackage()); for (CDOTransactionHandler handler : getHandlers()) { handler.attachingObject(this, object); } if (object instanceof CDOResourceImpl) { registerNew(lastSavepoint.getNewResources(), object); } else { registerNew(lastSavepoint.getNewObjects(), object); } } private void registerNewPackage(EPackage ePackage) { CDOPackageRegistry packageRegistry = getSession().getPackageRegistry(); if (!packageRegistry.containsKey(ePackage.getNsURI())) { packageRegistry.putEPackage(ePackage); } } /** * 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 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); //$NON-NLS-1$ } 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(MessageFormat.format(Messages.getString("CDOTransactionImpl.10"), object)); //$NON-NLS-1$ } if (!dirty) { dirty = true; fireEvent(new StartedEvent()); } } private List analyzeNewPackages() { CDOPackageRegistry packageRegistry = getSession().getPackageRegistry(); Set usedPackages = new HashSet(); Set usedNewPackages = new HashSet(); 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 result = new HashSet(); for (EPackage usedNewPackage : analyzeNewPackages(usedNewPackages, packageRegistry)) { CDOPackageUnit packageUnit = packageRegistry.getPackageUnit(usedNewPackage); result.add(packageUnit); } return new ArrayList(result); } return Collections.emptyList(); } private static List analyzeNewPackages(Collection usedTopLevelPackages, CDOPackageRegistry packageRegistry) { // Determine which of the corresdonding EPackages are new List newPackages = new ArrayList(); 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() { lastSavepoint = firstSavepoint; firstSavepoint.clear(); firstSavepoint.setNextSavepoint(null); firstSavepoint.getSharedDetachedObjects().clear(); dirty = false; conflict = 0; lastTemporaryID = 0; } public Map getDirtyObjects() { checkActive(); return lastSavepoint.getAllDirtyObjects(); } public Map getNewObjects() { checkActive(); return lastSavepoint.getAllNewObjects(); } public Map getNewResources() { checkActive(); return lastSavepoint.getAllNewResources(); } /** * @since 2.0 */ public Map getBaseNewObjects() { checkActive(); return lastSavepoint.getAllBaseNewObjects(); } public Map getRevisionDeltas() { checkActive(); return lastSavepoint.getAllRevisionDeltas(); } /** * @since 2.0 */ public Map 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 newResources; private Map newObjects; private Map dirtyObjects; private Map revisionDeltas; private Map detachedObjects; private List newPackageUnits; public CDOCommitContextImpl() { CDOTransactionImpl transaction = getTransaction(); newResources = transaction.getNewResources(); newObjects = transaction.getNewObjects(); dirtyObjects = transaction.getDirtyObjects(); detachedObjects = transaction.getDetachedObjects(); revisionDeltas = transaction.getRevisionDeltas(); newPackageUnits = transaction.analyzeNewPackages(); } public CDOTransactionImpl getTransaction() { return CDOTransactionImpl.this; } public Map getDirtyObjects() { return dirtyObjects; } public Map getNewObjects() { return newObjects; } public List getNewPackageUnits() { return newPackageUnits; } public Map getNewResources() { return newResources; } public Map getDetachedObjects() { return detachedObjects; } public Map getRevisionDeltas() { return revisionDeltas; } public void preCommit() { if (isDirty()) { if (TRACER.isEnabled()) { TRACER.trace("commit()"); //$NON-NLS-1$ } 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 deltas = getRevisionDeltas().values(); postCommit(getNewResources(), result); postCommit(getNewObjects(), result); postCommit(getDirtyObjects(), result); for (Entry entry : getDetachedObjects().entrySet()) { removeObject(entry.getKey()); } InternalCDOSession session = getSession(); for (CDOPackageUnit newPackageUnit : newPackageUnits) { ((InternalCDOPackageUnit)newPackageUnit).setState(CDOPackageUnit.State.LOADED); } long timeStamp = result.getTimeStamp(); Map dirtyObjects = getDirtyObjects(); Set dirtyIDs = new HashSet(); 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 detachedIDs = new HashSet(getDetachedObjects().keySet()); Collection deltasCopy = new ArrayList(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, newPackageUnits, dirtyIDs, detachedIDs, deltasCopy, getTransaction()); } lastCommitTime = timeStamp; for (CDOTransactionHandler handler : getHandlers()) { handler.committedTransaction(getTransaction(), this); } getChangeSubscriptionManager().committedTransaction(getTransaction(), this); getAdapterManager().committedTransaction(getTransaction(), this); cleanUp(); Map 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()); //$NON-NLS-1$ } } /** * @author Eike Stepper */ private final class FinishedEvent extends Event implements CDOTransactionFinishedEvent { private static final long serialVersionUID = 1L; private Type type; private Map idMappings; private FinishedEvent(Type type, Map idMappings) { this.type = type; this.idMappings = idMappings; } public Type getType() { return type; } public Map getIDMappings() { return idMappings; } @Override public String toString() { return MessageFormat.format("CDOTransactionFinishedEvent[source={0}, type={1}, idMappings={2}]", getSource(), //$NON-NLS-1$ 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}]", //$NON-NLS-1$ 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); //$NON-NLS-1$ } } /** * @author Eike Stepper * @since 2.0 */ protected final class OptionsImpl extends CDOViewImpl.OptionsImpl implements CDOTransaction.Options { private List conflictResolvers = new ArrayList(); 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(Messages.getString("CDOTransactionImpl.17")); //$NON-NLS-1$ } 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); } } } }