blob: c7bd27dd07787ee735002c33b47852f7aa8c5fe7 [file] [log] [blame]
/*
* Copyright (c) 2011-2013, 2015, 2016, 2019, 2020 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
* Caspar De Groot - write options
*/
package org.eclipse.emf.cdo.internal.server;
import org.eclipse.emf.cdo.common.CDOCommonView;
import org.eclipse.emf.cdo.common.branch.CDOBranch;
import org.eclipse.emf.cdo.common.branch.CDOBranchPoint;
import org.eclipse.emf.cdo.common.id.CDOID;
import org.eclipse.emf.cdo.common.id.CDOIDUtil;
import org.eclipse.emf.cdo.common.lock.CDOLockUtil;
import org.eclipse.emf.cdo.common.revision.CDOIDAndBranch;
import org.eclipse.emf.cdo.common.revision.CDORevision;
import org.eclipse.emf.cdo.common.revision.CDORevisionManager;
import org.eclipse.emf.cdo.common.revision.CDORevisionProvider;
import org.eclipse.emf.cdo.common.revision.CDORevisionUtil;
import org.eclipse.emf.cdo.server.IRepository;
import org.eclipse.emf.cdo.server.ISession;
import org.eclipse.emf.cdo.server.ISessionManager;
import org.eclipse.emf.cdo.server.IStoreAccessor;
import org.eclipse.emf.cdo.server.IStoreAccessor.DurableLocking;
import org.eclipse.emf.cdo.server.IStoreAccessor.DurableLocking2;
import org.eclipse.emf.cdo.server.IView;
import org.eclipse.emf.cdo.server.StoreThreadLocal;
import org.eclipse.emf.cdo.spi.common.branch.CDOBranchUtil;
import org.eclipse.emf.cdo.spi.common.revision.ManagedRevisionProvider;
import org.eclipse.emf.cdo.spi.server.InternalLockManager;
import org.eclipse.emf.cdo.spi.server.InternalRepository;
import org.eclipse.emf.cdo.spi.server.InternalSessionManager;
import org.eclipse.emf.cdo.spi.server.InternalStore;
import org.eclipse.emf.cdo.spi.server.InternalView;
import org.eclipse.net4j.util.CheckUtil;
import org.eclipse.net4j.util.ImplementationError;
import org.eclipse.net4j.util.ReflectUtil.ExcludeFromDump;
import org.eclipse.net4j.util.WrappedException;
import org.eclipse.net4j.util.collection.ConcurrentArray;
import org.eclipse.net4j.util.concurrent.RWOLockManager;
import org.eclipse.net4j.util.container.ContainerEventAdapter;
import org.eclipse.net4j.util.container.IContainer;
import org.eclipse.net4j.util.event.EventUtil;
import org.eclipse.net4j.util.event.IListener;
import org.eclipse.net4j.util.lifecycle.ILifecycle;
import org.eclipse.net4j.util.lifecycle.LifecycleEventAdapter;
import org.eclipse.net4j.util.options.IOptionsContainer;
import org.eclipse.net4j.util.registry.HashMapRegistry;
import org.eclipse.net4j.util.registry.IRegistry;
import org.eclipse.core.runtime.PlatformObject;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/**
* @author Simon McDuff
* @since 3.0
*/
public class LockingManager extends RWOLockManager<Object, IView> implements InternalLockManager
{
private static final LockType[] ALL_LOCK_TYPES = LockType.values();
private InternalRepository repository;
private Map<String, InternalView> openDurableViews = new HashMap<>();
private Map<String, DurableView> durableViews = new HashMap<>();
private ConcurrentArray<DurableViewHandler> durableViewHandlers = new ConcurrentArray<DurableViewHandler>()
{
@Override
protected DurableViewHandler[] newArray(int length)
{
return new DurableViewHandler[length];
}
};
@ExcludeFromDump
private transient IListener sessionListener = new ContainerEventAdapter<IView>()
{
@Override
protected void onRemoved(IContainer<IView> container, IView view)
{
String durableLockingID = view.getDurableLockingID();
if (durableLockingID == null)
{
repository.unlock((InternalView)view, null, null, false);
}
else
{
DurableView durableView = new DurableView(durableLockingID, view, view.isReadOnly());
changeContext(view, durableView);
unregisterOpenDurableView(durableLockingID);
durableViews.put(durableLockingID, durableView);
}
}
};
@ExcludeFromDump
private transient IListener sessionManagerListener = new ContainerEventAdapter<ISession>()
{
@Override
protected void onAdded(IContainer<ISession> container, ISession session)
{
session.addListener(sessionListener);
}
@Override
protected void onRemoved(IContainer<ISession> container, ISession session)
{
session.removeListener(sessionListener);
}
};
public LockingManager()
{
}
@Override
public InternalRepository getRepository()
{
return repository;
}
@Override
public void setRepository(InternalRepository repository)
{
this.repository = repository;
}
@Override
public synchronized Object getLockEntryObject(Object key)
{
LockState<Object, IView> lockState = getObjectToLocksMap().get(key);
return lockState == null ? null : lockState.getLockedObject();
}
@Override
public Object getLockKey(CDOID id, CDOBranch branch)
{
if (repository.isSupportingBranches())
{
return CDOIDUtil.createIDAndBranch(id, branch);
}
return id;
}
@Override
public synchronized Map<CDOID, LockGrade> getLocks(final IView view)
{
final Map<CDOID, LockGrade> result = CDOIDUtil.createMap();
for (LockState<Object, IView> lockState : getObjectToLocksMap().values())
{
LockGrade grade = LockGrade.NONE;
if (lockState.hasLock(LockType.READ, view, false))
{
grade = grade.getUpdated(LockType.READ, true);
}
if (lockState.hasLock(LockType.WRITE, view, false))
{
grade = grade.getUpdated(LockType.WRITE, true);
}
if (lockState.hasLock(LockType.OPTION, view, false))
{
grade = grade.getUpdated(LockType.OPTION, true);
}
if (grade != LockGrade.NONE)
{
CDOID id = getLockKeyID(lockState.getLockedObject());
result.put(id, grade);
}
}
return result;
}
@Override
@Deprecated
public void lock(boolean explicit, LockType type, IView view, Collection<? extends Object> objectsToLock, long timeout) throws InterruptedException
{
lock2(explicit, type, view, objectsToLock, false, timeout);
}
@Override
public List<LockState<Object, IView>> lock2(boolean explicit, LockType type, IView view, Collection<? extends Object> objectsToLock, boolean recursive,
long timeout) throws InterruptedException
{
String durableLockingID = null;
DurableLocking accessor = null;
if (explicit)
{
durableLockingID = view.getDurableLockingID();
if (durableLockingID != null)
{
accessor = getDurableLocking();
}
}
long startTime = timeout == WAIT ? 0L : currentTimeMillis();
List<LockState<Object, IView>> newLockStates;
synchronized (this)
{
if (recursive)
{
objectsToLock = createContentSet(objectsToLock, view);
}
// Adjust timeout for delay we may have incurred on entering this synchronized block
if (timeout != WAIT)
{
timeout -= currentTimeMillis() - startTime;
}
newLockStates = super.lock2(type, view, objectsToLock, timeout);
}
if (accessor != null)
{
accessor.lock(durableLockingID, type, objectsToLock);
}
return newLockStates;
}
@Override
public void lock(LockType type, IView context, Collection<? extends Object> objectsToLock, long timeout) throws InterruptedException
{
lock2(false, type, context, objectsToLock, false, timeout);
}
@Override
public void lock(LockType type, IView context, Object objectToLock, long timeout) throws InterruptedException
{
Collection<Object> objectsToLock = new LinkedHashSet<>();
objectsToLock.add(objectToLock);
lock2(false, type, context, objectsToLock, false, timeout);
}
@Override
public List<LockState<Object, IView>> lock2(LockType type, IView context, Collection<? extends Object> objectsToLock, long timeout)
throws InterruptedException
{
return lock2(false, type, context, objectsToLock, false, timeout);
}
private Set<? extends Object> createContentSet(Collection<? extends Object> objectsToLock, IView view)
{
CDOBranch branch = view.getBranch();
boolean branching = repository.isSupportingBranches();
CDORevisionManager revisionManager = view.getSession().getRepository().getRevisionManager();
CDORevisionProvider revisionProvider = new ManagedRevisionProvider(revisionManager, branch.getHead());
Set<Object> contents = new HashSet<>();
for (Object objectToLock : objectsToLock)
{
contents.add(objectToLock);
CDOID id = branching ? ((CDOIDAndBranch)objectToLock).getID() : (CDOID)objectToLock;
CDORevision revision = revisionProvider.getRevision(id);
createContentSet(branch, branching, revisionProvider, revision, contents);
}
return contents;
}
private void createContentSet(CDOBranch branch, boolean branching, CDORevisionProvider revisionProvider, CDORevision revision, Set<Object> contents)
{
for (CDORevision child : CDORevisionUtil.getChildRevisions(revision, revisionProvider))
{
CDOID childID = child.getID();
contents.add(branching ? CDOIDUtil.createIDAndBranch(childID, branch) : childID);
createContentSet(branch, branching, revisionProvider, child, contents);
}
}
@Override
@Deprecated
public synchronized void unlock(boolean explicit, LockType type, IView view, Collection<? extends Object> objectsToUnlock)
{
unlock2(explicit, type, view, objectsToUnlock, false);
}
@Override
public synchronized List<LockState<Object, IView>> unlock2(boolean explicit, LockType type, IView view, Collection<? extends Object> objects,
boolean recursive)
{
List<LockState<Object, IView>> newLockStates;
synchronized (this)
{
if (recursive)
{
objects = createContentSet(objects, view);
}
newLockStates = super.unlock2(type, view, objects);
}
if (explicit)
{
String durableLockingID = view.getDurableLockingID();
if (durableLockingID != null)
{
DurableLocking accessor = getDurableLocking();
accessor.unlock(durableLockingID, type, objects);
}
}
return newLockStates;
}
@Override
@Deprecated
public synchronized void unlock(boolean explicit, IView view)
{
unlock2(explicit, view);
}
@Override
public synchronized List<LockState<Object, IView>> unlock2(boolean explicit, IView view)
{
if (explicit)
{
String durableLockingID = view.getDurableLockingID();
if (durableLockingID != null)
{
DurableLocking accessor = getDurableLocking();
accessor.unlock(durableLockingID);
}
}
return super.unlock2(view);
}
@Override
public synchronized List<RWOLockManager.LockState<Object, IView>> unlock2(IView context)
{
return unlock2(false, context);
}
@Override
public synchronized List<RWOLockManager.LockState<Object, IView>> unlock2(IView context, Collection<? extends Object> objectsToUnlock)
{
// If no locktype is specified, use the LockType.WRITE
return unlock2(false, LockType.WRITE, context, objectsToUnlock, false);
}
@Override
public synchronized List<RWOLockManager.LockState<Object, IView>> unlock2(LockType type, IView context, Collection<? extends Object> objectsToUnlock)
{
return unlock2(false, type, context, objectsToUnlock, false);
}
@Override
public synchronized void unlock(IView context)
{
unlock2(context);
}
@Override
public synchronized void unlock(LockType type, IView context, Collection<? extends Object> objectsToUnlock)
{
unlock2(type, context, objectsToUnlock);
}
@Override
public LockArea createLockArea(String userID, CDOBranchPoint branchPoint, boolean readOnly, Map<CDOID, LockGrade> locks)
{
return createLockArea(userID, branchPoint, readOnly, locks, null);
}
private LockArea createLockArea(String userID, CDOBranchPoint branchPoint, boolean readOnly, Map<CDOID, LockGrade> locks, String lockAreaID)
{
if (lockAreaID == null)
{
DurableLocking accessor = getDurableLocking();
return accessor.createLockArea(userID, branchPoint, readOnly, locks);
}
DurableLocking2 accessor = getDurableLocking2();
return accessor.createLockArea(lockAreaID, userID, branchPoint, readOnly, locks);
}
@Override
public LockArea createLockArea(InternalView view)
{
return createLockArea(view, null);
}
@Override
public LockArea createLockArea(InternalView view, String lockAreaID)
{
String userID = view.getSession().getUserID();
CDOBranchPoint branchPoint = CDOBranchUtil.copyBranchPoint(view);
boolean readOnly = view.isReadOnly();
Map<CDOID, LockGrade> locks = getLocks(view);
LockArea area = createLockArea(userID, branchPoint, readOnly, locks, lockAreaID);
synchronized (openDurableViews)
{
openDurableViews.put(area.getDurableLockingID(), view);
}
return area;
}
@Override
public LockArea getLockArea(String durableLockingID) throws LockAreaNotFoundException
{
DurableLocking accessor = getDurableLocking();
return accessor.getLockArea(durableLockingID);
}
@Override
public void getLockAreas(String userIDPrefix, LockArea.Handler handler)
{
if (userIDPrefix == null)
{
userIDPrefix = "";
}
DurableLocking accessor = getDurableLocking();
accessor.getLockAreas(userIDPrefix, handler);
}
@Override
public void deleteLockArea(String durableLockingID)
{
DurableLocking accessor = getDurableLocking();
accessor.deleteLockArea(durableLockingID);
unregisterOpenDurableView(durableLockingID);
}
@Override
public IView openView(ISession session, int viewID, boolean readOnly, final String durableLockingID)
{
synchronized (openDurableViews)
{
InternalView view = openDurableViews.get(durableLockingID);
if (view != null)
{
throw new IllegalStateException("Durable view is already open: " + view);
}
LockArea area = getLockArea(durableLockingID);
if (area.isReadOnly() != readOnly)
{
throw new IllegalStateException("Durable read-only state does not match the request");
}
for (DurableViewHandler handler : durableViewHandlers.get())
{
try
{
handler.openingView(session, viewID, readOnly, area);
}
catch (Exception ex)
{
throw WrappedException.wrap(ex);
}
}
if (readOnly)
{
view = (InternalView)session.openView(viewID, area);
}
else
{
view = (InternalView)session.openTransaction(viewID, area);
}
DurableView durableView = durableViews.get(durableLockingID);
changeContext(durableView, view);
view.setDurableLockingID(durableLockingID);
view.addListener(new LifecycleEventAdapter()
{
@Override
protected void onDeactivated(ILifecycle lifecycle)
{
synchronized (openDurableViews)
{
openDurableViews.remove(durableLockingID);
}
}
});
openDurableViews.put(durableLockingID, view);
return view;
}
}
@Override
protected void doActivate() throws Exception
{
super.doActivate();
loadLocks();
InternalSessionManager sessionManager = getRepository().getSessionManager();
sessionManager.addListener(sessionManagerListener);
for (ISession session : sessionManager.getSessions())
{
session.addListener(sessionListener);
}
}
@Override
protected void doDeactivate() throws Exception
{
ISessionManager sessionManager = getRepository().getSessionManager();
sessionManager.removeListener(sessionManagerListener);
for (ISession session : sessionManager.getSessions())
{
session.removeListener(sessionListener);
}
super.doDeactivate();
}
private DurableLocking getDurableLocking()
{
IStoreAccessor accessor = StoreThreadLocal.getAccessor();
if (accessor instanceof DurableLocking)
{
return (DurableLocking)accessor;
}
throw new IllegalStateException("Store does not implement " + DurableLocking.class.getSimpleName());
}
private DurableLocking2 getDurableLocking2()
{
IStoreAccessor accessor = StoreThreadLocal.getAccessor();
if (accessor instanceof DurableLocking2)
{
return (DurableLocking2)accessor;
}
throw new IllegalStateException("Store does not implement " + DurableLocking2.class.getSimpleName());
}
@Override
public void reloadLocks()
{
DurableLockLoader handler = new DurableLockLoader();
getLockAreas(null, handler);
}
private void loadLocks()
{
InternalStore store = repository.getStore();
IStoreAccessor reader = null;
try
{
reader = store.getReader(null);
if (reader instanceof DurableLocking)
{
StoreThreadLocal.setAccessor(reader);
reloadLocks();
}
}
finally
{
StoreThreadLocal.release();
}
}
private void unregisterOpenDurableView(String durableLockingID)
{
synchronized (openDurableViews)
{
InternalView view = openDurableViews.remove(durableLockingID);
if (view != null)
{
view.setDurableLockingID(null);
}
}
}
@Override
public CDOID getLockKeyID(Object key)
{
CDOID id = CDOLockUtil.getLockedObjectID(key);
if (id == null)
{
throw new ImplementationError("Unexpected lock object: " + key);
}
return id;
}
@Override
public void addDurableViewHandler(DurableViewHandler handler)
{
durableViewHandlers.add(handler);
}
@Override
public void removeDurableViewHandler(DurableViewHandler handler)
{
durableViewHandlers.remove(handler);
}
@Override
public DurableViewHandler[] getDurableViewHandlers()
{
return durableViewHandlers.get();
}
/**
* @author Eike Stepper
*/
private final class DurableView extends PlatformObject implements IView, CDOCommonView.Options
{
private final String durableLockingID;
private final CDOBranchPoint branchPoint;
private final boolean readOnly;
private IRegistry<String, Object> properties;
public DurableView(String durableLockingID, CDOBranchPoint branchPoint, boolean readOnly)
{
this.durableLockingID = durableLockingID;
this.readOnly = readOnly;
this.branchPoint = CDOBranchUtil.copyBranchPoint(branchPoint);
}
@Override
public String getDurableLockingID()
{
return durableLockingID;
}
@Override
public boolean isDurableView()
{
return true;
}
@Override
public int getSessionID()
{
return CDOLockUtil.DURABLE_SESSION_ID;
}
@Override
public int getViewID()
{
return CDOLockUtil.DURABLE_VIEW_ID;
}
@Override
public boolean isReadOnly()
{
return readOnly;
}
@Override
public boolean isHistorical()
{
return branchPoint.getTimeStamp() != CDOBranchPoint.UNSPECIFIED_DATE;
}
@Override
public CDOBranch getBranch()
{
return branchPoint.getBranch();
}
@Override
public long getTimeStamp()
{
return branchPoint.getTimeStamp();
}
@Override
public CDORevision getRevision(CDOID id)
{
CDORevisionManager revisionManager = getRepository().getRevisionManager();
return revisionManager.getRevision(id, branchPoint, CDORevision.UNCHUNKED, CDORevision.DEPTH_NONE, true);
}
@Override
public void close()
{
throw new UnsupportedOperationException();
}
@Override
public boolean isClosed()
{
return false;
}
@Override
public IRepository getRepository()
{
return LockingManager.this.getRepository();
}
@Override
public ISession getSession()
{
return null;
}
@Override
public int hashCode()
{
return durableLockingID.hashCode();
}
@Override
public boolean equals(Object obj)
{
if (obj == this)
{
return true;
}
if (obj instanceof DurableView)
{
DurableView that = (DurableView)obj;
return durableLockingID.equals(that.getDurableLockingID());
}
return false;
}
@Override
public String toString()
{
return MessageFormat.format("DurableView[{0}]", durableLockingID);
}
@Override
public IOptionsContainer getContainer()
{
return this;
}
@Override
public void addListener(IListener listener)
{
}
@Override
public void removeListener(IListener listener)
{
}
@Override
public boolean hasListeners()
{
return false;
}
@Override
public IListener[] getListeners()
{
return EventUtil.NO_LISTENERS;
}
@Override
public Options options()
{
return this;
}
@Override
public synchronized IRegistry<String, Object> properties()
{
if (properties == null)
{
properties = new HashMapRegistry.AutoCommit<>();
}
return properties;
}
@Override
public boolean isLockNotificationEnabled()
{
return false;
}
@Override
public void setLockNotificationEnabled(boolean enabled)
{
}
}
/**
* @author Eike Stepper
*/
private final class DurableLockLoader implements LockArea.Handler
{
public DurableLockLoader()
{
}
private IView getView(String lockAreaID)
{
IView view = openDurableViews.get(lockAreaID);
if (view == null)
{
view = durableViews.get(lockAreaID);
}
return view;
}
@Override
public boolean handleLockArea(LockArea area)
{
String durableLockingID = area.getDurableLockingID();
IView view = getView(durableLockingID);
if (view != null)
{
unlock2(view);
}
else
{
view = new DurableView(durableLockingID, area, area.isReadOnly());
durableViews.put(durableLockingID, (DurableView)view);
}
Collection<Object> readLocks = new ArrayList<>();
Collection<Object> writeLocks = new ArrayList<>();
Collection<Object> writeOptions = new ArrayList<>();
for (Entry<CDOID, LockGrade> entry : area.getLocks().entrySet())
{
Object key = getLockKey(entry.getKey(), area.getBranch());
LockGrade grade = entry.getValue();
if (grade.isRead())
{
readLocks.add(key);
}
if (grade.isWrite())
{
writeLocks.add(key);
}
if (grade.isOption())
{
writeOptions.add(key);
}
}
try
{
lock(LockType.READ, view, readLocks, 1000L);
lock(LockType.WRITE, view, writeLocks, 1000L);
lock(LockType.OPTION, view, writeOptions, 1000L);
}
catch (InterruptedException ex)
{
throw WrappedException.wrap(ex);
}
return true;
}
}
@Override
public LockGrade getLockGrade(Object key)
{
LockState<Object, IView> lockState = getObjectToLocksMap().get(key);
LockGrade grade = LockGrade.NONE;
if (lockState != null)
{
for (LockType type : ALL_LOCK_TYPES)
{
if (lockState.hasLock(type))
{
grade = grade.getUpdated(type, true);
}
}
}
return grade;
}
private LockArea getLockAreaNoEx(String durableLockingID)
{
try
{
return getLockArea(durableLockingID);
}
catch (LockAreaNotFoundException e)
{
return null;
}
}
@Override
public void updateLockArea(LockArea lockArea)
{
String durableLockingID = lockArea.getDurableLockingID();
DurableLocking2 accessor = getDurableLocking2();
if (lockArea.isMissing())
{
LockArea localLockArea = getLockAreaNoEx(durableLockingID);
if (localLockArea != null && localLockArea.getLocks().size() > 0)
{
accessor.deleteLockArea(durableLockingID);
DurableView deletedView = durableViews.remove(durableLockingID);
CheckUtil.checkNull(deletedView, "deletedView");
}
}
else
{
accessor.updateLockArea(lockArea);
new DurableLockLoader().handleLockArea(lockArea);
}
}
}