| /* |
| * Copyright (c) 2010-2013, 2015, 2016, 2019, 2021, 2022 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: |
| * Eike Stepper - initial API and implementation |
| * Andre Dietisheim - bug 256649 |
| */ |
| package org.eclipse.emf.cdo.internal.common.commit; |
| |
| import org.eclipse.emf.cdo.common.CDOCommonRepository; |
| import org.eclipse.emf.cdo.common.branch.CDOBranch; |
| import org.eclipse.emf.cdo.common.branch.CDOBranchManager; |
| import org.eclipse.emf.cdo.common.branch.CDOBranchPoint; |
| import org.eclipse.emf.cdo.common.commit.CDOCommitData; |
| import org.eclipse.emf.cdo.common.commit.CDOCommitHistory; |
| import org.eclipse.emf.cdo.common.commit.CDOCommitInfo; |
| import org.eclipse.emf.cdo.common.commit.CDOCommitInfoHandler; |
| import org.eclipse.emf.cdo.internal.common.bundle.OM; |
| import org.eclipse.emf.cdo.spi.common.commit.CDOCommitInfoUtil; |
| import org.eclipse.emf.cdo.spi.common.commit.InternalCDOCommitInfoManager; |
| |
| import org.eclipse.net4j.util.collection.CollectionUtil; |
| import org.eclipse.net4j.util.collection.ConcurrentArray; |
| import org.eclipse.net4j.util.event.IListener; |
| import org.eclipse.net4j.util.ref.ReferenceValueMap2; |
| |
| import java.lang.ref.SoftReference; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.WeakHashMap; |
| |
| /** |
| * @author Andre Dietisheim |
| */ |
| public class CDOCommitInfoManagerImpl extends CDOCommitHistoryProviderImpl<CDOBranch, CDOCommitHistory> implements InternalCDOCommitInfoManager |
| { |
| private final Map<Long, CDOCommitInfo> cache; |
| |
| private final Object cacheLock = new Object(); |
| |
| private final Map<CDOBranch, BranchInfoCache> branches = new WeakHashMap<>(); |
| |
| private final ConcurrentArray<CDOCommitInfoHandler> handlers = new ConcurrentArray<CDOCommitInfoHandler>() |
| { |
| @Override |
| protected CDOCommitInfoHandler[] newArray(int length) |
| { |
| return new CDOCommitInfoHandler[length]; |
| } |
| }; |
| |
| private final IListener branchManagerListener = new CDOBranchManager.EventAdapter() |
| { |
| @Override |
| protected void onBranchesDeleted(CDOBranch rootBranch, int[] branchIDs) |
| { |
| synchronized (branches) |
| { |
| for (Iterator<CDOBranch> it = branches.keySet().iterator(); it.hasNext();) |
| { |
| CDOBranch branch = it.next(); |
| if (branch.isDeleted()) |
| { |
| it.remove(); |
| } |
| } |
| } |
| |
| synchronized (cacheLock) |
| { |
| // We need to collect and delete in two loops because cache.entrySet().iterator().remove() is not supported. |
| CollectionUtil.removeAll(cache, (t, commitInfo) -> commitInfo.getBranch().isDeleted()); |
| } |
| } |
| }; |
| |
| private CDOBranchManager branchManager; |
| |
| private CDOCommonRepository repository; |
| |
| private CommitInfoLoader loader; |
| |
| private long lastCommit = CDOBranchPoint.UNSPECIFIED_DATE; |
| |
| public CDOCommitInfoManagerImpl(boolean caching) |
| { |
| if (caching) |
| { |
| cache = new ReferenceValueMap2.Soft<>(); |
| } |
| else |
| { |
| cache = null; |
| } |
| } |
| |
| @Override |
| public CDOCommonRepository getRepository() |
| { |
| return repository; |
| } |
| |
| @Override |
| public void setRepository(CDOCommonRepository repository) |
| { |
| checkInactive(); |
| this.repository = repository; |
| } |
| |
| @Override |
| public CDOBranchManager getBranchManager() |
| { |
| return branchManager; |
| } |
| |
| @Override |
| public void setBranchManager(CDOBranchManager branchManager) |
| { |
| this.branchManager = branchManager; |
| } |
| |
| @Override |
| public CommitInfoLoader getCommitInfoLoader() |
| { |
| return loader; |
| } |
| |
| @Override |
| public void setCommitInfoLoader(CommitInfoLoader commitInfoLoader) |
| { |
| checkInactive(); |
| loader = commitInfoLoader; |
| } |
| |
| @Override |
| public CDOCommitInfoHandler[] getCommitInfoHandlers() |
| { |
| return handlers.get(); |
| } |
| |
| /** |
| * @since 4.0 |
| */ |
| @Override |
| public void addCommitInfoHandler(CDOCommitInfoHandler handler) |
| { |
| handlers.add(handler); |
| } |
| |
| /** |
| * @since 4.0 |
| */ |
| @Override |
| public void removeCommitInfoHandler(CDOCommitInfoHandler handler) |
| { |
| handlers.remove(handler); |
| } |
| |
| @Override |
| public void notifyCommitInfoHandlers(CDOCommitInfo commitInfo) |
| { |
| for (CDOCommitInfoHandler handler : getCommitInfoHandlers()) |
| { |
| try |
| { |
| handler.handleCommitInfo(commitInfo); |
| } |
| catch (Exception ex) |
| { |
| OM.LOG.error(ex); |
| } |
| } |
| } |
| |
| @Override |
| public CDOCommitInfo createCommitInfo(CDOBranch branch, long timeStamp, long previousTimeStamp, String userID, String comment, CDOCommitData commitData) |
| { |
| return createCommitInfo(branch, timeStamp, previousTimeStamp, userID, comment, null, commitData); |
| } |
| |
| @Override |
| public CDOCommitInfo createCommitInfo(CDOBranch branch, long timeStamp, long previousTimeStamp, String userID, String comment, CDOBranchPoint mergeSource, |
| CDOCommitData commitData) |
| { |
| checkActive(); |
| CDOCommitInfo commitInfo = new CDOCommitInfoImpl(this, branch, timeStamp, previousTimeStamp, userID, comment, mergeSource, commitData); |
| return intern(commitInfo); |
| } |
| |
| @Override |
| public CDOCommitInfo getCommitInfo(long timeStamp) |
| { |
| return getCommitInfo(timeStamp, true); |
| } |
| |
| @Override |
| public CDOCommitInfo getCommitInfo(long timeStamp, boolean loadOnDemand) |
| { |
| checkActive(); |
| |
| if (cache != null) |
| { |
| synchronized (cacheLock) |
| { |
| CDOCommitInfo commitInfo = cache.get(timeStamp); |
| if (commitInfo != null) |
| { |
| return commitInfo; |
| } |
| } |
| } |
| |
| final CDOCommitInfo[] result = { null }; |
| |
| if (loadOnDemand) |
| { |
| getCommitInfos(null, timeStamp, timeStamp, new CDOCommitInfoHandler() |
| { |
| @Override |
| public void handleCommitInfo(CDOCommitInfo commitInfo) |
| { |
| result[0] = commitInfo; |
| } |
| }); |
| } |
| |
| return result[0]; |
| } |
| |
| @Override |
| public CDOCommitInfo getCommitInfo(CDOBranch branch, long startTime, boolean up) |
| { |
| checkActive(); |
| int count = up ? 1 : -1; |
| |
| final CDOCommitInfo[] result = { null }; |
| getCommitInfos(branch, startTime, null, null, count, new CDOCommitInfoHandler() |
| { |
| @Override |
| public void handleCommitInfo(CDOCommitInfo commitInfo) |
| { |
| result[0] = commitInfo; |
| } |
| }); |
| |
| return result[0]; |
| } |
| |
| @Override |
| public void getCommitInfos(CDOBranch branch, long startTime, long endTime, CDOCommitInfoHandler handler) |
| { |
| checkActive(); |
| if (cache != null) |
| { |
| CDOCommitInfoHandler delegate = handler; |
| handler = commitInfo -> delegate.handleCommitInfo(intern(commitInfo)); |
| } |
| |
| loader.loadCommitInfos(branch, startTime, endTime, handler); |
| } |
| |
| @Override |
| public void getCommitInfos(CDOBranch branch, long startTime, String reserved1, String reserved2, int count, CDOCommitInfoHandler handler) |
| { |
| if (reserved1 != null || reserved2 != null) |
| { |
| throw new IllegalArgumentException("The parameters reserved1 and reserved2 are not supported"); |
| } |
| |
| long endTime = CDOCommitInfoUtil.encodeCount(count); |
| getCommitInfos(branch, startTime, endTime, handler); |
| } |
| |
| @Override |
| public CDOCommitInfo getBaseOfBranch(CDOBranch branch) |
| { |
| if (branch.isMainBranch()) |
| { |
| return null; |
| } |
| |
| BranchInfoCache infoCache = getBranchInfoCache(branch, true); |
| CDOCommitInfo base = infoCache.getBase(); |
| if (base == null) |
| { |
| base = loadBaseOfBranch(branch); |
| infoCache.setBase(base); |
| } |
| |
| return base; |
| } |
| |
| @Override |
| public CDOCommitInfo getFirstOfBranch(CDOBranch branch) |
| { |
| BranchInfoCache infoCache = getBranchInfoCache(branch, true); |
| CDOCommitInfo first = infoCache.getFirst(); |
| if (first == null) |
| { |
| first = loadFirstOfBranch(branch); |
| infoCache.setFirst(first); |
| } |
| |
| return first; |
| } |
| |
| @Override |
| public CDOCommitInfo getLastOfBranch(CDOBranch branch) |
| { |
| BranchInfoCache infoCache = getBranchInfoCache(branch, true); |
| CDOCommitInfo last = infoCache.getLast(); |
| if (last == null) |
| { |
| last = loadLastOfBranch(branch); |
| infoCache.setLast(last); |
| } |
| |
| return last; |
| } |
| |
| @Override |
| public long getLastCommitOfBranch(CDOBranch branch, boolean loadOnDemand) |
| { |
| if (branch == null) |
| { |
| return lastCommit; |
| } |
| |
| BranchInfoCache infoCache = getBranchInfoCache(branch, loadOnDemand); |
| if (infoCache != null) |
| { |
| long lastCommit = infoCache.getLastCommit(); |
| if (lastCommit == CDOBranchPoint.UNSPECIFIED_DATE && loadOnDemand) |
| { |
| CDOCommitInfo last = loadLastOfBranch(branch); |
| if (last != null) |
| { |
| infoCache.setLast(last); |
| lastCommit = last.getTimeStamp(); |
| } |
| } |
| |
| return lastCommit; |
| } |
| |
| return CDOBranchPoint.UNSPECIFIED_DATE; |
| } |
| |
| @Override |
| public void setLastCommitOfBranch(CDOBranch branch, long lastCommit) |
| { |
| if (branch != null) |
| { |
| BranchInfoCache infoCache = getBranchInfoCache(branch, true); |
| infoCache.setLastCommit(lastCommit); |
| } |
| |
| setLastCommit(lastCommit); |
| } |
| |
| @Override |
| public long getLastCommit() |
| { |
| return lastCommit; |
| } |
| |
| public void setLastCommit(long lastCommit) |
| { |
| if (lastCommit > this.lastCommit) |
| { |
| this.lastCommit = lastCommit; |
| } |
| } |
| |
| @Override |
| public String toString() |
| { |
| return "CommitInfoManager"; |
| } |
| |
| @Override |
| protected void doBeforeActivate() throws Exception |
| { |
| super.doBeforeActivate(); |
| checkState(branchManager, "branchManager"); //$NON-NLS-1$ |
| checkState(loader, "commitInfoLoader"); //$NON-NLS-1$ |
| } |
| |
| @Override |
| protected void doActivate() throws Exception |
| { |
| super.doActivate(); |
| branchManager.addListener(branchManagerListener); |
| } |
| |
| @Override |
| protected void doDeactivate() throws Exception |
| { |
| branchManager.removeListener(branchManagerListener); |
| |
| if (cache != null) |
| { |
| synchronized (cacheLock) |
| { |
| cache.clear(); |
| } |
| } |
| |
| super.doDeactivate(); |
| } |
| |
| @Override |
| protected CDOCommitHistory createHistory(CDOBranch key) |
| { |
| return new CDOCommitHistoryImpl(this, key); |
| } |
| |
| private CDOCommitInfo intern(CDOCommitInfo commitInfo) |
| { |
| if (cache != null && commitInfo != null) |
| { |
| long timeStamp = commitInfo.getTimeStamp(); |
| |
| synchronized (cacheLock) |
| { |
| CDOCommitInfo cachedCommitInfo = cache.get(timeStamp); |
| if (cachedCommitInfo != null) |
| { |
| return cachedCommitInfo; |
| } |
| |
| cache.put(timeStamp, commitInfo); |
| } |
| } |
| |
| return commitInfo; |
| } |
| |
| private BranchInfoCache getBranchInfoCache(CDOBranch branch, boolean createOnDemand) |
| { |
| synchronized (branches) |
| { |
| BranchInfoCache infoCache = branches.get(branch); |
| if (infoCache == null && createOnDemand) |
| { |
| infoCache = new BranchInfoCache(); |
| branches.put(branch, infoCache); |
| } |
| |
| return infoCache; |
| } |
| } |
| |
| private CDOCommitInfo loadBaseOfBranch(CDOBranch branch) |
| { |
| CDOBranchPoint base = branch.getBase(); |
| for (;;) |
| { |
| CDOBranch baseBranch = base.getBranch(); |
| if (baseBranch == null) |
| { |
| break; |
| } |
| |
| long baseTime = base.getTimeStamp(); |
| |
| CDOCommitInfo commitInfo = getCommitInfo(baseBranch, baseTime, false); |
| if (commitInfo != null) |
| { |
| return commitInfo; |
| } |
| |
| base = baseBranch.getBase(); |
| } |
| |
| return null; |
| } |
| |
| private CDOCommitInfo loadFirstOfBranch(CDOBranch branch) |
| { |
| return getCommitInfo(branch, 1L, true); |
| } |
| |
| private CDOCommitInfo loadLastOfBranch(CDOBranch branch) |
| { |
| return getCommitInfo(branch, Long.MAX_VALUE, false); |
| } |
| |
| /** |
| * @author Eike Stepper |
| */ |
| private static final class BranchInfoCache |
| { |
| private SoftReference<CDOCommitInfo> base; |
| |
| private SoftReference<CDOCommitInfo> first; |
| |
| private SoftReference<CDOCommitInfo> last; |
| |
| private long lastCommit = CDOBranchPoint.UNSPECIFIED_DATE; |
| |
| public BranchInfoCache() |
| { |
| } |
| |
| public CDOCommitInfo getBase() |
| { |
| return base != null ? base.get() : null; |
| } |
| |
| public void setBase(CDOCommitInfo base) |
| { |
| this.base = new SoftReference<>(base); |
| } |
| |
| public CDOCommitInfo getFirst() |
| { |
| return first != null ? first.get() : null; |
| } |
| |
| public void setFirst(CDOCommitInfo first) |
| { |
| this.first = new SoftReference<>(first); |
| } |
| |
| public CDOCommitInfo getLast() |
| { |
| return last != null ? last.get() : null; |
| } |
| |
| public void setLast(CDOCommitInfo last) |
| { |
| if (last != null) |
| { |
| this.last = new SoftReference<>(last); |
| |
| long timeStamp = last.getTimeStamp(); |
| if (timeStamp > lastCommit) |
| { |
| lastCommit = timeStamp; |
| } |
| } |
| } |
| |
| public long getLastCommit() |
| { |
| return lastCommit; |
| } |
| |
| public void setLastCommit(long lastCommit) |
| { |
| if (lastCommit > this.lastCommit) |
| { |
| this.lastCommit = lastCommit; |
| |
| CDOCommitInfo last = getLast(); |
| if (last != null) |
| { |
| if (lastCommit > last.getTimeStamp()) |
| { |
| last = null; |
| } |
| } |
| } |
| } |
| } |
| } |