2 * ============LICENSE_START=======================================================
3 * feature-distributed-locking
4 * ================================================================================
5 * Copyright (C) 2018 AT&T Intellectual Property. All rights reserved.
6 * ================================================================================
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 * ============LICENSE_END=========================================================
21 package org.onap.policy.distributed.locking;
23 import java.sql.Connection;
24 import java.sql.PreparedStatement;
25 import java.sql.ResultSet;
26 import java.sql.SQLException;
27 import java.util.UUID;
28 import org.apache.commons.dbcp2.BasicDataSource;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
32 public class TargetLock {
34 private static final Logger logger = LoggerFactory.getLogger(TargetLock.class);
37 * The Target resource we want to lock.
39 private String resourceId;
42 * Data source used to connect to the DB containing locks.
44 private BasicDataSource dataSource;
57 * Constructs a TargetLock object.
59 * @param resourceId ID of the entity we want to lock
60 * @param dataSource used to connect to the DB containing locks
62 public TargetLock(String resourceId, UUID uuid, String owner, BasicDataSource dataSource) {
63 this.resourceId = resourceId;
66 this.dataSource = dataSource;
71 * @param holdSec the amount of time, in seconds, that the lock should be held
73 public boolean lock(int holdSec) {
75 return grabLock(holdSec);
81 * @param holdSec the amount of time, in seconds, that the lock should be held
82 * @return {@code true} if the lock was refreshed, {@code false} if the resource is
83 * not currently locked by the given owner
85 public boolean refresh(int holdSec) {
86 return updateLock(holdSec);
90 * Unlock a resource by deleting it's associated record in the db.
92 public boolean unlock() {
97 * "Grabs" lock by attempting to insert a new record in the db.
98 * If the insert fails due to duplicate key error resource is already locked
99 * so we call secondGrab.
100 * @param holdSec the amount of time, in seconds, that the lock should be held
102 private boolean grabLock(int holdSec) {
104 // try to insert a record into the table(thereby grabbing the lock)
105 try (Connection conn = dataSource.getConnection();
107 PreparedStatement statement = conn.prepareStatement(
108 "INSERT INTO pooling.locks (resourceId, host, owner, expirationTime) "
109 + "values (?, ?, ?, timestampadd(second, ?, now()))")) {
112 statement.setString(index++, this.resourceId);
113 statement.setString(index++, this.uuid.toString());
114 statement.setString(index++, this.owner);
115 statement.setInt(index++, holdSec);
116 statement.executeUpdate();
119 catch (SQLException e) {
120 logger.error("error in TargetLock.grabLock()", e);
121 return secondGrab(holdSec);
128 * A second attempt at grabbing a lock. It first attempts to update the lock in case it is expired.
129 * If that fails, it attempts to insert a new record again
130 * @param holdSec the amount of time, in seconds, that the lock should be held
132 private boolean secondGrab(int holdSec) {
134 try (Connection conn = dataSource.getConnection();
136 PreparedStatement updateStatement = conn.prepareStatement(
137 "UPDATE pooling.locks SET host = ?, owner = ?, "
138 + "expirationTime = timestampadd(second, ?, now()) "
139 + "WHERE resourceId = ? AND expirationTime < now()");
141 PreparedStatement insertStatement = conn.prepareStatement(
142 "INSERT INTO pooling.locks (resourceId, host, owner, expirationTime) "
143 + "values (?, ?, ?, timestampadd(second, ?, now()))");) {
146 updateStatement.setString(index++, this.uuid.toString());
147 updateStatement.setString(index++, this.owner);
148 updateStatement.setInt(index++, holdSec);
149 updateStatement.setString(index++, this.resourceId);
151 // The lock was expired and we grabbed it.
153 if (updateStatement.executeUpdate() == 1) {
157 // If our update does not return 1 row, the lock either has not expired
158 // or it was removed. Try one last grab
161 insertStatement.setString(index++, this.resourceId);
162 insertStatement.setString(index++, this.uuid.toString());
163 insertStatement.setString(index++, this.owner);
164 insertStatement.setInt(index++, holdSec);
166 // If our insert returns 1 we successfully grabbed the lock
167 return (insertStatement.executeUpdate() == 1);
170 } catch (SQLException e) {
171 logger.error("error in TargetLock.secondGrab()", e);
178 * Updates the DB record associated with the lock.
180 * @param holdSec the amount of time, in seconds, that the lock should be held
181 * @return {@code true} if the record was updated, {@code false} otherwise
183 private boolean updateLock(int holdSec) {
185 try (Connection conn = dataSource.getConnection();
187 PreparedStatement updateStatement = conn.prepareStatement(
188 "UPDATE pooling.locks SET host = ?, owner = ?, "
189 + "expirationTime = timestampadd(second, ?, now()) "
190 + "WHERE resourceId = ? AND owner = ? AND expirationTime >= now()")) {
193 updateStatement.setString(index++, this.uuid.toString());
194 updateStatement.setString(index++, this.owner);
195 updateStatement.setInt(index++, holdSec);
196 updateStatement.setString(index++, this.resourceId);
197 updateStatement.setString(index++, this.owner);
199 // refresh succeeded iff a record was updated
200 return (updateStatement.executeUpdate() == 1);
202 } catch (SQLException e) {
203 logger.error("error in TargetLock.refreshLock()", e);
210 *To remove a lock we simply delete the record from the db .
212 private boolean deleteLock() {
214 try (Connection conn = dataSource.getConnection();
216 PreparedStatement deleteStatement = conn.prepareStatement(
217 "DELETE FROM pooling.locks WHERE resourceId = ? AND owner = ? AND host = ?")) {
219 deleteStatement.setString(1, this.resourceId);
220 deleteStatement.setString(2, this.owner);
221 deleteStatement.setString(3, this.uuid.toString());
223 return (deleteStatement.executeUpdate() == 1);
225 } catch (SQLException e) {
226 logger.error("error in TargetLock.deleteLock()", e);
233 * Is the lock active.
235 public boolean isActive() {
236 try (Connection conn = dataSource.getConnection();
238 PreparedStatement selectStatement = conn.prepareStatement(
239 "SELECT * FROM pooling.locks "
240 + "WHERE resourceId = ? AND host = ? AND owner= ? AND expirationTime >= now()")) {
242 selectStatement.setString(1, this.resourceId);
243 selectStatement.setString(2, this.uuid.toString());
244 selectStatement.setString(3, this.owner);
245 try (ResultSet result = selectStatement.executeQuery()) {
247 // This will return true if the
248 // query returned at least one row
249 return result.first();
254 catch (SQLException e) {
255 logger.error("error in TargetLock.isActive()", e);
262 * Is the resource locked.
264 public boolean isLocked() {
266 try (Connection conn = dataSource.getConnection();
267 PreparedStatement selectStatement = conn
268 .prepareStatement("SELECT * FROM pooling.locks WHERE resourceId = ? AND expirationTime >= now()")) {
270 selectStatement.setString(1, this.resourceId);
271 try (ResultSet result = selectStatement.executeQuery()) {
272 // This will return true if the
273 // query returned at least one row
274 return result.first();
276 } catch (SQLException e) {
277 logger.error("error in TargetLock.isActive()", e);