Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
* This implementation uses hierarchical rights so requesting a right will
* need that subrights are also available for the requested use. The value
* {@code null} is not considered to be a valid hierarchical right.
* See {@link HierarchicalRight} for further details on hierarchical rights.
*
*
Events
* This implementation can notify clients if an access token is acquired or
* released. These notification events are submitted to a user specified
* {@link TaskExecutor TaskExecutor}, so clients can define
* where the events execute but cannot define on what thread these events
* are submitted to this {@code TaskExecutor}. They maybe scheduled on the
* thread used by the {@code TaskExecutor} of the
* {@link AccessToken AccessTokens} or in the call stack of caller of a methods
* of this class. Although is not possible to determine which event is submitted
* on which thread, these events will be submitted in the order they occurred.
*
*
Thread safety
* This class is thread-safe without any further synchronization. Note however
* that although thread-safe, this implementation will not scale well with
* multiple processors.
*
Synchronization transparency
* The methods of this class are not synchronization transparent
* because they may notify the specified
* {@link AccessChangeListener AccessChangeListener}. In case
* scheduling the events of the changes in right states is
* synchronization transparent then the methods of this class are also
* synchronization transparent. Note that in most case you cannot
* assume this and can only rely on that they do not block indefinitely.
*
* @param the type of the request ID (see
* {@link AccessRequest#getRequestID()})
*
* @see HierarchicalRight
*/
public final class HierarchicalAccessManager
implements
AccessManager {
private final ReentrantLock mainLock;
private final AccessTree> readTree;
private final AccessTree> writeTree;
private final TaskScheduler eventScheduler;
private final ListenerManager> listeners;
/**
* Creates a new {@code HierarchicalAccessManager} executing events in the
* context of the specified executor.
*
* @param eventExecutor the {@code Executor} to which events are submitted
* to. This argument cannot be {@code null}.
*
* @throws NullPointerException thrown if the specified executor is
* {@code null}
*/
public HierarchicalAccessManager(TaskExecutor eventExecutor) {
Objects.requireNonNull(eventExecutor, "eventExecutor");
this.mainLock = new ReentrantLock();
this.eventScheduler = new TaskScheduler(eventExecutor);
this.readTree = new AccessTree<>();
this.writeTree = new AccessTree<>();
this.listeners = new CopyOnTriggerListenerManager<>();
}
/**
* {@inheritDoc }
*/
@Override
public ListenerRef addAccessChangeListener(AccessChangeListener listener) {
return listeners.registerListener(listener);
}
/**
* Returns the shared tokens of the specified tokens without returning
* the same token twice (using "==" for comparison).
*
* @param the type of the request ID
* @param tokens the tokens of which the shared tokens are to be returned.
* This argument cannot be {@code null}.
*/
private static Set> getUniqueSharedTokens(
Collection> tokens) {
Set> result = CollectionsEx.newIdentityHashSet(tokens.size());
for (AccessTokenImpl token: tokens) {
AccessToken sharedToken = token.getSharedToken();
if (sharedToken != null) {
result.add(sharedToken);
}
}
return result;
}
private static Set createSet(Collection extends V> collection) {
Set result = CollectionsEx.newIdentityHashSet(collection.size());
result.addAll(collection);
return result;
}
/**
* Submits state change events if the client requested to do so.
*/
private void dispatchEvents() {
assert !mainLock.isHeldByCurrentThread();
eventScheduler.dispatchTasks();
}
private void scheduleEvent(
final AccessRequest extends IDType, ? extends HierarchicalRight> request,
final boolean acquire) {
eventScheduler.scheduleTask(() -> {
listeners.onEvent((AccessChangeListener eventListener, Void arg) -> {
eventListener.onChangeAccess(request, acquire);
}, null);
});
}
/**
* Removes the rights associated with the specified token and eventually
* will notify the listener. This method is called right after the specified
* token has terminated.
*
* @param token the token which terminated
*/
private void removeToken(AccessTokenImpl token) {
AccessRequest extends IDType, ? extends HierarchicalRight> request;
Collection extends HierarchicalRight> readRights;
Collection extends HierarchicalRight> writeRights;
request = token.getRequest();
readRights = request.getReadRights();
writeRights = request.getWriteRights();
mainLock.lock();
try {
for (RefList.ElementRef> index: token.getTokenIndexes()) {
index.remove();
}
readTree.cleanupRights(readRights);
writeTree.cleanupRights(writeRights);
scheduleEvent(request, false);
} finally {
mainLock.unlock();
}
assert !mainLock.isHeldByCurrentThread();
dispatchEvents();
}
/**
* Add the rights associated with the specified token to {@code readTree}
* and {@code writeTree}. This method will eventually notify the listener
* if a right has changed state.
*
* @param token the token which rights must be added to the internal right
* trees. This argument is used only so that rights can be associated with
* with the token.
* @param request the right request which was requested by the client
* these rights are associated with the specified token
* @return the references of the rights in the internal right trees of
* the specified tokens. These references must be removed after
* the token terminates.
*/
private Collection>> addRigths(
AccessTokenImpl token,
AccessRequest extends IDType, ? extends HierarchicalRight> request) {
assert mainLock.isHeldByCurrentThread();
Collection>> result;
result = new ArrayList<>();
readTree.addRights(token, request.getReadRights(), result);
writeTree.addRights(token, request.getWriteRights(), result);
return result;
}
/**
* This method returns the tokens conflicting with the specified request.
*
* @param request the request which is checked for conflicts
* @param result the conflicting tokens will be added to this collection
*/
private void getBlockingTokensList(
AccessRequest extends IDType, ? extends HierarchicalRight> request,
Collection super AccessTokenImpl> result) {
assert mainLock.isHeldByCurrentThread();
Collection extends HierarchicalRight> readRights = request.getReadRights();
Collection extends HierarchicalRight> writeRights = request.getWriteRights();
getBlockingTokensList(readRights, writeRights, result);
}
/**
* This method returns the tokens conflicting with the specified request.
*
* @param requestedReadRights the requested read rights checked for
* conflicts
* @param requestedWriteRights the requested write rights checked for
* conflicts
* @param result the conflicting tokens will be added to this collection
*/
private void getBlockingTokensList(
Collection extends HierarchicalRight> requestedReadRights,
Collection extends HierarchicalRight> requestedWriteRights,
Collection super AccessTokenImpl> result) {
assert mainLock.isHeldByCurrentThread();
readTree.getBlockingTokens(requestedWriteRights, result);
writeTree.getBlockingTokens(requestedReadRights, result);
writeTree.getBlockingTokens(requestedWriteRights, result);
}
/**
* {@inheritDoc }
*/
@Override
public Collection> getBlockingTokens(
Collection extends HierarchicalRight> requestedReadRights,
Collection extends HierarchicalRight> requestedWriteRights) {
List> blockingTokens = new ArrayList<>();
mainLock.lock();
try {
getBlockingTokensList(requestedReadRights, requestedWriteRights,
blockingTokens);
} finally {
mainLock.unlock();
}
return getUniqueSharedTokens(blockingTokens);
}
/**
* {@inheritDoc }
*/
@Override
public boolean isAvailable(
Collection extends HierarchicalRight> requestedReadRights,
Collection extends HierarchicalRight> requestedWriteRights) {
mainLock.lock();
try {
if (readTree.hasConflict(requestedWriteRights)) {
return false;
}
if (writeTree.hasConflict(requestedReadRights)) {
return false;
}
if (writeTree.hasConflict(requestedWriteRights)) {
return false;
}
} finally {
mainLock.unlock();
}
return true;
}
/**
* {@inheritDoc }
*/
@Override
public AccessResult tryGetAccess(
AccessRequest extends IDType, ? extends HierarchicalRight> request) {
AccessTokenImpl token;
token = new AccessTokenImpl<>(request);
List> blockingTokens = new ArrayList<>();
mainLock.lock();
try {
getBlockingTokensList(request, blockingTokens);
if (blockingTokens.isEmpty()) {
Collection>> tokenIndexes;
tokenIndexes = addRigths(token, request);
token.setTokenIndexes(tokenIndexes);
scheduleEvent(request, true);
}
} finally {
mainLock.unlock();
}
dispatchEvents();
token.addReleaseListener(() -> removeToken(token));
if (blockingTokens.isEmpty()) {
token.setSharedToken(token);
return new AccessResult<>(token);
} else {
return new AccessResult<>(getUniqueSharedTokens(blockingTokens));
}
}
/**
* {@inheritDoc }
*/
@Override
public AccessResult getScheduledAccess(
AccessRequest extends IDType, ? extends HierarchicalRight> request) {
AccessTokenImpl token;
token = new AccessTokenImpl<>(request);
List> blockingTokens = new ArrayList<>();
mainLock.lock();
try {
getBlockingTokensList(request, blockingTokens);
Collection>> tokenIndexes;
tokenIndexes = addRigths(token, request);
token.setTokenIndexes(tokenIndexes);
scheduleEvent(request, true);
} finally {
mainLock.unlock();
}
dispatchEvents();
token.addReleaseListener(() -> removeToken(token));
Set> blockingTokenSet = createSet(blockingTokens);
AccessToken scheduledToken;
scheduledToken = ScheduledAccessToken.newToken(token, blockingTokenSet);
token.setSharedToken(scheduledToken);
return new AccessResult<>(scheduledToken, blockingTokenSet);
}
/**
* This tree represents a collection of rights. The graph should be
* considered to be a directed graph were edges point from the parent
* to children. A path in this graph represents a hierarchical right where
* the edges are the parts of this hierarchical right in order.
* Vertices of the graph contains tokens and these tokens mean that
* the right up to the point of this vertex is associated with these
* tokens. Note that since these rights are hierarchical this implies that
* the token also represents all the subrights (even if subrights
* do not contain the token explicitly).
*/
private static class AccessTree> {
private final RefList tokens;
private final Map