/* * Copyright (c) 2009-2016 Eike Stepper (Berlin, Germany) and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Eike Stepper - initial API and implementation * Simon McDuff - bug 201266 * Simon McDuff - bug 230832 */ package org.eclipse.emf.cdo.internal.common.revision; import org.eclipse.emf.cdo.common.branch.CDOBranch; import org.eclipse.emf.cdo.common.branch.CDOBranchManager; import org.eclipse.emf.cdo.common.branch.CDOBranchPoint; import org.eclipse.emf.cdo.common.branch.CDOBranchPointRange; import org.eclipse.emf.cdo.common.branch.CDOBranchVersion; import org.eclipse.emf.cdo.common.id.CDOID; import org.eclipse.emf.cdo.common.revision.CDORevision; import org.eclipse.emf.cdo.common.revision.CDORevisionCache; import org.eclipse.emf.cdo.common.revision.CDORevisionFactory; import org.eclipse.emf.cdo.common.revision.CDORevisionHandler; import org.eclipse.emf.cdo.common.revision.CDORevisionManager; import org.eclipse.emf.cdo.common.revision.CDORevisionUtil; import org.eclipse.emf.cdo.common.revision.CDORevisionsLoadedEvent; import org.eclipse.emf.cdo.internal.common.bundle.OM; import org.eclipse.emf.cdo.spi.common.branch.CDOBranchUtil; import org.eclipse.emf.cdo.spi.common.revision.DetachedCDORevision; import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevision; import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevisionCache; import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevisionManager; import org.eclipse.emf.cdo.spi.common.revision.PointerCDORevision; import org.eclipse.emf.cdo.spi.common.revision.RevisionInfo; import org.eclipse.emf.cdo.spi.common.revision.SyntheticCDORevision; import org.eclipse.net4j.util.ReflectUtil.ExcludeFromDump; import org.eclipse.net4j.util.event.Event; import org.eclipse.net4j.util.lifecycle.Lifecycle; import org.eclipse.net4j.util.lifecycle.LifecycleUtil; import org.eclipse.net4j.util.om.trace.ContextTracer; import org.eclipse.emf.ecore.EClass; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; /** * @author Eike Stepper */ public class CDORevisionManagerImpl extends Lifecycle implements InternalCDORevisionManager { private static final ContextTracer TRACER = new ContextTracer(OM.DEBUG_REVISION, CDORevisionManagerImpl.class); private boolean supportingAudits; private boolean supportingBranches; private RevisionLoader revisionLoader; private RevisionLocker revisionLocker; private CDORevisionFactory factory; private InternalCDORevisionCache cache; @ExcludeFromDump private transient Object loadAndAddLock = new Object() { @Override public String toString() { return "LoadAndAddLock"; //$NON-NLS-1$ } }; @ExcludeFromDump private transient Object reviseLock = new Object() { @Override public String toString() { return "ReviseLock"; //$NON-NLS-1$ } }; public CDORevisionManagerImpl() { } public boolean isSupportingAudits() { return supportingAudits; } public void setSupportingAudits(boolean on) { checkInactive(); supportingAudits = on; } public boolean isSupportingBranches() { return supportingBranches; } public void setSupportingBranches(boolean on) { checkInactive(); supportingBranches = on; } public RevisionLoader getRevisionLoader() { return revisionLoader; } public void setRevisionLoader(RevisionLoader revisionLoader) { checkInactive(); this.revisionLoader = revisionLoader; } public RevisionLocker getRevisionLocker() { return revisionLocker; } public void setRevisionLocker(RevisionLocker revisionLocker) { checkInactive(); this.revisionLocker = revisionLocker; } public CDORevisionFactory getFactory() { return factory; } public void setFactory(CDORevisionFactory factory) { checkInactive(); this.factory = factory; } public InternalCDORevisionCache getCache() { return cache; } public void setCache(CDORevisionCache cache) { checkInactive(); this.cache = (InternalCDORevisionCache)cache; } public EClass getObjectType(CDOID id, CDOBranchManager branchManagerForLoadOnDemand) { EClass type = cache.getObjectType(id); if (type == null && branchManagerForLoadOnDemand != null) { CDOBranch mainBranch = branchManagerForLoadOnDemand.getMainBranch(); CDORevision revision = getRevisionByVersion(id, mainBranch.getVersion(CDOBranchVersion.FIRST_VERSION), 0, true); if (revision != null) { type = revision.getEClass(); } } return type; } public EClass getObjectType(CDOID id) { return getObjectType(id, null); } public boolean containsRevision(CDOID id, CDOBranchPoint branchPoint) { if (supportingBranches) { return getRevision(id, branchPoint, CDORevision.UNCHUNKED, CDORevision.DEPTH_NONE, false, null) != null; } return getCachedRevision(id, branchPoint) != null; } public boolean containsRevisionByVersion(CDOID id, CDOBranchVersion branchVersion) { return cache.getRevisionByVersion(id, branchVersion) != null; } public void reviseLatest(CDOID id, CDOBranch branch) { acquireAtomicRequestLock(reviseLock); try { InternalCDORevision revision = (InternalCDORevision)cache.getRevision(id, branch.getHead()); if (revision != null) { CDOBranchVersion version = branch.getVersion(revision.getVersion()); cache.removeRevision(id, version); } } finally { releaseAtomicRequestLock(reviseLock); } } public void reviseVersion(CDOID id, CDOBranchVersion branchVersion, long timeStamp) { acquireAtomicRequestLock(reviseLock); try { InternalCDORevision revision = getCachedRevisionByVersion(id, branchVersion); if (revision != null) { if (timeStamp == CDORevision.UNSPECIFIED_DATE || !supportingAudits) { cache.removeRevision(id, branchVersion); } revision.setRevised(timeStamp - 1); } } finally { releaseAtomicRequestLock(reviseLock); } } public InternalCDORevision getRevisionByVersion(CDOID id, CDOBranchVersion branchVersion, int referenceChunk, boolean loadOnDemand) { checkArg(branchVersion.getVersion() >= CDOBranchVersion.FIRST_VERSION, "Invalid version: " + branchVersion.getVersion()); acquireAtomicRequestLock(loadAndAddLock); try { InternalCDORevision revision = getCachedRevisionByVersion(id, branchVersion); if (revision == null) { if (loadOnDemand) { if (TRACER.isEnabled()) { TRACER.format("Loading revision {0} from {1}", id, branchVersion); //$NON-NLS-1$ } revision = revisionLoader.loadRevisionByVersion(id, branchVersion, referenceChunk); addRevision(revision); } } return revision; } finally { releaseAtomicRequestLock(loadAndAddLock); } } public CDOBranchPointRange getObjectLifetime(CDOID id, CDOBranchPoint branchPoint) { if (revisionLoader instanceof RevisionLoader2) { RevisionLoader2 revisionLoader2 = (RevisionLoader2)revisionLoader; return revisionLoader2.loadObjectLifetime(id, branchPoint); } return null; } public InternalCDORevision getRevision(CDOID id, CDOBranchPoint branchPoint, int referenceChunk, int prefetchDepth, boolean loadOnDemand) { return getRevision(id, branchPoint, referenceChunk, prefetchDepth, loadOnDemand, null); } public InternalCDORevision getRevision(CDOID id, CDOBranchPoint branchPoint, int referenceChunk, int prefetchDepth, boolean loadOnDemand, SyntheticCDORevision[] synthetics) { List<CDOID> ids = Collections.singletonList(id); CDORevision result = getRevisions(ids, branchPoint, referenceChunk, prefetchDepth, loadOnDemand, synthetics).get(0); return (InternalCDORevision)result; } public List<CDORevision> getRevisions(List<CDOID> ids, CDOBranchPoint branchPoint, int referenceChunk, int prefetchDepth, boolean loadOnDemand) { return getRevisions(ids, branchPoint, referenceChunk, prefetchDepth, loadOnDemand, null); } public List<CDORevision> getRevisions(List<CDOID> ids, CDOBranchPoint branchPoint, int referenceChunk, int prefetchDepth, boolean loadOnDemand, SyntheticCDORevision[] synthetics) { RevisionInfo[] infos = new RevisionInfo[ids.size()]; List<RevisionInfo> infosToLoad = createRevisionInfos(ids, branchPoint, prefetchDepth, loadOnDemand, infos); if (infosToLoad != null) { List<? extends CDORevision> additionalLoadedRevisions // = loadRevisions(infosToLoad, branchPoint, referenceChunk, prefetchDepth); List<? extends CDORevision> primaryLoadedRevisions // = getResultsAndSynthetics(infosToLoad.toArray(new RevisionInfo[0]), null); if (primaryLoadedRevisions != null && !primaryLoadedRevisions.isEmpty() || additionalLoadedRevisions != null && !additionalLoadedRevisions.isEmpty()) { if (primaryLoadedRevisions == null) { primaryLoadedRevisions = Collections.emptyList(); } if (additionalLoadedRevisions == null) { additionalLoadedRevisions = Collections.emptyList(); } fireEvent(new RevisionsLoadedEvent(this, primaryLoadedRevisions, additionalLoadedRevisions, prefetchDepth)); } } return getResultsAndSynthetics(infos, synthetics); } public void handleRevisions(EClass eClass, CDOBranch branch, boolean exactBranch, long timeStamp, boolean exactTime, CDORevisionHandler handler) { revisionLoader.handleRevisions(eClass, branch, exactBranch, timeStamp, exactTime, handler); } @Override public String toString() { return "RevisionManager"; } private List<RevisionInfo> createRevisionInfos(List<CDOID> ids, CDOBranchPoint branchPoint, int prefetchDepth, boolean loadOnDemand, RevisionInfo[] infos) { List<RevisionInfo> infosToLoad = null; Iterator<CDOID> idIterator = ids.iterator(); for (int i = 0; i < infos.length; i++) { CDOID id = idIterator.next(); RevisionInfo info = createRevisionInfo(id, branchPoint); infos[i] = info; if (loadOnDemand && (prefetchDepth != CDORevision.DEPTH_NONE || info.isLoadNeeded())) { if (infosToLoad == null) { infosToLoad = new ArrayList<RevisionInfo>(1); } infosToLoad.add(info); } } return infosToLoad; } private RevisionInfo createRevisionInfo(CDOID id, CDOBranchPoint branchPoint) { InternalCDORevision revision = getCachedRevision(id, branchPoint); if (revision != null) { return createRevisionInfoAvailable(revision, branchPoint); } if (supportingBranches) { revision = getCachedRevisionRecursively(id, branchPoint); if (revision != null) { return createRevisionInfoAvailable(revision, branchPoint); } } return createRevisionInfoMissing(id, branchPoint); } private RevisionInfo.Available createRevisionInfoAvailable(InternalCDORevision revision, CDOBranchPoint requestedBranchPoint) { if (revision instanceof PointerCDORevision) { PointerCDORevision pointer = (PointerCDORevision)revision; CDOBranchVersion target = pointer.getTarget(); InternalCDORevision targetRevision = target == null ? null : getCachedRevisionByVersion(pointer.getID(), target); if (targetRevision != null) { target = targetRevision; } return new RevisionInfo.Available.Pointer(pointer.getID(), requestedBranchPoint, pointer, target); } if (revision instanceof DetachedCDORevision) { DetachedCDORevision detached = (DetachedCDORevision)revision; return new RevisionInfo.Available.Detached(detached.getID(), requestedBranchPoint, detached); } return new RevisionInfo.Available.Normal(revision.getID(), requestedBranchPoint, revision); } private RevisionInfo.Missing createRevisionInfoMissing(CDOID id, CDOBranchPoint requestedBranchPoint) { return new RevisionInfo.Missing(id, requestedBranchPoint); } protected List<InternalCDORevision> loadRevisions(List<RevisionInfo> infosToLoad, CDOBranchPoint branchPoint, int referenceChunk, int prefetchDepth) { acquireAtomicRequestLock(loadAndAddLock); try { List<InternalCDORevision> additionalRevisions = null; List<RevisionInfo> additionalRevisionInfos = // revisionLoader.loadRevisions(infosToLoad, branchPoint, referenceChunk, prefetchDepth); if (additionalRevisionInfos != null) { additionalRevisions = new ArrayList<InternalCDORevision>(additionalRevisionInfos.size()); for (RevisionInfo info : additionalRevisionInfos) { info.processResult(this, new ArrayList<CDORevision>(), null, 0); additionalRevisions.add(info.getResult()); } } return additionalRevisions; } finally { releaseAtomicRequestLock(loadAndAddLock); } } private List<CDORevision> getResultsAndSynthetics(RevisionInfo[] infos, SyntheticCDORevision[] synthetics) { List<CDORevision> results = new ArrayList<CDORevision>(infos.length); for (int i = 0; i < infos.length; i++) { RevisionInfo info = infos[i]; info.processResult(this, results, synthetics, i); } return results; } public void addRevision(CDORevision revision) { if (revision != null) { acquireAtomicRequestLock(loadAndAddLock); try { if (revision instanceof PointerCDORevision) { PointerCDORevision pointer = (PointerCDORevision)revision; CDOBranchVersion target = pointer.getTarget(); if (target instanceof InternalCDORevision) { revision = new PointerCDORevision(pointer.getEClass(), pointer.getID(), pointer.getBranch(), pointer.getRevised(), CDOBranchUtil.copyBranchVersion(target)); } } int oldVersion = revision.getVersion() - 1; if (oldVersion >= CDORevision.UNSPECIFIED_VERSION) { CDOID id = revision.getID(); CDOBranchVersion old = revision.getBranch().getVersion(oldVersion); InternalCDORevision oldRevision = getCachedRevisionByVersion(id, old); if (!revision.isHistorical()) { if (oldRevision != null) { oldRevision.setRevised(revision.getTimeStamp() - 1); } else { // Remove last revision from cache, which is not revised InternalCDORevision cachedLatestRevision = getCachedRevision(id, revision); if (cachedLatestRevision != null && !cachedLatestRevision.isHistorical()) { // Found revision is stale. // We cannot revise it now because of lack information, thus remove it from the cache cache.removeRevision(id, cachedLatestRevision); } } } } cache.addRevision(revision); } finally { releaseAtomicRequestLock(loadAndAddLock); } } } @Override protected void doBeforeActivate() throws Exception { super.doBeforeActivate(); if (factory == null) { factory = CDORevisionFactory.DEFAULT; } if (cache == null) { cache = (InternalCDORevisionCache)CDORevisionUtil.createRevisionCache(supportingAudits, supportingBranches); } if (cache instanceof AbstractCDORevisionCache) { String name = revisionLoader.toString(); ((AbstractCDORevisionCache)cache).setName(name); } } @Override protected void doActivate() throws Exception { super.doActivate(); LifecycleUtil.activate(cache); } @Override protected void doDeactivate() throws Exception { LifecycleUtil.deactivate(cache); super.doDeactivate(); } private void acquireAtomicRequestLock(Object key) { if (revisionLocker != null) { revisionLocker.acquireAtomicRequestLock(key); } } private void releaseAtomicRequestLock(Object key) { if (revisionLocker != null) { revisionLocker.releaseAtomicRequestLock(key); } } private InternalCDORevision getCachedRevisionByVersion(CDOID id, CDOBranchVersion branchVersion) { return (InternalCDORevision)cache.getRevisionByVersion(id, branchVersion); } private InternalCDORevision getCachedRevision(CDOID id, CDOBranchPoint branchPoint) { return (InternalCDORevision)cache.getRevision(id, branchPoint); } private InternalCDORevision getCachedRevisionRecursively(CDOID id, CDOBranchPoint branchPoint) { CDOBranch branch = branchPoint.getBranch(); if (!branch.isMainBranch()) { CDOBranchPoint base = branch.getBase(); InternalCDORevision revision = getCachedRevision(id, base); if (revision != null) { return revision; } // Recurse return getCachedRevisionRecursively(id, base); } // Reached main branch return null; } /** * @author Esteban Dugueperoux */ private static class RevisionsLoadedEvent extends Event implements CDORevisionsLoadedEvent { private static final long serialVersionUID = 1L; private List<? extends CDORevision> primaryLoadedRevisions; private List<? extends CDORevision> additionalLoadedRevisions; private int prefetchDepth; public RevisionsLoadedEvent(CDORevisionManager revisionManager, List<? extends CDORevision> primaryLoadedRevisions, List<? extends CDORevision> additionalLoadedRevisions, int prefetchDepth) { super(revisionManager); this.primaryLoadedRevisions = primaryLoadedRevisions; this.additionalLoadedRevisions = additionalLoadedRevisions; this.prefetchDepth = prefetchDepth; } @Override public CDORevisionManager getSource() { return (CDORevisionManager)super.getSource(); } public List<? extends CDORevision> getPrimaryLoadedRevisions() { return primaryLoadedRevisions; } public List<? extends CDORevision> getAdditionalLoadedRevisions() { return additionalLoadedRevisions; } public int getPrefetchDepth() { return prefetchDepth; } } }