Fix checkstyle for features submodules.
[policy/drools-pdp.git] / feature-distributed-locking / src / main / java / org / onap / policy / distributed / locking / TargetLock.java
1 /*
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
10  * 
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  * 
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=========================================================
19  */
20
21 package org.onap.policy.distributed.locking;
22
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;
31
32 public class TargetLock {
33
34     private static final Logger logger = LoggerFactory.getLogger(TargetLock.class);
35
36     /**
37      * The Target resource we want to lock.
38      */
39     private String resourceId;
40
41     /**
42      * Data source used to connect to the DB containing locks.
43      */
44     private BasicDataSource dataSource;
45
46     /**
47      * UUID .
48      */
49     private UUID uuid;
50
51     /**
52      * Owner.
53      */
54     private String owner;
55
56     /**
57      * Constructs a TargetLock object.
58      * 
59      * @param resourceId ID of the entity we want to lock
60      * @param dataSource used to connect to the DB containing locks
61      */
62     public TargetLock(String resourceId, UUID uuid, String owner, BasicDataSource dataSource) {
63         this.resourceId = resourceId;
64         this.uuid = uuid;
65         this.owner = owner;
66         this.dataSource = dataSource;
67     }
68
69     /**
70      * Obtain a lock.
71      * @param holdSec the amount of time, in seconds, that the lock should be held
72      */
73     public boolean lock(int holdSec) {
74
75         return grabLock(holdSec);
76     }
77
78     /**
79      * Refresh a lock.
80      * 
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
84      */
85     public boolean refresh(int holdSec) {
86         return updateLock(holdSec);
87     }
88
89     /**
90      * Unlock a resource by deleting it's associated record in the db.
91      */
92     public boolean unlock() {
93         return deleteLock();
94     }
95
96     /**
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
101      */
102     private boolean grabLock(int holdSec) {
103
104         // try to insert a record into the table(thereby grabbing the lock)
105         try (Connection conn = dataSource.getConnection();
106
107                 PreparedStatement statement = conn.prepareStatement(
108                         "INSERT INTO pooling.locks (resourceId, host, owner, expirationTime) "
109                         + "values (?, ?, ?, timestampadd(second, ?, now()))")) {
110
111             int index = 1;
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();
117         }
118
119         catch (SQLException e) {
120             logger.error("error in TargetLock.grabLock()", e);
121             return secondGrab(holdSec);
122         }
123
124         return true;
125     }
126
127     /**
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
131      */
132     private boolean secondGrab(int holdSec) {
133
134         try (Connection conn = dataSource.getConnection();
135
136                 PreparedStatement updateStatement = conn.prepareStatement(
137                         "UPDATE pooling.locks SET host = ?, owner = ?, "
138                         + "expirationTime = timestampadd(second, ?, now()) "
139                         + "WHERE resourceId = ? AND expirationTime < now()");
140
141                 PreparedStatement insertStatement = conn.prepareStatement(
142                         "INSERT INTO pooling.locks (resourceId, host, owner, expirationTime) "
143                         + "values (?, ?, ?, timestampadd(second, ?, now()))");) {
144
145             int index = 1;
146             updateStatement.setString(index++, this.uuid.toString());
147             updateStatement.setString(index++, this.owner);
148             updateStatement.setInt(index++, holdSec);
149             updateStatement.setString(index++, this.resourceId);
150
151             // The lock was expired and we grabbed it.
152             // return true
153             if (updateStatement.executeUpdate() == 1) {
154                 return true;
155             }
156
157             // If our update does not return 1 row, the lock either has not expired
158             // or it was removed. Try one last grab
159             else {
160                 index = 1;
161                 insertStatement.setString(index++, this.resourceId);
162                 insertStatement.setString(index++, this.uuid.toString());
163                 insertStatement.setString(index++, this.owner);
164                 insertStatement.setInt(index++, holdSec);
165
166                 // If our insert returns 1 we successfully grabbed the lock
167                 return (insertStatement.executeUpdate() == 1);
168             }
169
170         } catch (SQLException e) {
171             logger.error("error in TargetLock.secondGrab()", e);
172             return false;
173         }
174
175     }
176
177     /**
178      * Updates the DB record associated with the lock.
179      * 
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
182      */
183     private boolean updateLock(int holdSec) {
184
185         try (Connection conn = dataSource.getConnection();
186
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()")) {
191
192             int index = 1;
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);
198
199             // refresh succeeded iff a record was updated
200             return (updateStatement.executeUpdate() == 1);
201
202         } catch (SQLException e) {
203             logger.error("error in TargetLock.refreshLock()", e);
204             return false;
205         }
206
207     }
208
209     /**
210      *To remove a lock we simply delete the record from the db .
211      */
212     private boolean deleteLock() {
213
214         try (Connection conn = dataSource.getConnection();
215
216                 PreparedStatement deleteStatement = conn.prepareStatement(
217                         "DELETE FROM pooling.locks WHERE resourceId = ? AND owner = ? AND host = ?")) {
218
219             deleteStatement.setString(1, this.resourceId);
220             deleteStatement.setString(2, this.owner);
221             deleteStatement.setString(3, this.uuid.toString());
222
223             return (deleteStatement.executeUpdate() == 1);
224
225         } catch (SQLException e) {
226             logger.error("error in TargetLock.deleteLock()", e);
227             return false;
228         }
229
230     }
231
232     /**
233      * Is the lock active.
234      */
235     public boolean isActive() {
236         try (Connection conn = dataSource.getConnection();
237
238                 PreparedStatement selectStatement = conn.prepareStatement(
239                         "SELECT * FROM pooling.locks "
240                         + "WHERE resourceId = ? AND host = ? AND owner= ? AND expirationTime >= now()")) {
241
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()) {
246
247                 // This will return true if the
248                 // query returned at least one row
249                 return result.first();
250             }
251
252         }
253
254         catch (SQLException e) {
255             logger.error("error in TargetLock.isActive()", e);
256             return false;
257         }
258
259     }
260
261     /**
262      * Is the resource locked.
263      */
264     public boolean isLocked() {
265
266         try (Connection conn = dataSource.getConnection();
267                 PreparedStatement selectStatement = conn
268                     .prepareStatement("SELECT * FROM pooling.locks WHERE resourceId = ? AND expirationTime >= now()")) {
269
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();
275             }
276         } catch (SQLException e) {
277             logger.error("error in TargetLock.isActive()", e);
278             return false;
279         }
280     }
281
282 }