| /* |
| * Copyright (c) 2009-2013, 2016, 2017, 2019 Eike Stepper (Loehne, Germany) and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Simon McDuff - initial API and implementation |
| * Eike Stepper - maintenance |
| * Simon McDuff - bug 204890 |
| */ |
| package org.eclipse.emf.internal.cdo.transaction; |
| |
| import org.eclipse.emf.cdo.CDOObject; |
| import org.eclipse.emf.cdo.common.branch.CDOBranchVersion; |
| import org.eclipse.emf.cdo.common.commit.CDOChangeSetData; |
| import org.eclipse.emf.cdo.common.id.CDOID; |
| import org.eclipse.emf.cdo.common.id.CDOIDUtil; |
| import org.eclipse.emf.cdo.common.revision.CDOIDAndVersion; |
| import org.eclipse.emf.cdo.common.revision.CDORevision; |
| import org.eclipse.emf.cdo.common.revision.CDORevisionKey; |
| import org.eclipse.emf.cdo.common.revision.delta.CDOFeatureDelta; |
| import org.eclipse.emf.cdo.common.revision.delta.CDORevisionDelta; |
| import org.eclipse.emf.cdo.internal.common.commit.CDOChangeSetDataImpl; |
| import org.eclipse.emf.cdo.internal.common.revision.delta.CDORevisionDeltaImpl; |
| import org.eclipse.emf.cdo.spi.common.revision.InternalCDOFeatureDelta; |
| |
| import org.eclipse.net4j.util.collection.MultiMap; |
| import org.eclipse.net4j.util.lifecycle.LifecycleUtil; |
| |
| import org.eclipse.emf.spi.cdo.CDOTransactionStrategy; |
| import org.eclipse.emf.spi.cdo.InternalCDOSavepoint; |
| import org.eclipse.emf.spi.cdo.InternalCDOTransaction; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| import java.util.concurrent.ConcurrentMap; |
| |
| /** |
| * @author Simon McDuff |
| * @since 2.0 |
| */ |
| public class CDOSavepointImpl extends CDOUserSavepointImpl implements InternalCDOSavepoint |
| { |
| private final InternalCDOTransaction transaction; |
| |
| private Map<CDOID, CDORevision> baseNewObjects = CDOIDUtil.createMap(); |
| |
| private Map<CDOID, CDOObject> newObjects = CDOIDUtil.createMap(); |
| |
| // Bug 283985 (Re-attachment) |
| private Map<CDOID, CDOObject> reattachedObjects = CDOIDUtil.createMap(); |
| |
| private Map<CDOID, CDOObject> detachedObjects = new HashMap<CDOID, CDOObject>() |
| { |
| private static final long serialVersionUID = 1L; |
| |
| @Override |
| public CDOObject put(CDOID key, CDOObject object) |
| { |
| synchronized (transaction.getViewMonitor()) |
| { |
| transaction.lockView(); |
| |
| try |
| { |
| baseNewObjects.remove(key); |
| newObjects.remove(key); |
| reattachedObjects.remove(key); |
| dirtyObjects.remove(key); |
| revisionDeltas.remove(key); |
| return super.put(key, object); |
| } |
| finally |
| { |
| transaction.unlockView(); |
| } |
| } |
| } |
| }; |
| |
| private Map<CDOID, CDOObject> dirtyObjects = CDOIDUtil.createMap(); |
| |
| private Map<CDOID, CDORevisionDelta> revisionDeltas = new HashMap<CDOID, CDORevisionDelta>() |
| { |
| private static final long serialVersionUID = 1L; |
| |
| @Override |
| public CDORevisionDelta put(CDOID id, CDORevisionDelta delta) |
| { |
| transaction.clearResourcePathCacheIfNecessary(delta); |
| return super.put(id, delta); |
| } |
| |
| @Override |
| public void putAll(Map<? extends CDOID, ? extends CDORevisionDelta> m) |
| { |
| throw new UnsupportedOperationException(); |
| } |
| }; |
| |
| private boolean wasDirty; |
| |
| public CDOSavepointImpl(InternalCDOTransaction transaction, InternalCDOSavepoint lastSavepoint) |
| { |
| super(transaction, lastSavepoint); |
| this.transaction = transaction; |
| wasDirty = transaction.isDirty(); |
| } |
| |
| @Override |
| public InternalCDOTransaction getTransaction() |
| { |
| return (InternalCDOTransaction)super.getTransaction(); |
| } |
| |
| @Override |
| public InternalCDOSavepoint getFirstSavePoint() |
| { |
| synchronized (transaction.getViewMonitor()) |
| { |
| transaction.lockView(); |
| |
| try |
| { |
| return (InternalCDOSavepoint)super.getFirstSavePoint(); |
| } |
| finally |
| { |
| transaction.unlockView(); |
| } |
| } |
| } |
| |
| @Override |
| public InternalCDOSavepoint getPreviousSavepoint() |
| { |
| synchronized (transaction.getViewMonitor()) |
| { |
| transaction.lockView(); |
| |
| try |
| { |
| return (InternalCDOSavepoint)super.getPreviousSavepoint(); |
| } |
| finally |
| { |
| transaction.unlockView(); |
| } |
| } |
| } |
| |
| @Override |
| public InternalCDOSavepoint getNextSavepoint() |
| { |
| synchronized (transaction.getViewMonitor()) |
| { |
| transaction.lockView(); |
| |
| try |
| { |
| return (InternalCDOSavepoint)super.getNextSavepoint(); |
| } |
| finally |
| { |
| transaction.unlockView(); |
| } |
| } |
| } |
| |
| @Override |
| public void clear() |
| { |
| synchronized (transaction.getViewMonitor()) |
| { |
| transaction.lockView(); |
| |
| try |
| { |
| newObjects.clear(); |
| dirtyObjects.clear(); |
| revisionDeltas.clear(); |
| baseNewObjects.clear(); |
| detachedObjects.clear(); |
| reattachedObjects.clear(); |
| } |
| finally |
| { |
| transaction.unlockView(); |
| } |
| } |
| } |
| |
| @Override |
| public boolean wasDirty() |
| { |
| return wasDirty; |
| } |
| |
| @Override |
| public Map<CDOID, CDOObject> getNewObjects() |
| { |
| return newObjects; |
| } |
| |
| @Override |
| public Map<CDOID, CDOObject> getDetachedObjects() |
| { |
| return detachedObjects; |
| } |
| |
| // Bug 283985 (Re-attachment) |
| @Override |
| public Map<CDOID, CDOObject> getReattachedObjects() |
| { |
| return reattachedObjects; |
| } |
| |
| @Override |
| public Map<CDOID, CDOObject> getDirtyObjects() |
| { |
| return dirtyObjects; |
| } |
| |
| @Override |
| @Deprecated |
| public Set<CDOID> getSharedDetachedObjects() |
| { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| @Deprecated |
| public void recalculateSharedDetachedObjects() |
| { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| @Deprecated |
| public ConcurrentMap<CDOID, CDORevisionDelta> getRevisionDeltas() |
| { |
| return new ConcurrentMap<CDOID, CDORevisionDelta>() |
| { |
| @Override |
| public int size() |
| { |
| return revisionDeltas.size(); |
| } |
| |
| @Override |
| public boolean isEmpty() |
| { |
| return revisionDeltas.isEmpty(); |
| } |
| |
| @Override |
| public boolean containsKey(Object key) |
| { |
| return revisionDeltas.containsKey(key); |
| } |
| |
| @Override |
| public boolean containsValue(Object value) |
| { |
| return revisionDeltas.containsValue(value); |
| } |
| |
| @Override |
| public CDORevisionDelta get(Object key) |
| { |
| return revisionDeltas.get(key); |
| } |
| |
| @Override |
| public CDORevisionDelta put(CDOID key, CDORevisionDelta value) |
| { |
| return revisionDeltas.put(key, value); |
| } |
| |
| @Override |
| public CDORevisionDelta remove(Object key) |
| { |
| return revisionDeltas.remove(key); |
| } |
| |
| @Override |
| public void putAll(Map<? extends CDOID, ? extends CDORevisionDelta> m) |
| { |
| revisionDeltas.putAll(m); |
| } |
| |
| @Override |
| public void clear() |
| { |
| revisionDeltas.clear(); |
| } |
| |
| @Override |
| public Set<CDOID> keySet() |
| { |
| return revisionDeltas.keySet(); |
| } |
| |
| @Override |
| public Collection<CDORevisionDelta> values() |
| { |
| return revisionDeltas.values(); |
| } |
| |
| @Override |
| public Set<java.util.Map.Entry<CDOID, CDORevisionDelta>> entrySet() |
| { |
| return revisionDeltas.entrySet(); |
| } |
| |
| @Override |
| public boolean equals(Object o) |
| { |
| return revisionDeltas.equals(o); |
| } |
| |
| @Override |
| public int hashCode() |
| { |
| return revisionDeltas.hashCode(); |
| } |
| |
| @Override |
| public CDORevisionDelta putIfAbsent(CDOID key, CDORevisionDelta value) |
| { |
| return null; |
| } |
| |
| @Override |
| public boolean remove(Object key, Object value) |
| { |
| return false; |
| } |
| |
| @Override |
| public boolean replace(CDOID key, CDORevisionDelta oldValue, CDORevisionDelta newValue) |
| { |
| return false; |
| } |
| |
| @Override |
| public CDORevisionDelta replace(CDOID key, CDORevisionDelta value) |
| { |
| return null; |
| } |
| }; |
| } |
| |
| @Override |
| public Map<CDOID, CDORevisionDelta> getRevisionDeltas2() |
| { |
| return revisionDeltas; |
| } |
| |
| @Override |
| public CDOChangeSetData getChangeSetData() |
| { |
| synchronized (transaction.getViewMonitor()) |
| { |
| transaction.lockView(); |
| |
| try |
| { |
| return createChangeSetData(newObjects, revisionDeltas, detachedObjects); |
| } |
| finally |
| { |
| transaction.unlockView(); |
| } |
| } |
| } |
| |
| @Override |
| public CDOChangeSetData getAllChangeSetData() |
| { |
| synchronized (transaction.getViewMonitor()) |
| { |
| transaction.lockView(); |
| |
| try |
| { |
| return createChangeSetData(getAllNewObjects(), getAllRevisionDeltas(), getAllDetachedObjects()); |
| } |
| finally |
| { |
| transaction.unlockView(); |
| } |
| } |
| } |
| |
| private CDOChangeSetData createChangeSetData(Map<CDOID, CDOObject> newObjects, Map<CDOID, CDORevisionDelta> revisionDeltas, |
| Map<CDOID, CDOObject> detachedObjects) |
| { |
| List<CDOIDAndVersion> newList = new ArrayList<>(newObjects.size()); |
| for (CDOObject object : newObjects.values()) |
| { |
| newList.add(object.cdoRevision()); |
| } |
| |
| List<CDORevisionKey> changedList = new ArrayList<>(revisionDeltas.size()); |
| for (CDORevisionDelta delta : revisionDeltas.values()) |
| { |
| changedList.add(delta); |
| } |
| |
| List<CDOIDAndVersion> detachedList = new ArrayList<>(detachedObjects.size()); |
| for (CDOID id : detachedObjects.keySet()) |
| { |
| detachedList.add(CDOIDUtil.createIDAndVersion(id, CDOBranchVersion.UNSPECIFIED_VERSION)); |
| } |
| |
| return new CDOChangeSetDataImpl(newList, changedList, detachedList); |
| } |
| |
| @Override |
| public Map<CDOID, CDORevision> getBaseNewObjects() |
| { |
| return baseNewObjects; |
| } |
| |
| /** |
| * Return the list of new objects from this point. |
| */ |
| @Override |
| public Map<CDOID, CDOObject> getAllDirtyObjects() |
| { |
| synchronized (transaction.getViewMonitor()) |
| { |
| transaction.lockView(); |
| |
| try |
| { |
| if (getPreviousSavepoint() == null) |
| { |
| return Collections.unmodifiableMap(getDirtyObjects()); |
| } |
| |
| MultiMap.ListBased<CDOID, CDOObject> dirtyObjects = new MultiMap.ListBased<>(); |
| for (InternalCDOSavepoint savepoint = this; savepoint != null; savepoint = savepoint.getPreviousSavepoint()) |
| { |
| dirtyObjects.getDelegates().add(savepoint.getDirtyObjects()); |
| } |
| |
| return dirtyObjects; |
| } |
| finally |
| { |
| transaction.unlockView(); |
| } |
| } |
| } |
| |
| /** |
| * Return the list of new objects from this point without objects that are removed. |
| */ |
| @Override |
| public Map<CDOID, CDOObject> getAllNewObjects() |
| { |
| synchronized (transaction.getViewMonitor()) |
| { |
| transaction.lockView(); |
| |
| try |
| { |
| if (getPreviousSavepoint() == null) |
| { |
| return Collections.unmodifiableMap(getNewObjects()); |
| } |
| |
| Map<CDOID, CDOObject> newObjects = CDOIDUtil.createMap(); |
| for (InternalCDOSavepoint savepoint = getFirstSavePoint(); savepoint != null; savepoint = savepoint.getNextSavepoint()) |
| { |
| newObjects.putAll(savepoint.getNewObjects()); |
| |
| for (CDOID removedID : savepoint.getDetachedObjects().keySet()) |
| { |
| newObjects.remove(removedID); |
| } |
| } |
| |
| return newObjects; |
| } |
| finally |
| { |
| transaction.unlockView(); |
| } |
| } |
| } |
| |
| /** |
| * @since 2.0 |
| */ |
| @Override |
| public Map<CDOID, CDORevision> getAllBaseNewObjects() |
| { |
| synchronized (transaction.getViewMonitor()) |
| { |
| transaction.lockView(); |
| |
| try |
| { |
| if (getPreviousSavepoint() == null) |
| { |
| return Collections.unmodifiableMap(getBaseNewObjects()); |
| } |
| |
| MultiMap.ListBased<CDOID, CDORevision> newObjects = new MultiMap.ListBased<>(); |
| for (InternalCDOSavepoint savepoint = this; savepoint != null; savepoint = savepoint.getPreviousSavepoint()) |
| { |
| newObjects.getDelegates().add(savepoint.getBaseNewObjects()); |
| } |
| |
| return newObjects; |
| } |
| finally |
| { |
| transaction.unlockView(); |
| } |
| } |
| } |
| |
| /** |
| * Return the list of all deltas without objects that are removed. |
| */ |
| @Override |
| public Map<CDOID, CDORevisionDelta> getAllRevisionDeltas() |
| { |
| synchronized (transaction.getViewMonitor()) |
| { |
| transaction.lockView(); |
| |
| try |
| { |
| if (getPreviousSavepoint() == null) |
| { |
| return Collections.unmodifiableMap(getRevisionDeltas2()); |
| } |
| |
| InternalCDOSavepoint firstSavePoint = getFirstSavePoint(); |
| boolean multiSavepoint = firstSavePoint.getNextSavepoint() != null; |
| Set<CDOFeatureDelta> originalFeatureDeltas = multiSavepoint ? new HashSet<>() : null; |
| |
| // We need to combine the results for all deltas in different savepoints. |
| Map<CDOID, CDORevisionDelta> allRevisionDeltas = CDOIDUtil.createMap(); |
| |
| for (InternalCDOSavepoint savepoint = firstSavePoint; savepoint != null; savepoint = savepoint.getNextSavepoint()) |
| { |
| for (CDORevisionDelta revisionDelta : savepoint.getRevisionDeltas2().values()) |
| { |
| CDOID id = revisionDelta.getID(); |
| if (!isNewObject(id)) |
| { |
| CDORevisionDeltaImpl oldRevisionDelta = (CDORevisionDeltaImpl)allRevisionDeltas.get(id); |
| if (oldRevisionDelta == null) |
| { |
| if (multiSavepoint) |
| { |
| for (CDOFeatureDelta featureDelta : revisionDelta.getFeatureDeltas()) |
| { |
| originalFeatureDeltas.add(featureDelta); |
| } |
| } |
| |
| allRevisionDeltas.put(id, revisionDelta.copy()); |
| } |
| else |
| { |
| for (CDOFeatureDelta featureDelta : revisionDelta.getFeatureDeltas()) |
| { |
| if (!multiSavepoint || originalFeatureDeltas.add(featureDelta)) |
| { |
| CDOFeatureDelta copy = ((InternalCDOFeatureDelta)featureDelta).copy(); |
| oldRevisionDelta.addFeatureDelta(copy, null); |
| } |
| } |
| } |
| } |
| } |
| |
| Set<CDOID> reattachedObjects = savepoint.getReattachedObjects().keySet(); |
| for (CDOID detachedID : savepoint.getDetachedObjects().keySet()) |
| { |
| if (!reattachedObjects.contains(detachedID)) |
| { |
| allRevisionDeltas.remove(detachedID); |
| } |
| } |
| } |
| |
| return Collections.unmodifiableMap(allRevisionDeltas); |
| } |
| finally |
| { |
| transaction.unlockView(); |
| } |
| } |
| } |
| |
| @Override |
| public Map<CDOID, CDOObject> getAllDetachedObjects() |
| { |
| synchronized (transaction.getViewMonitor()) |
| { |
| transaction.lockView(); |
| |
| try |
| { |
| if (getPreviousSavepoint() == null && getReattachedObjects().isEmpty()) |
| { |
| return Collections.unmodifiableMap(getDetachedObjects()); |
| } |
| |
| Map<CDOID, CDOObject> detachedObjects = CDOIDUtil.createMap(); |
| for (InternalCDOSavepoint savepoint = getFirstSavePoint(); savepoint != null; savepoint = savepoint.getNextSavepoint()) |
| { |
| for (Entry<CDOID, CDOObject> entry : savepoint.getDetachedObjects().entrySet()) |
| { |
| CDOID detachedID = entry.getKey(); |
| if (!isNewObject(detachedID)) |
| { |
| CDOObject detachedObject = entry.getValue(); |
| detachedObjects.put(detachedID, detachedObject); |
| } |
| } |
| |
| Map<CDOID, CDOObject> reattachedObjects = savepoint.getReattachedObjects(); |
| for (CDOID reattachedID : reattachedObjects.keySet()) |
| { |
| detachedObjects.remove(reattachedID); |
| } |
| } |
| |
| return detachedObjects; |
| } |
| finally |
| { |
| transaction.unlockView(); |
| } |
| } |
| } |
| |
| @Override |
| public boolean isNewObject(CDOID id) |
| { |
| if (id.isTemporary()) |
| { |
| return true; |
| } |
| |
| synchronized (transaction.getViewMonitor()) |
| { |
| transaction.lockView(); |
| |
| try |
| { |
| for (InternalCDOSavepoint savepoint = this; savepoint != null; savepoint = savepoint.getPreviousSavepoint()) |
| { |
| if (savepoint.getNewObjects().containsKey(id)) |
| { |
| return true; |
| } |
| } |
| } |
| finally |
| { |
| transaction.unlockView(); |
| } |
| } |
| |
| return false; |
| } |
| |
| // TODO Not sure if this new implementation is needed. The existing one passes all tests. |
| // public boolean isNewObject(CDOID id) |
| // { |
| // if (id.isTemporary()) |
| // { |
| // return true; |
| // } |
| // |
| // boolean isNew = false; |
| // boolean wasNew = false; |
| // synchronized (transaction) // TODO synchronized -> transaction.getViewLock().lock() |
| // { |
| // for (InternalCDOSavepoint savepoint = this; savepoint != null; savepoint = savepoint.getPreviousSavepoint()) |
| // { |
| // if (savepoint.getNewObjects().containsKey(id)) |
| // { |
| // isNew = true; |
| // wasNew = true; |
| // } |
| // |
| // if (isNew && savepoint.getDetachedObjects().containsKey(id)) |
| // { |
| // isNew = false; |
| // } |
| // |
| // if (!isNew && wasNew && savepoint.getReattachedObjects().containsKey(id)) |
| // { |
| // isNew = true; |
| // } |
| // } |
| // } |
| // |
| // return isNew; |
| // } |
| |
| @Override |
| public CDOObject getDetachedObject(CDOID id) |
| { |
| synchronized (transaction.getViewMonitor()) |
| { |
| transaction.lockView(); |
| |
| try |
| { |
| for (InternalCDOSavepoint savepoint = this; savepoint != null; savepoint = savepoint.getPreviousSavepoint()) |
| { |
| Map<CDOID, CDOObject> reattachedObjects = savepoint.getReattachedObjects(); |
| if (!reattachedObjects.isEmpty()) |
| { |
| CDOObject object = reattachedObjects.get(id); |
| if (object != null) |
| { |
| return null; |
| } |
| } |
| |
| Map<CDOID, CDOObject> detachedObjects = savepoint.getDetachedObjects(); |
| if (!detachedObjects.isEmpty()) |
| { |
| CDOObject object = detachedObjects.get(id); |
| if (object != null) |
| { |
| return object; |
| } |
| } |
| } |
| } |
| finally |
| { |
| transaction.unlockView(); |
| } |
| } |
| |
| return null; |
| } |
| |
| @Override |
| public CDOObject getDirtyObject(CDOID id) |
| { |
| synchronized (transaction.getViewMonitor()) |
| { |
| transaction.lockView(); |
| |
| try |
| { |
| for (InternalCDOSavepoint savepoint = this; savepoint != null; savepoint = savepoint.getPreviousSavepoint()) |
| { |
| Map<CDOID, CDOObject> dirtyObjects = savepoint.getDirtyObjects(); |
| if (!dirtyObjects.isEmpty()) |
| { |
| CDOObject object = dirtyObjects.get(id); |
| if (object != null) |
| { |
| return object; |
| } |
| } |
| } |
| } |
| finally |
| { |
| transaction.unlockView(); |
| } |
| } |
| |
| return null; |
| } |
| |
| @Override |
| public void rollback() |
| { |
| synchronized (transaction.getViewMonitor()) |
| { |
| transaction.lockView(); |
| |
| try |
| { |
| InternalCDOTransaction transaction = getTransaction(); |
| LifecycleUtil.checkActive(transaction); |
| |
| CDOTransactionStrategy transactionStrategy = transaction.getTransactionStrategy(); |
| transactionStrategy.rollback(transaction, this); |
| } |
| finally |
| { |
| transaction.unlockView(); |
| } |
| } |
| } |
| } |