diff options
author | Jorge Hernandez <jh1730@att.com> | 2018-04-03 21:33:29 +0000 |
---|---|---|
committer | Gerrit Code Review <gerrit@onap.org> | 2018-04-03 21:33:29 +0000 |
commit | 3fd657d7beee8aaff667710e2feb65c8f1c5020a (patch) | |
tree | 939cbaf41fee9af68e3ce1617fd33883d4696300 /policy-core/src/main/java/org | |
parent | 1d33f6b237c824c7f33b1ad137f6783d3d7d88df (diff) | |
parent | 3a80de9806cbdd0461716e814f5e674e259d42b3 (diff) |
Merge "Add api-resource-locks feature"
Diffstat (limited to 'policy-core/src/main/java/org')
3 files changed, 538 insertions, 0 deletions
diff --git a/policy-core/src/main/java/org/onap/policy/drools/core/lock/Lock.java b/policy-core/src/main/java/org/onap/policy/drools/core/lock/Lock.java new file mode 100644 index 00000000..ea5e2521 --- /dev/null +++ b/policy-core/src/main/java/org/onap/policy/drools/core/lock/Lock.java @@ -0,0 +1,162 @@ +/* + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2018 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.drools.core.lock; + +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map.Entry; +import org.onap.policy.drools.utils.Pair; + +/** + * Lock that is held for a resource. This not only identifies the current owner of the + * lock, but it also includes a queue of requesters. An item is associated with each + * requester that is waiting in the queue. Note: this class is <b>not</b> thread-safe. + * + * @param <T> type of item to be associated with a request + */ +public class Lock<T> { + + /** + * Result returned by <i>removeRequester()</i>. + */ + public enum RemoveResult { + /** + * The requester was the owner of the lock, and the lock is no longer needed, + * because there were no other requesters waiting to get the lock. + */ + UNLOCKED, + + /** + * The requester was the owner of the lock, and has been replaced with the next + * requester waiting in the queue. + */ + RELOCKED, + + /** + * The requester had been waiting in the queue, and has now been removed. + */ + REMOVED, + + /** + * The requester was not the owner, nor was it waiting in the queue. + */ + NOT_FOUND + }; + + /** + * The last owner to grab the lock, never {@code null}. + */ + private String owner; + + /** + * Requesters waiting to get the lock. Maps the requester (i.e., owner for which the + * request is being made) to its associated item. Uses a Linked map so that the order + * of the requesters is maintained. We don't expect many requesters for any given + * lock, thus we'll start with a small hash size. + */ + private LinkedHashMap<String, T> requester2item = new LinkedHashMap<>(5); + + /** + * + * @param owner the current owner of this lock + */ + public Lock(String owner) { + this.owner = owner; + } + + /** + * + * @return the current owner of the lock, or the last owner of the lock, if the lock + * is not currently owned. (This will never be {@code null}.) + */ + public String getOwner() { + return owner; + } + + /** + * Adds a new requester to the queue of requesters. + * + * @param requester + * @param item to be associated with the requester, must not be {@code null} + * @return {@code true} if the requester was added, {@code false} if it already owns + * the lock or is already in the queue + * @throws IllegalArgumentException if the item is null + */ + public boolean add(String requester, T item) { + if (item == null) { + throw LockRequestFuture.makeNullArgException("lock requester item is null"); + } + + if (requester.equals(owner)) { + // requester already owns the lock + return false; + } + + T prev = requester2item.putIfAbsent(requester, item); + + // if there's a previous value, then that means this requester is already + // waiting for a lock on this resource. In that case, we return false + return (prev == null); + } + + /** + * Removes a requester from the lock. The requester may currently own the lock, or it + * may be in the queue waiting for the lock. Note: as this is agnostic to the type of + * item associated with the requester, it is unable to notify the new owner that it's + * the new owner; that is left up to the code that invokes this method. + * + * @param requester + * @param newOwner the new owner info is placed here, if the result is <i>RELOCKED</i> + * @return the result + */ + public RemoveResult removeRequester(String requester, Pair<String, T> newOwner) { + + if (!requester.equals(owner)) { + // requester does not currently own the lock - remove it from the + // queue + T ent = requester2item.remove(requester); + + // if there was an entry in the queue, then return true to indicate + // that it was removed. Otherwise, return false + return (ent != null ? RemoveResult.REMOVED : RemoveResult.NOT_FOUND); + } + + /* + * requester was the owner - find something to take over + */ + Iterator<Entry<String, T>> it = requester2item.entrySet().iterator(); + if (!it.hasNext()) { + // no one to take over the lock - it's now unlocked + return RemoveResult.UNLOCKED; + } + + // there's another requester to take over + Entry<String, T> ent = it.next(); + it.remove(); + + owner = ent.getKey(); + + newOwner.first(owner); + newOwner.second(ent.getValue()); + + return RemoveResult.RELOCKED; + } +} diff --git a/policy-core/src/main/java/org/onap/policy/drools/core/lock/PolicyResourceLockManager.java b/policy-core/src/main/java/org/onap/policy/drools/core/lock/PolicyResourceLockManager.java new file mode 100644 index 00000000..d51f2b91 --- /dev/null +++ b/policy-core/src/main/java/org/onap/policy/drools/core/lock/PolicyResourceLockManager.java @@ -0,0 +1,208 @@ +/* + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2018 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.drools.core.lock; + +import static org.onap.policy.drools.core.lock.LockRequestFuture.MSG_NULL_OWNER; +import static org.onap.policy.drools.core.lock.LockRequestFuture.MSG_NULL_RESOURCE_ID; +import static org.onap.policy.drools.core.lock.LockRequestFuture.makeNullArgException; +import java.util.List; +import java.util.concurrent.Future; +import java.util.function.Function; +import org.onap.policy.drools.core.lock.PolicyResourceLockFeatureAPI.Callback; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Manager of resource locks. Checks for API implementers. + */ +public class PolicyResourceLockManager extends SimpleLockManager { + + private static Logger logger = LoggerFactory.getLogger(PolicyResourceLockManager.class); + + /** + * Used to access various objects. + */ + public static Factory factory = new Factory(); + + /** + * Used by junit tests. + */ + protected PolicyResourceLockManager() { + super(); + } + + /** + * + * @return the manager singleton + */ + public static PolicyResourceLockManager getInstance() { + return Singleton.instance; + } + + protected static Factory getFactory() { + return factory; + } + + /** + * Sets the factory to be used by junit tests. + * + * @param factory + */ + protected static void setFactory(Factory factory) { + PolicyResourceLockManager.factory = factory; + } + + @Override + public Future<Boolean> lock(String resourceId, String owner, Callback callback) { + if (resourceId == null) { + throw makeNullArgException(MSG_NULL_RESOURCE_ID); + } + + if (owner == null) { + throw makeNullArgException(MSG_NULL_OWNER); + } + + Future<Boolean> result = doIntercept(null, impl -> impl.beforeLock(resourceId, owner, callback)); + if (result != null) { + return result; + } + + // implementer didn't do the work - use superclass + result = super.lock(resourceId, owner, callback); + + boolean locked = ((LockRequestFuture) result).isLocked(); + + doIntercept(false, impl -> impl.afterLock(resourceId, owner, locked)); + + return result; + } + + @Override + public boolean unlock(String resourceId, String owner) { + if (resourceId == null) { + throw makeNullArgException(MSG_NULL_RESOURCE_ID); + } + + if (owner == null) { + throw makeNullArgException(MSG_NULL_OWNER); + } + + Boolean result = doIntercept(null, impl -> impl.beforeUnlock(resourceId, owner)); + if (result != null) { + return result; + } + + // implementer didn't do the work - use superclass + boolean unlocked = super.unlock(resourceId, owner); + + doIntercept(false, impl -> impl.afterUnlock(resourceId, owner, unlocked)); + + return unlocked; + } + + /** + * + * @throws IllegalArgumentException if the resourceId is {@code null} + */ + @Override + public boolean isLocked(String resourceId) { + if (resourceId == null) { + throw makeNullArgException(MSG_NULL_RESOURCE_ID); + } + + Boolean result = doIntercept(null, impl -> impl.beforeIsLocked(resourceId)); + if (result != null) { + return result; + } + + return super.isLocked(resourceId); + } + + /** + * + * @throws IllegalArgumentException if the resourceId or owner is {@code null} + */ + @Override + public boolean isLockedBy(String resourceId, String owner) { + if (resourceId == null) { + throw makeNullArgException(MSG_NULL_RESOURCE_ID); + } + + if (owner == null) { + throw makeNullArgException(MSG_NULL_OWNER); + } + + Boolean result = doIntercept(null, impl -> impl.beforeIsLockedBy(resourceId, owner)); + if (result != null) { + return result; + } + + return super.isLockedBy(resourceId, owner); + } + + /** + * Applies a function to each implementer of the lock feature. Returns as soon as one + * of them returns a non-null value. + * + * @param continueValue if the implementer returns this value, then it continues to + * check addition implementers + * @param func function to be applied to the implementers + * @return first non-null value returned by an implementer, <i>continueValue<i/> if + * they all returned <i>continueValue<i/> + */ + public static <T> T doIntercept(T continueValue, Function<PolicyResourceLockFeatureAPI, T> func) { + + for (PolicyResourceLockFeatureAPI impl : factory.getImplementers()) { + try { + T result = func.apply(impl); + if (result != continueValue) { + return result; + } + + } catch (RuntimeException e) { + logger.warn("lock feature {} threw an exception", impl, e); + } + } + + return continueValue; + } + + /** + * Initialization-on-demand holder idiom. + */ + private static class Singleton { + private static final PolicyResourceLockManager instance = new PolicyResourceLockManager(); + } + + /** + * Used to access various objects. + */ + public static class Factory { + + /** + * + * @return the list of feature implementers + */ + public List<PolicyResourceLockFeatureAPI> getImplementers() { + return PolicyResourceLockFeatureAPI.impl.getList(); + } + } +} diff --git a/policy-core/src/main/java/org/onap/policy/drools/core/lock/SimpleLockManager.java b/policy-core/src/main/java/org/onap/policy/drools/core/lock/SimpleLockManager.java new file mode 100644 index 00000000..14cffaab --- /dev/null +++ b/policy-core/src/main/java/org/onap/policy/drools/core/lock/SimpleLockManager.java @@ -0,0 +1,168 @@ +/* + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2018 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.drools.core.lock; + +import static org.onap.policy.drools.core.lock.LockRequestFuture.MSG_NULL_OWNER; +import static org.onap.policy.drools.core.lock.LockRequestFuture.MSG_NULL_RESOURCE_ID; +import static org.onap.policy.drools.core.lock.LockRequestFuture.makeNullArgException; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Future; +import org.onap.policy.drools.core.lock.PolicyResourceLockFeatureAPI.Callback; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Simple lock manager. Callbacks are ignored. Does not redirect to lock feature + * implementers. + */ +public class SimpleLockManager { + + protected static Logger logger = LoggerFactory.getLogger(SimpleLockManager.class); + + /** + * Maps a resource to the owner that holds the lock on it. + */ + private ConcurrentHashMap<String, String> resource2owner = new ConcurrentHashMap<>(); + + /** + * + */ + public SimpleLockManager() { + super(); + } + + // TODO: for ease of use by clients, should we always invoke the callback, even though + // this is synchronous? + + /** + * Attempts to lock a resource. This method ignores the callback and always returns a + * {@link CompletedLockRequest}. + * + * @param resourceId + * @param owner + * @param callback function to invoke, if the requester wishes to wait for the lock to + * be acquired, {@code null} to provide immediate replies + * @return a future for the lock request. The future will be in one of three states: + * <dl> + * <dt>isDone()=true and get()=true</dt> + * <dd>the lock has been acquired; the callback may or may not have been + * invoked</dd> + * <dt>isDone()=true and get()=false</dt> + * <dd>the lock request has been denied; the callback may or may not have been + * invoked</dd> + * <dt>isDone()=false</dt> + * <dd>the lock was not immediately available and a callback was provided. The + * callback will be invoked once the lock is acquired (or denied). In this + * case, the future may be used to cancel the request</dd> + * </dl> + * @throws IllegalArgumentException if the resourceId or owner is {@code null} + * @throws IllegalStateException if the owner already holds the lock or is already in + * the queue to get the lock + */ + public Future<Boolean> lock(String resourceId, String owner, Callback callback) { + + if (resourceId == null) { + throw makeNullArgException(MSG_NULL_RESOURCE_ID); + } + + if (owner == null) { + throw makeNullArgException(MSG_NULL_OWNER); + } + + boolean locked = (resource2owner.putIfAbsent(resourceId, owner) == null); + + if (!locked && owner.equals(resource2owner.get(resourceId))) { + throw new IllegalStateException("lock for resource " + resourceId + " already owned by " + owner); + } + + logger.info("lock {} for resource {} owner {}", locked, resourceId, owner); + + return new LockRequestFuture(resourceId, owner, locked); + } + + /** + * Unlocks a resource. + * + * @param resourceId + * @param owner + * @return {@code true} if unlocked, {@code false} if the given owner does not + * currently hold a lock on the resource + * @throws IllegalArgumentException if the resourceId or owner is {@code null} + */ + public boolean unlock(String resourceId, String owner) { + if (resourceId == null) { + throw makeNullArgException(MSG_NULL_RESOURCE_ID); + } + + if (owner == null) { + throw makeNullArgException(MSG_NULL_OWNER); + } + + boolean unlocked = resource2owner.remove(resourceId, owner); + logger.info("unlock resource {} owner {} = {}", resourceId, owner, unlocked); + + return unlocked; + } + + /** + * Determines if a resource is locked by anyone. + * + * @param resourceId + * @return {@code true} if the resource is locked, {@code false} otherwise + * @throws IllegalArgumentException if the resourceId is {@code null} + */ + public boolean isLocked(String resourceId) { + + if (resourceId == null) { + throw makeNullArgException(MSG_NULL_RESOURCE_ID); + } + + boolean locked = resource2owner.containsKey(resourceId); + + logger.debug("resource {} isLocked = {}", resourceId, locked); + + return locked; + } + + /** + * Determines if a resource is locked by a particular owner. + * + * @param resourceId + * @param owner + * @return {@code true} if the resource is locked, {@code false} otherwise + * @throws IllegalArgumentException if the resourceId or owner is {@code null} + */ + public boolean isLockedBy(String resourceId, String owner) { + + if (resourceId == null) { + throw makeNullArgException(MSG_NULL_RESOURCE_ID); + } + + if (owner == null) { + throw makeNullArgException(MSG_NULL_OWNER); + } + + boolean locked = owner.equals(resource2owner.get(resourceId)); + logger.debug("resource {} isLockedBy {} = {}", resourceId, owner, locked); + + return locked; + } +} |