From 6e0b450abe7e62fa47ffe14e95a67d035174dbdb Mon Sep 17 00:00:00 2001 From: Jim Hahn Date: Tue, 24 Sep 2019 10:51:21 -0400 Subject: Reimplement Lock API using Lock objects Modified PolicyResourceLockManager to just return a feature, deferring the lock() call/method to the feature, itself. The manager was also modified so that, if it can't find an enabled provider, it will return a default provider, whose lock() methods always fail. Once a feature has been identified, the manager will cache it for use thereafter. Modified the feature API to return lock objects and simplified the interface to remove the beforeXxx and afterXxx methods. Moved the unlock and refresh methods from the feature API into the lock class, renaming them to free and extend, respectively. Added a separate, feature-simple-locking project, which implements a simple version of the locking feature, over a single JVM. Extensively revised the distributed locking feature to fit in with the new API. Added support for persistence so that the various LockImpl classes can be serialized and still function correctly when they are deserialized back into new feature instances Added default implementations of free & extend to LockImpl. Modified API to take the ownerKey string, instead of the owner object. Removed Extractor as unneeded - may add via another review, if still useful. Updates per review comments: - Updated licenses in feature-simple-locking - Added beforeCreateLock & afterCreateLock to feature API - Moved SimpleLockingFeature into policy-management so that it's always available - Moved the executor service, "exsvc", into PolicyEngine - Moved Extrator into policy-utils - Changed Extractor logging level for exceptions - Fixed feature sequence numbers - Fixed mixing of seconds and milliseconds - Renamed exsvc - Modified to use property method with default value - Configured scheduled executor - Added suffix to Extractor.register() - Eliminated Feature Api and tied lock manager into engine - Added non-null checks to LockImpl parameters - Added non-null checks to createLock() parameters - Checked that lockManager is initialized Change-Id: Iddba38157ddc5f7277656979c0e679e5489eb7b1 Issue-ID: POLICY-2113 Signed-off-by: Jim Hahn --- .../policy/drools/core/lock/AlwaysFailLock.java | 71 ++++ .../org/onap/policy/drools/core/lock/Lock.java | 164 +++------ .../onap/policy/drools/core/lock/LockCallback.java | 44 +++ .../org/onap/policy/drools/core/lock/LockImpl.java | 158 +++++++++ .../onap/policy/drools/core/lock/LockState.java | 43 +++ .../core/lock/PolicyResourceLockFeatureApi.java | 168 --------- .../PolicyResourceLockFeatureApiConstants.java | 39 --- .../core/lock/PolicyResourceLockManager.java | 227 ++---------- .../policy/drools/core/lock/SimpleLockManager.java | 384 --------------------- 9 files changed, 384 insertions(+), 914 deletions(-) create mode 100644 policy-core/src/main/java/org/onap/policy/drools/core/lock/AlwaysFailLock.java create mode 100644 policy-core/src/main/java/org/onap/policy/drools/core/lock/LockCallback.java create mode 100644 policy-core/src/main/java/org/onap/policy/drools/core/lock/LockImpl.java create mode 100644 policy-core/src/main/java/org/onap/policy/drools/core/lock/LockState.java delete mode 100644 policy-core/src/main/java/org/onap/policy/drools/core/lock/PolicyResourceLockFeatureApi.java delete mode 100644 policy-core/src/main/java/org/onap/policy/drools/core/lock/PolicyResourceLockFeatureApiConstants.java delete mode 100644 policy-core/src/main/java/org/onap/policy/drools/core/lock/SimpleLockManager.java (limited to 'policy-core/src/main') diff --git a/policy-core/src/main/java/org/onap/policy/drools/core/lock/AlwaysFailLock.java b/policy-core/src/main/java/org/onap/policy/drools/core/lock/AlwaysFailLock.java new file mode 100644 index 00000000..0a4d327b --- /dev/null +++ b/policy-core/src/main/java/org/onap/policy/drools/core/lock/AlwaysFailLock.java @@ -0,0 +1,71 @@ +/* + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2019 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; + + +/** + * Lock implementation whose operations always fail. + */ +public class AlwaysFailLock extends LockImpl { + private static final long serialVersionUID = 1L; + + /** + * Constructs the object. + */ + public AlwaysFailLock() { + super(); + } + + /** + * Constructs the object. + * + * @param resourceId identifier of the resource to be locked + * @param ownerKey information identifying the owner requesting the lock + * @param holdSec amount of time, in seconds, for which the lock should be held once + * it has been granted, after which it will automatically be released + * @param callback callback to be invoked once the lock is granted, or subsequently + * lost; must not be {@code null} + */ + public AlwaysFailLock(String resourceId, String ownerKey, int holdSec, LockCallback callback) { + super(LockState.UNAVAILABLE, resourceId, ownerKey, holdSec, callback); + } + + /** + * Always returns false. + */ + @Override + public boolean free() { + return false; + } + + /** + * Always fails and invokes {@link LockCallback#lockUnavailable(Lock)}. + */ + @Override + public void extend(int holdSec, LockCallback callback) { + synchronized (this) { + setHoldSec(holdSec); + setCallback(callback); + } + + notifyUnavailable(); + } +} 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 index de62b24a..b2ed9c7f 100644 --- 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 @@ -2,14 +2,14 @@ * ============LICENSE_START======================================================= * ONAP * ================================================================================ - * Copyright (C) 2018 AT&T Intellectual Property. All rights reserved. + * Copyright (C) 2019 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. @@ -20,145 +20,67 @@ 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 not thread-safe. - * - * @param type of item to be associated with a request + * Lock held on a resource. */ -public class Lock { +public interface Lock { /** - * Result returned by removeRequester(). + * Frees/release the lock. + * + *

+ * Note: client code may choose to invoke this method before the lock has been + * granted. + * + * @return {@code true} if the request was accepted, {@code false} if the lock is + * unavailable */ - 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 - } + boolean free(); /** - * The last owner to grab the lock, never {@code null}. + * Determines if the lock is active. + * + * @return {@code true} if the lock is ACTIVE, {@code false} otherwise */ - private String owner; + boolean isActive(); /** - * 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. + * Determines if the lock is unavailable. Once a lock object becomes unavailable, it + * will never become active again. + * + * @return {@code true} if the lock is UNAVAILABLE, {@code false} otherwise */ - private LinkedHashMap requester2item = new LinkedHashMap<>(5); + boolean isUnavailable(); /** - * Constructor. - * - * @param owner the current owner of this lock + * Determines if this object is waiting for a lock to be granted or denied. This + * applies when the lock is first created, or after {@link #extend(int, LockCallback)} + * has been invoked. + * + * @return {@code true} if the lock is WAITING, {@code false} otherwise */ - public Lock(String owner) { - this.owner = owner; - } + boolean isWaiting(); /** - * Get 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}.) + * Gets the ID of the resource to which the lock applies. + * + * @return the ID of the resource to which the lock applies */ - public String getOwner() { - return owner; - } + String getResourceId(); /** - * Adds a new requester to the queue of requesters. - * - * @param requester the 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 + * Gets the lock's owner key. + * + * @return the lock's owner key */ - public boolean add(String requester, T item) { - if (item == null) { - throw SimpleLockManager.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); - } + String getOwnerKey(); /** - * 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 the requester - * @param newOwner the new owner info is placed here, if the result is RELOCKED - * @return the result + * Extends a lock an additional amount of time from now. The callback will always be + * invoked, and may be invoked before this method returns. + * + * @param holdSec the additional amount of time to hold the lock, in seconds + * @param callback callback to be invoked when the extension completes */ - public RemoveResult removeRequester(String requester, Pair 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> 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 ent = it.next(); - it.remove(); - - owner = ent.getKey(); - - newOwner.first(owner); - newOwner.second(ent.getValue()); - - return RemoveResult.RELOCKED; - } + void extend(int holdSec, LockCallback callback); } diff --git a/policy-core/src/main/java/org/onap/policy/drools/core/lock/LockCallback.java b/policy-core/src/main/java/org/onap/policy/drools/core/lock/LockCallback.java new file mode 100644 index 00000000..fae1cb43 --- /dev/null +++ b/policy-core/src/main/java/org/onap/policy/drools/core/lock/LockCallback.java @@ -0,0 +1,44 @@ +/* + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2019 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; + +/** + * Callback invoked when a lock is granted or lost. + * + *

+ * Note: these methods may or may not be invoked by the thread that requested the lock. + */ +public interface LockCallback { + + /** + * Called to indicate that a lock has been granted. + * + * @param lock lock that has been granted + */ + void lockAvailable(Lock lock); + + /** + * Called to indicate that a lock is permanently unavailable (e.g., lost, expired). + * + * @param lock lock that has been lost + */ + void lockUnavailable(Lock lock); +} diff --git a/policy-core/src/main/java/org/onap/policy/drools/core/lock/LockImpl.java b/policy-core/src/main/java/org/onap/policy/drools/core/lock/LockImpl.java new file mode 100644 index 00000000..9596dbe8 --- /dev/null +++ b/policy-core/src/main/java/org/onap/policy/drools/core/lock/LockImpl.java @@ -0,0 +1,158 @@ +/* + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2019 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.io.Serializable; +import lombok.Getter; +import lombok.NonNull; +import lombok.Setter; +import lombok.ToString; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Lock implementation. + */ +@Getter +@Setter +@ToString(exclude = {"callback"}) +public class LockImpl implements Lock, Serializable { + private static final long serialVersionUID = 1L; + + private static final Logger logger = LoggerFactory.getLogger(LockImpl.class); + + private LockState state; + private final String resourceId; + private final String ownerKey; + private transient LockCallback callback; + private int holdSec; + + /** + * Constructs the object. + */ + public LockImpl() { + this.state = LockState.UNAVAILABLE; + this.resourceId = null; + this.ownerKey = null; + this.callback = null; + this.holdSec = 0; + } + + /** + * Constructs the object. + * + * @param state the initial lock state + * @param resourceId identifier of the resource to be locked + * @param ownerKey information identifying the owner requesting the lock + * @param holdSec amount of time, in seconds, for which the lock should be held once + * it has been granted, after which it will automatically be released + * @param callback callback to be invoked once the lock is granted, or subsequently + * lost; must not be {@code null} + */ + public LockImpl(@NonNull LockState state, @NonNull String resourceId, @NonNull String ownerKey, int holdSec, + @NonNull LockCallback callback) { + + if (holdSec < 0) { + throw new IllegalArgumentException("holdSec is negative"); + } + + this.state = state; + this.resourceId = resourceId; + this.ownerKey = ownerKey; + this.callback = callback; + this.holdSec = holdSec; + } + + @Override + public boolean isActive() { + return (getState() == LockState.ACTIVE); + } + + @Override + public boolean isUnavailable() { + return (getState() == LockState.UNAVAILABLE); + } + + @Override + public boolean isWaiting() { + return (getState() == LockState.WAITING); + } + + /** + * This method always succeeds, unless the lock is already unavailable. + */ + @Override + public synchronized boolean free() { + if (isUnavailable()) { + return false; + } + + logger.info("releasing lock: {}", this); + setState(LockState.UNAVAILABLE); + + return true; + } + + /** + * This method always succeeds, unless the lock is already unavailable. + */ + @Override + public void extend(int holdSec, LockCallback callback) { + synchronized (this) { + if (isUnavailable()) { + return; + } + + logger.info("lock granted: {}", this); + setState(LockState.ACTIVE); + setHoldSec(holdSec); + setCallback(callback); + } + + notifyAvailable(); + } + + /** + * Invokes the {@link LockCallback#lockAvailable(Lock)}, from the current + * thread. Note: subclasses may choose to invoke the callback from other threads. + */ + public void notifyAvailable() { + try { + callback.lockAvailable(this); + + } catch (RuntimeException e) { + logger.warn("lock callback threw an exception", e); + } + } + + /** + * Invokes the {@link LockCallback#lockUnavailable(Lock)}, from the current + * thread. Note: subclasses may choose to invoke the callback from other threads. + */ + public void notifyUnavailable() { + try { + callback.lockUnavailable(this); + + } catch (RuntimeException e) { + logger.warn("lock callback threw an exception", e); + } + } +} diff --git a/policy-core/src/main/java/org/onap/policy/drools/core/lock/LockState.java b/policy-core/src/main/java/org/onap/policy/drools/core/lock/LockState.java new file mode 100644 index 00000000..41699ce6 --- /dev/null +++ b/policy-core/src/main/java/org/onap/policy/drools/core/lock/LockState.java @@ -0,0 +1,43 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2019 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; + +/** + * States of a Lock. + */ + +public enum LockState { + + /** + * Waiting for the lock request to complete. + */ + WAITING, + + /** + * This lock currently holds the resource. + */ + ACTIVE, + + /** + * The resource is no longer available to the lock. + */ + UNAVAILABLE +} diff --git a/policy-core/src/main/java/org/onap/policy/drools/core/lock/PolicyResourceLockFeatureApi.java b/policy-core/src/main/java/org/onap/policy/drools/core/lock/PolicyResourceLockFeatureApi.java deleted file mode 100644 index b7968486..00000000 --- a/policy-core/src/main/java/org/onap/policy/drools/core/lock/PolicyResourceLockFeatureApi.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * api-resource-locks - * ================================================================================ - * Copyright (C) 2018-2019 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 org.onap.policy.common.utils.services.OrderedService; - -/** - * Resource locks. Each lock has an "owner", which is intended to be unique across a - * single instance of a running PolicyEngine. - * - *

This interface provides a way to invoke optional features at various points in the - * code. At appropriate points in the application, the code iterates through this list, - * invoking these optional methods. - * - *

Implementers may choose to implement a level of locking appropriate to the application. - * For instance, they may choose to implement an engine-wide locking scheme, or they may - * choose to implement a global locking scheme (e.g., through a shared DB). - */ -public interface PolicyResourceLockFeatureApi extends OrderedService { - - /** - * Result of a requested operation. - */ - public enum OperResult { - - /** - * The implementer accepted the request; no additional locking logic should be - * performed. - */ - OPER_ACCEPTED, - - /** - * The implementer denied the request; no additional locking logic should be - * performed. - */ - OPER_DENIED, - - - /** - * The implementer did not handle the request; additional locking logic should - * be performed. - */ - OPER_UNHANDLED - } - - /** - * This method is called before a lock is acquired on a resource. - * - * @param resourceId resource id - * @param owner owner - * @param holdSec the amount of time, in seconds, that the lock should be held - * @return the result, where OPER_DENIED indicates that the lock is currently - * held by another owner - */ - public default OperResult beforeLock(String resourceId, String owner, int holdSec) { - return OperResult.OPER_UNHANDLED; - } - - /** - * This method is called after a lock for a resource has been acquired or denied. - * - * @param resourceId resource id - * @param owner owner - * @param locked {@code true} if the lock was acquired, {@code false} if it was denied - * @return {@code true} if the implementer handled the request, {@code false} - * otherwise - */ - public default boolean afterLock(String resourceId, String owner, boolean locked) { - return false; - } - - /** - * This method is called before a lock is refreshed on a resource. It may be invoked - * repeatedly to extend the time that a lock is held. - * - * @param resourceId resource id - * @param owner owner - * @param holdSec the amount of time, in seconds, that the lock should be held - * @return the result, where OPER_DENIED indicates that the resource is not - * currently locked by the given owner - */ - public default OperResult beforeRefresh(String resourceId, String owner, int holdSec) { - return OperResult.OPER_UNHANDLED; - } - - /** - * This method is called after a lock for a resource has been refreshed (or after the - * refresh has been denied). - * - * @param resourceId resource id - * @param owner owner - * @param locked {@code true} if the lock was acquired, {@code false} if it was denied - * @return {@code true} if the implementer handled the request, {@code false} - * otherwise - */ - public default boolean afterRefresh(String resourceId, String owner, boolean locked) { - return false; - } - - /** - * This method is called before a lock on a resource is released. - * - * @param resourceId resource id - * @param owner owner - * @return the result, where OPER_DENIED indicates that the lock is not - * currently held by the given owner - */ - public default OperResult beforeUnlock(String resourceId, String owner) { - return OperResult.OPER_UNHANDLED; - } - - /** - * This method is called after a lock on a resource is released. - * - * @param resourceId resource id - * @param owner owner - * @param unlocked {@code true} if the lock was released, {@code false} if the owner - * did not have a lock on the resource - * @return {@code true} if the implementer handled the request, {@code false} - * otherwise - */ - public default boolean afterUnlock(String resourceId, String owner, boolean unlocked) { - return false; - } - - /** - * This method is called before a check is made to determine if a resource is locked. - * - * @param resourceId resource id - * @return the result, where OPER_ACCEPTED indicates that the resource is - * locked, while OPER_DENIED indicates that it is not - */ - public default OperResult beforeIsLocked(String resourceId) { - return OperResult.OPER_UNHANDLED; - } - - /** - * This method is called before a check is made to determine if a particular owner - * holds the lock on a resource. - * - * @param resourceId resource id - * @param owner owner - * @return the result, where OPER_ACCEPTED indicates that the resource is - * locked by the given owner, while OPER_DENIED indicates that it is - * not - */ - public default OperResult beforeIsLockedBy(String resourceId, String owner) { - return OperResult.OPER_UNHANDLED; - } -} diff --git a/policy-core/src/main/java/org/onap/policy/drools/core/lock/PolicyResourceLockFeatureApiConstants.java b/policy-core/src/main/java/org/onap/policy/drools/core/lock/PolicyResourceLockFeatureApiConstants.java deleted file mode 100644 index 8510e3d9..00000000 --- a/policy-core/src/main/java/org/onap/policy/drools/core/lock/PolicyResourceLockFeatureApiConstants.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * api-resource-locks - * ================================================================================ - * Copyright (C) 2019 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 lombok.Getter; -import org.onap.policy.common.utils.services.OrderedServiceImpl; - -public class PolicyResourceLockFeatureApiConstants { - - /** - * 'FeatureAPI.impl.getList()' returns an ordered list of objects implementing the - * 'FeatureAPI' interface. - */ - @Getter - private static final OrderedServiceImpl impl = - new OrderedServiceImpl<>(PolicyResourceLockFeatureApi.class); - - private PolicyResourceLockFeatureApiConstants() { - // do nothing - } -} 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 index 0e73eac1..bbf7d229 100644 --- 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 @@ -20,213 +20,36 @@ package org.onap.policy.drools.core.lock; -import java.util.List; -import java.util.function.Function; -import java.util.function.Supplier; -import org.onap.policy.drools.core.lock.PolicyResourceLockFeatureApi.OperResult; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.onap.policy.common.capabilities.Lockable; +import org.onap.policy.common.capabilities.Startable; /** - * Manager of resource locks. Checks for API implementers. + * Manager of resource locks. */ -public class PolicyResourceLockManager extends SimpleLockManager { - - private static Logger logger = LoggerFactory.getLogger(PolicyResourceLockManager.class); - - /** - * Used by junit tests. - */ - protected PolicyResourceLockManager() { - super(); - } - - /** - * Get instance. - * - * @return the manager singleton - */ - public static PolicyResourceLockManager getInstance() { - return Singleton.instance; - } - - @Override - public boolean lock(String resourceId, String owner, int holdSec) { - if (resourceId == null) { - throw makeNullArgException(MSG_NULL_RESOURCE_ID); - } - - if (owner == null) { - throw makeNullArgException(MSG_NULL_OWNER); - } - - - return doBoolIntercept(impl -> impl.beforeLock(resourceId, owner, holdSec), () -> { - - // implementer didn't do the work - defer to the superclass - boolean locked = super.lock(resourceId, owner, holdSec); - - doIntercept(false, impl -> impl.afterLock(resourceId, owner, locked)); - - return locked; - }); - } - - @Override - public boolean refresh(String resourceId, String owner, int holdSec) { - if (resourceId == null) { - throw makeNullArgException(MSG_NULL_RESOURCE_ID); - } - - if (owner == null) { - throw makeNullArgException(MSG_NULL_OWNER); - } - - - return doBoolIntercept(impl -> impl.beforeRefresh(resourceId, owner, holdSec), () -> { - - // implementer didn't do the work - defer to the superclass - boolean refreshed = super.refresh(resourceId, owner, holdSec); - - doIntercept(false, impl -> impl.afterRefresh(resourceId, owner, refreshed)); - - return refreshed; - }); - } - - @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); - } - - - return doBoolIntercept(impl -> impl.beforeUnlock(resourceId, owner), () -> { - - // implementer didn't do the work - defer to the superclass - boolean unlocked = super.unlock(resourceId, owner); - - doIntercept(false, impl -> impl.afterUnlock(resourceId, owner, unlocked)); - - return unlocked; - }); - } - - /** - * Is locked. - * - * @throws IllegalArgumentException if the resourceId is {@code null} - */ - @Override - public boolean isLocked(String resourceId) { - if (resourceId == null) { - throw makeNullArgException(MSG_NULL_RESOURCE_ID); - } - - - return doBoolIntercept(impl -> impl.beforeIsLocked(resourceId), () -> - - // implementer didn't do the work - defer to the superclass - super.isLocked(resourceId) - ); - } +public interface PolicyResourceLockManager extends Startable, Lockable { /** - * Is locked by. + * Requests a lock on a resource. Typically, the lock is not immediately granted, + * though a "lock" object is always returned. Once the lock has been granted (or + * denied), the callback will be invoked to indicate the result. * - * @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); - } - - return doBoolIntercept(impl -> impl.beforeIsLockedBy(resourceId, owner), () -> - - // implementer didn't do the work - defer to the superclass - super.isLockedBy(resourceId, owner) - ); - } - - /** - * Applies a function to each implementer of the lock feature. Returns as soon as one - * of them returns a result other than OPER_UNHANDLED. If they all return - * OPER_UNHANDLED, then it returns the result of applying the default function. + *

+ * Notes: + *

+ *
  • The callback may be invoked before this method returns
  • + *
  • The implementation need not honor waitForLock={@code true}
  • + *
    * - * @param interceptFunc intercept function - * @param defaultFunc default function - * @return {@code true} if success, {@code false} otherwise - */ - private boolean doBoolIntercept(Function interceptFunc, - Supplier defaultFunc) { - - OperResult result = doIntercept(OperResult.OPER_UNHANDLED, interceptFunc); - if (result != OperResult.OPER_UNHANDLED) { - return (result == OperResult.OPER_ACCEPTED); - } - - return defaultFunc.get(); - } - - /** - * 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, continueValue if - * they all returned continueValue - */ - private T doIntercept(T continueValue, Function func) { - - for (PolicyResourceLockFeatureApi impl : 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; - } - - // these may be overridden by junit tests - - /** - * Get implementers. - * - * @return the list of feature implementers - */ - protected List getImplementers() { - return PolicyResourceLockFeatureApiConstants.getImpl().getList(); - } - - /** - * Initialization-on-demand holder idiom. - */ - private static class Singleton { - - private static final PolicyResourceLockManager instance = new PolicyResourceLockManager(); - - /** - * Not invoked. - */ - private Singleton() { - super(); - } - } + * @param resourceId identifier of the resource to be locked + * @param ownerKey information identifying the owner requesting the lock + * @param holdSec amount of time, in seconds, for which the lock should be held once + * it has been granted, after which it will automatically be released + * @param callback callback to be invoked once the lock is granted, or subsequently + * lost; must not be {@code null} + * @param waitForLock {@code true} to wait for the lock, if it is currently locked, + * {@code false} otherwise + * @return a new lock + */ + public Lock createLock(String resourceId, String ownerKey, int holdSec, LockCallback callback, + boolean waitForLock); } 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 deleted file mode 100644 index 427fbbc6..00000000 --- a/policy-core/src/main/java/org/onap/policy/drools/core/lock/SimpleLockManager.java +++ /dev/null @@ -1,384 +0,0 @@ -/* - * ============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.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.SortedSet; -import java.util.TreeSet; -import java.util.concurrent.TimeUnit; -import org.onap.policy.common.utils.time.CurrentTime; -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); - - // messages used in exceptions - public static final String MSG_NULL_RESOURCE_ID = "null resourceId"; - public static final String MSG_NULL_OWNER = "null owner"; - - /** - * Used to access the current time. May be overridden by junit tests. - */ - private static CurrentTime currentTime = new CurrentTime(); - - /** - * Used to synchronize updates to {@link #resource2data} and {@link #locks}. - */ - private final Object locker = new Object(); - - /** - * Maps a resource to its lock data. Lock data is stored in both this and in - * {@link #locks}. - */ - private final Map resource2data = new HashMap<>(); - - /** - * Lock data, sorted by expiration time. Lock data is stored in both this and in - * {@link #resource2data}. Whenever a lock operation is performed, this structure is - * examined and any expired locks are removed; thus no timer threads are needed to - * remove expired locks. - */ - private final SortedSet locks = new TreeSet<>(); - - /** - * Constructor. - */ - public SimpleLockManager() { - super(); - } - - /** - * Attempts to lock a resource, rejecting the lock if it is already owned, even if - * it's the same owner; the same owner can use {@link #refresh(String, String, int) - * refresh()}, instead, to extend a lock on a resource. - * - * @param resourceId resource id - * @param owner owner - * @param holdSec the amount of time, in seconds, that the lock should be held - * @return {@code true} if locked, {@code false} if the resource is already locked by - * a different owner - * @throws IllegalArgumentException if the resourceId or owner is {@code null} - */ - public boolean lock(String resourceId, String owner, int holdSec) { - - if (resourceId == null) { - throw makeNullArgException(MSG_NULL_RESOURCE_ID); - } - - if (owner == null) { - throw makeNullArgException(MSG_NULL_OWNER); - } - - boolean locked = false; - - synchronized (locker) { - cleanUpLocks(); - - if (!resource2data.containsKey(resourceId)) { - Data data = new Data(owner, resourceId, currentTime.getMillis() + TimeUnit.SECONDS.toMillis(holdSec)); - resource2data.put(resourceId, data); - locks.add(data); - locked = true; - } - } - - logger.info("lock {} for resource {} owner {}", locked, resourceId, owner); - - return locked; - } - - /** - * Attempts to refresh a lock on a resource. - * - * @param resourceId resource id - * @param owner owner - * @param holdSec the amount of time, in seconds, that the lock should be held - * @return {@code true} if locked, {@code false} if the resource is not currently - * locked by the given owner - * @throws IllegalArgumentException if the resourceId or owner is {@code null} - */ - public boolean refresh(String resourceId, String owner, int holdSec) { - - if (resourceId == null) { - throw makeNullArgException(MSG_NULL_RESOURCE_ID); - } - - if (owner == null) { - throw makeNullArgException(MSG_NULL_OWNER); - } - - boolean refreshed = false; - - synchronized (locker) { - cleanUpLocks(); - - Data existingLock = resource2data.get(resourceId); - if (existingLock != null && existingLock.getOwner().equals(owner)) { - // MUST remove the existing lock from the set - locks.remove(existingLock); - - refreshed = true; - - Data data = new Data(owner, resourceId, currentTime.getMillis() + TimeUnit.SECONDS.toMillis(holdSec)); - resource2data.put(resourceId, data); - locks.add(data); - } - } - - logger.info("refresh lock {} for resource {} owner {}", refreshed, resourceId, owner); - - return refreshed; - } - - /** - * Unlocks a resource. - * - * @param resourceId resource id - * @param owner 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); - } - - Data data; - - synchronized (locker) { - cleanUpLocks(); - - if ((data = resource2data.get(resourceId)) != null) { - if (owner.equals(data.getOwner())) { - resource2data.remove(resourceId); - locks.remove(data); - - } else { - data = null; - } - } - } - - boolean unlocked = (data != null); - logger.info("unlock resource {} owner {} = {}", resourceId, owner, unlocked); - - return unlocked; - } - - /** - * Determines if a resource is locked by anyone. - * - * @param resourceId resource id - * @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; - - synchronized (locker) { - cleanUpLocks(); - - locked = resource2data.containsKey(resourceId); - } - - logger.debug("resource {} isLocked = {}", resourceId, locked); - - return locked; - } - - /** - * Determines if a resource is locked by a particular owner. - * - * @param resourceId resource id - * @param owner 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); - } - - Data data; - - synchronized (locker) { - cleanUpLocks(); - - data = resource2data.get(resourceId); - } - - boolean locked = (data != null && owner.equals(data.getOwner())); - logger.debug("resource {} isLockedBy {} = {}", resourceId, owner, locked); - - return locked; - } - - /** - * Releases expired locks. - */ - private void cleanUpLocks() { - long tcur = currentTime.getMillis(); - - synchronized (locker) { - Iterator it = locks.iterator(); - while (it.hasNext()) { - Data data = it.next(); - if (data.getExpirationMs() <= tcur) { - it.remove(); - resource2data.remove(data.getResource()); - } else { - break; - } - } - } - } - - /** - * Makes an exception for when an argument is {@code null}. - * - * @param msg exception message - * @return a new Exception - */ - public static IllegalArgumentException makeNullArgException(String msg) { - return new IllegalArgumentException(msg); - } - - /** - * Data for a single Lock. Sorts by expiration time, then resource, and - * then owner. - */ - protected static class Data implements Comparable { - - /** - * Owner of the lock. - */ - private final String owner; - - /** - * Resource that is locked. - */ - private final String resource; - - /** - * Time when the lock will expire, in milliseconds. - */ - private final long texpireMs; - - /** - * Constructor. - * - * @param resource resource - * @param owner owner - * @param texpireMs time expire in milliseconds - */ - public Data(String owner, String resource, long texpireMs) { - this.owner = owner; - this.resource = resource; - this.texpireMs = texpireMs; - } - - public String getOwner() { - return owner; - } - - public String getResource() { - return resource; - } - - public long getExpirationMs() { - return texpireMs; - } - - @Override - public int compareTo(Data data) { - int diff = Long.compare(texpireMs, data.texpireMs); - if (diff == 0) { - diff = resource.compareTo(data.resource); - } - if (diff == 0) { - diff = owner.compareTo(data.owner); - } - return diff; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((owner == null) ? 0 : owner.hashCode()); - result = prime * result + ((resource == null) ? 0 : resource.hashCode()); - result = prime * result + (int) (texpireMs ^ (texpireMs >>> 32)); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - Data other = (Data) obj; - if (owner == null) { - if (other.owner != null) { - return false; - } - } else if (!owner.equals(other.owner)) { - return false; - } - if (resource == null) { - if (other.resource != null) { - return false; - } - } else if (!resource.equals(other.resource)) { - return false; - } - return (texpireMs == other.texpireMs); - } - } -} -- cgit 1.2.3-korg