Java tutorial
/* * Copyright 2011-2015 B2i Healthcare Pte Ltd, http://b2i.sg * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.b2international.snowowl.datastore.server.oplock; import java.io.Serializable; import java.text.MessageFormat; import java.util.BitSet; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import javax.annotation.OverridingMethodsMustInvokeSuper; import org.eclipse.core.runtime.ListenerList; import com.b2international.snowowl.datastore.oplock.IOperationLockManager; import com.b2international.snowowl.datastore.oplock.IOperationLockTarget; import com.b2international.snowowl.datastore.oplock.OperationLockException; import com.b2international.snowowl.datastore.oplock.OperationLockInfo; import com.b2international.snowowl.datastore.oplock.impl.DatastoreOperationLockException; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Maps; /** * An abstract superclass of {@link IOperationLockManager} providing common methods. * */ public abstract class AbstractOperationLockManager<C extends Serializable> implements IOperationLockManager<C> { protected static final String ACQUIRE_FAILED_MESSAGE = "Could not acquire requested lock(s)."; private static final String RELEASE_FAILED_MESSAGE = "Could not release requested lock(s)."; private static final String LOCK_EXISTS_BUT_NOT_HELD_MESSAGE = "Lock for target {0} exists, but no lock is held."; private static final int EXPECTED_LOCKS = 128; private final Object syncObject = new Object(); private final Map<IOperationLockTarget, IOperationLock<C>> grantedLocks = Maps.newHashMap(); private final ListenerList listenerList = new ListenerList(); private final BitSet assignedIds = new BitSet(EXPECTED_LOCKS); private int lastAssignedId = 0; @Override public void lock(final C context, final long timeoutMillis, final IOperationLockTarget firstTarget, final IOperationLockTarget... restTargets) throws OperationLockException, InterruptedException { lock(context, timeoutMillis, Lists.asList(firstTarget, restTargets)); } @Override public void lock(final C context, final long timeoutMillis, final Iterable<? extends IOperationLockTarget> targets) throws OperationLockException, InterruptedException { final Map<IOperationLockTarget, C> alreadyLockedTargets = Maps.newHashMap(); final long startTimeMillis = getCurrentTimeMillis(); synchronized (syncObject) { while (true) { alreadyLockedTargets.clear(); canContextLockTargets(context, targets, alreadyLockedTargets); if (alreadyLockedTargets.isEmpty()) { for (final IOperationLockTarget newTarget : targets) { final IOperationLock<C> existingLock = getOrCreateLock(newTarget); existingLock.acquire(context); fireTargetAcquired(existingLock.getTarget(), context); } syncObject.notifyAll(); return; } if (NO_TIMEOUT == timeoutMillis) { syncObject.wait(); } else { final long remainingTimeoutMillis = timeoutMillis - (getCurrentTimeMillis() - startTimeMillis); if (remainingTimeoutMillis < 1L) { throwLockException(ACQUIRE_FAILED_MESSAGE, alreadyLockedTargets); } else { syncObject.wait(remainingTimeoutMillis); } } } } } @Override public void unlock(final C context, final IOperationLockTarget firstTarget, final IOperationLockTarget... restTargets) throws OperationLockException { unlock(context, Lists.asList(firstTarget, restTargets)); } @Override public void unlock(final C context, final Iterable<? extends IOperationLockTarget> targets) throws OperationLockException { final Map<IOperationLockTarget, C> notUnlockedTargets = Maps.newHashMap(); synchronized (syncObject) { for (final IOperationLockTarget targetToUnlock : targets) { for (final IOperationLock<C> existingLock : getExistingLocks()) { if (existingLock.targetEquals(targetToUnlock) && !canContextUnlock(context, existingLock)) { notUnlockedTargets.put(existingLock.getTarget(), existingLock.getContext()); } } } if (!notUnlockedTargets.isEmpty()) { throwLockException(RELEASE_FAILED_MESSAGE, notUnlockedTargets); } for (final IOperationLockTarget targetToUnlock : targets) { final IOperationLock<C> existingLock = getOrCreateLock(targetToUnlock); try { existingLock.release(context); fireTargetReleased(existingLock.getTarget(), context); } finally { if (!existingLock.isLocked()) { removeLock(existingLock); } } } syncObject.notifyAll(); } } /** * (non-API) * <p> * Releases all lock targets tracked by this lock manager. */ public void unlockAll() { synchronized (syncObject) { for (IOperationLock<C> lockToRemove : getAllGrantedLocks()) { if (!lockToRemove.isLocked()) { throw new IllegalStateException( MessageFormat.format(LOCK_EXISTS_BUT_NOT_HELD_MESSAGE, lockToRemove.getTarget())); } else { removeLock(lockToRemove); } } syncObject.notifyAll(); } } /** * (non-API) * <p> * Forces lock removal for the target with the specified identifier. * * @param id the lock identifier to forcefully unlock * @return */ public boolean unlockById(final int id) { synchronized (syncObject) { for (IOperationLock<C> lockToRemove : getAllGrantedLocks()) { if (!lockToRemove.isLocked()) { throw new IllegalStateException( MessageFormat.format(LOCK_EXISTS_BUT_NOT_HELD_MESSAGE, lockToRemove.getTarget())); } if (id == lockToRemove.getId()) { removeLock(lockToRemove); syncObject.notifyAll(); return true; } } } return false; } private ImmutableList<IOperationLock<C>> getAllGrantedLocks() { return ImmutableList.copyOf(grantedLocks.values()); } @Override public List<OperationLockInfo<C>> getLocks() { final List<OperationLockInfo<C>> result = Lists.newArrayList(); synchronized (syncObject) { for (final IOperationLock<C> existingLock : getExistingLocks()) { result.add(createLockInfo(existingLock)); } } Collections.sort(result); return result; } public void addLockTargetListener(final IOperationLockTargetListener<C> listener) { listenerList.add(listener); } public void removeLockTargetListener(final IOperationLockTargetListener<C> listener) { listenerList.remove(listener); } protected void throwLockException(final String message, final Map<IOperationLockTarget, C> targets) throws OperationLockException { throw new OperationLockException(message); } protected abstract IOperationLock<C> createLock(final int id, final IOperationLockTarget target); protected abstract OperationLockInfo<C> createLockInfo(final IOperationLock<C> existingLock); @OverridingMethodsMustInvokeSuper protected void canContextLockTargets(final C context, final Iterable<? extends IOperationLockTarget> targets, final Map<IOperationLockTarget, C> alreadyLockedTargets) throws DatastoreOperationLockException { for (final IOperationLockTarget newTarget : targets) { for (final IOperationLock<C> existingLock : getExistingLocks()) { if (existingLock.targetConflicts(newTarget) && !canContextLock(context, existingLock)) { alreadyLockedTargets.put(newTarget, existingLock.getContext()); } } } } protected abstract boolean canContextLock(final C context, final IOperationLock<C> existingLock); protected boolean canContextUnlock(final C context, final IOperationLock<C> existingLock) { return canContextLock(context, existingLock); } protected void clearListeners() { listenerList.clear(); } private long getCurrentTimeMillis() { return System.nanoTime() / (1000L * 1000L); } private IOperationLock<C> getOrCreateLock(final IOperationLockTarget target) { IOperationLock<C> existingLock = grantedLocks.get(target); if (null == existingLock) { lastAssignedId = assignedIds.nextClearBit(lastAssignedId); existingLock = createLock(lastAssignedId, target); assignedIds.set(lastAssignedId); /* * XXX (apeteri): this makes the lock manager revisit low IDs after every 128 issued locks, but * it can still assign a number over 128 if all of the early ones are in use, since the BitSet grows unbounded. */ lastAssignedId = lastAssignedId % EXPECTED_LOCKS; } grantedLocks.put(target, existingLock); return existingLock; } private void removeLock(final IOperationLock<C> existingLock) { if (grantedLocks.values().remove(existingLock)) { assignedIds.clear(existingLock.getId()); for (final C context : existingLock.getAllContexts()) { fireTargetReleased(existingLock.getTarget(), context); } } } @SuppressWarnings("unchecked") private void fireTargetAcquired(final IOperationLockTarget target, final C context) { for (final Object listener : listenerList.getListeners()) { ((IOperationLockTargetListener<C>) listener).targetAcquired(target, context); } } @SuppressWarnings("unchecked") private void fireTargetReleased(final IOperationLockTarget target, final C context) { for (final Object listener : listenerList.getListeners()) { ((IOperationLockTargetListener<C>) listener).targetReleased(target, context); } } private Collection<IOperationLock<C>> getExistingLocks() { return grantedLocks.values(); } }