Test coverage in lockmanager.sql.pessimistic
[appc.git] / appc-dispatcher / appc-dispatcher-common / lock-manager-lib / lock-manager-impl / src / main / java / org / onap / appc / lockmanager / impl / sql / pessimistic / SqlLockManager.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP : APPC
4  * ================================================================================
5  * Copyright (C) 2017-2018 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Copyright (C) 2017 Amdocs
8  * ================================================================================
9  * Modifications Copyright (C) 2019 Ericsson
10  * =============================================================================
11  * Licensed under the Apache License, Version 2.0 (the "License");
12  * you may not use this file except in compliance with the License.
13  * You may obtain a copy of the License at
14  * 
15  *      http://www.apache.org/licenses/LICENSE-2.0
16  * 
17  * Unless required by applicable law or agreed to in writing, software
18  * distributed under the License is distributed on an "AS IS" BASIS,
19  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20  * See the License for the specific language governing permissions and
21  * limitations under the License.
22  * 
23  * ============LICENSE_END=========================================================
24  */
25
26 package org.onap.appc.lockmanager.impl.sql.pessimistic;
27
28 import java.sql.Connection;
29 import java.sql.PreparedStatement;
30 import java.sql.ResultSet;
31 import java.sql.SQLException;
32
33 import org.onap.appc.lockmanager.api.LockException;
34 import org.onap.appc.lockmanager.api.LockRuntimeException;
35 import org.onap.appc.lockmanager.impl.sql.JdbcLockManager;
36 import org.onap.appc.lockmanager.impl.sql.Messages;
37
38 abstract class SqlLockManager extends JdbcLockManager {
39
40     static final String SQL_LOAD_LOCK_RECORD = "SELECT * FROM %s WHERE RESOURCE_ID=?";
41     static final String SQL_INSERT_LOCK_RECORD = "INSERT INTO %s (RESOURCE_ID, OWNER_ID, UPDATED, TIMEOUT) VALUES (?, ?, ?, ?)";
42     static final String SQL_UPDATE_LOCK_RECORD = "UPDATE %s SET OWNER_ID=?, UPDATED=?, TIMEOUT=? WHERE RESOURCE_ID=?";
43     static final String SQL_CURRENT_TIMESTAMP = "SELECT CURRENT_TIMESTAMP()";
44
45     private String sqlLoadLockRecord;
46     private String sqlInsertLockRecord;
47     private String sqlUpdateLockRecord;
48
49     @Override
50     public boolean acquireLock(String resource, String owner) throws LockException {
51         return acquireLock(resource, owner, 0);
52     }
53
54     @Override
55     public boolean acquireLock(String resource, String owner, long timeout) throws LockException {
56         if(owner == null) {
57             throw new LockRuntimeException(Messages.ERR_NULL_LOCK_OWNER.format(resource));
58         }
59         boolean res = false;
60         Connection connection = openDbConnection();
61         try {
62             enterCriticalSection(connection, resource);
63             try {
64                 res = lockResource(connection, resource, owner, timeout);
65             } finally {
66                 leaveCriticalSection(connection, resource);
67             }
68         } finally {
69             closeDbConnection(connection);
70         }
71         return res;
72     }
73
74     @Override
75     public void releaseLock(String resource, String owner) throws LockException {
76         Connection connection = openDbConnection();
77         try {
78             enterCriticalSection(connection, resource);
79             try {
80                 unlockResource(connection, resource, owner);
81             } finally {
82                 leaveCriticalSection(connection, resource);
83             }
84         } finally {
85             closeDbConnection(connection);
86         }
87     }
88
89     @Override
90     public boolean isLocked(String resource) {
91         Connection connection = openDbConnection();
92         try {
93             LockRecord lockRecord = loadLockRecord(connection,resource);
94             if(lockRecord == null){
95                 return false;
96             }else{
97                 if(lockRecord.getOwner() == null){
98                     return false;
99                 }else if(isLockExpired(lockRecord, connection)){
100                     return false;
101                 }else{
102                     return true;
103                 }
104             }
105         } catch (SQLException e) {
106             throw new LockRuntimeException(Messages.EXP_CHECK_LOCK.format(resource));
107         }finally {
108             closeDbConnection(connection);
109         }
110     }
111
112     @Override
113     public String getLockOwner(String resource) {
114         Connection connection=openDbConnection();
115         try {
116             org.onap.appc.lockmanager.impl.sql.pessimistic.LockRecord lockRecord=loadLockRecord(connection,resource);
117             if(lockRecord == null || lockRecord.getOwner() == null ){
118                 return null;
119             }else{
120                 if(isLockExpired(lockRecord, connection)){
121                     return null;
122                 }else{
123                     return lockRecord.getOwner();
124                 }
125             }
126         } catch (SQLException e) {
127             throw new LockRuntimeException(Messages.EXP_CHECK_LOCK.format(resource));
128         }finally {
129             closeDbConnection(connection);
130         }
131     }
132
133     private boolean lockResource(Connection connection, String resource, String owner, long timeout) throws LockException {
134         try {
135             boolean res = false;
136             LockRecord lockRecord = loadLockRecord(connection, resource);
137             if(lockRecord != null) {
138                 // lock record already exists
139                 String currentOwner = lockRecord.getOwner();
140                 if(currentOwner != null) {
141                     if(isLockExpired(lockRecord, connection)) {
142                         currentOwner = null;
143                     } else if(!owner.equals(currentOwner)) {
144                         throw new LockException(Messages.ERR_LOCK_LOCKED_BY_OTHER.format(resource, currentOwner));
145                     }
146                 }
147                 // set new owner on the resource lock record
148                 updateLockRecord(connection, resource, owner, timeout);
149                 if(currentOwner == null) {
150                     // no one locked the resource before
151                     res = true;
152                 }
153             } else {
154                 // resource record does not exist in lock table => create new record
155                 addLockRecord(connection, resource, owner, timeout);
156                 res = true;
157             }
158             return res;
159         } catch(SQLException e) {
160             throw new LockRuntimeException(Messages.EXP_LOCK.format(resource), e);
161         }
162     }
163
164     private void unlockResource(Connection connection, String resource, String owner) throws LockException {
165         try {
166             LockRecord lockRecord = loadLockRecord(connection, resource);
167             if(lockRecord != null) {
168                 // check if expired
169                 if(isLockExpired(lockRecord, connection)) {
170                     // lock is expired => no lock
171                     lockRecord = null;
172                 }
173             }
174             if((lockRecord == null) || (lockRecord.getOwner() == null)) {
175                 // resource is not locked
176                 throw new LockException(Messages.ERR_UNLOCK_NOT_LOCKED.format(resource));
177             }
178             String currentOwner = lockRecord.getOwner();
179             if(!owner.equals(currentOwner)) {
180                 throw new LockException(Messages.ERR_UNLOCK_LOCKED_BY_OTHER.format(resource, owner, currentOwner));
181             }
182             updateLockRecord(connection, resource, null, 0);
183             // TODO delete record from table on lock release?
184 //            deleteLockRecord(connection, resource);
185         } catch(SQLException e) {
186             throw new LockRuntimeException(Messages.EXP_UNLOCK.format(resource), e);
187         }
188     }
189
190     protected abstract void enterCriticalSection(Connection connection, String resource);
191
192     protected abstract void leaveCriticalSection(Connection connection, String resource);
193
194     protected LockRecord loadLockRecord(Connection connection, String resource) throws SQLException {
195         LockRecord res = null;
196         if(sqlLoadLockRecord == null) {
197             sqlLoadLockRecord = String.format(SQL_LOAD_LOCK_RECORD, tableName);
198         }
199         try(PreparedStatement statement = connection.prepareStatement(sqlLoadLockRecord)) {
200             statement.setString(1, resource);
201             try(ResultSet resultSet = statement.executeQuery()) {
202                 if(resultSet.next()) {
203                     res = new LockRecord(resource);
204                     res.setOwner(resultSet.getString(2));
205                     res.setUpdated(resultSet.getLong(3));
206                     res.setTimeout(resultSet.getLong(4));
207                 }
208             }
209         }
210         return res;
211     }
212
213     protected void addLockRecord(Connection connection, String resource, String owner, long timeout) throws SQLException {
214         if(sqlInsertLockRecord == null) {
215             sqlInsertLockRecord = String.format(SQL_INSERT_LOCK_RECORD, tableName);
216         }
217         try(PreparedStatement statement = connection.prepareStatement(sqlInsertLockRecord)) {
218             statement.setString(1, resource);
219             statement.setString(2, owner);
220             statement.setLong(3, getCurrentTime(connection));
221             statement.setLong(4, timeout);
222             statement.executeUpdate();
223         }
224     }
225
226     protected void updateLockRecord(Connection connection, String resource, String owner, long timeout) throws SQLException {
227         if(sqlUpdateLockRecord == null) {
228             sqlUpdateLockRecord = String.format(SQL_UPDATE_LOCK_RECORD, tableName);
229         }
230         try(PreparedStatement statement = connection.prepareStatement(sqlUpdateLockRecord)) {
231             statement.setString(1, owner);
232             statement.setLong(2, getCurrentTime(connection));
233             statement.setLong(3, timeout);
234             statement.setString(4, resource);
235             statement.executeUpdate();
236         }
237     }
238
239 //    protected void deleteLockRecord(Connection connection, String resource) throws SQLException {
240 //        if(sqlDeleteLockRecord == null) {
241 //            sqlDeleteLockRecord = String.format(SQL_DELETE_LOCK_RECORD, tableName);
242 //        }
243 //        try(PreparedStatement statement = connection.prepareStatement(sqlDeleteLockRecord)) {
244 //            statement.setString(1, resource);
245 //            statement.executeUpdate();
246 //        }
247 //    }
248
249     private boolean isLockExpired(LockRecord lockRecord, Connection connection) throws SQLException {
250         long timeout = lockRecord.getTimeout();
251         if(timeout == 0) {
252             return false;
253         }
254         long updated = lockRecord.getUpdated();
255         long now = getCurrentTime(connection);
256         long expiration = updated + timeout;
257         return (now > expiration);
258     }
259
260     private long getCurrentTime(Connection connection) throws SQLException {
261         long res = -1;
262         if(connection != null) {
263             try(PreparedStatement statement = connection.prepareStatement(SQL_CURRENT_TIMESTAMP)) {
264                 try(ResultSet resultSet = statement.executeQuery()) {
265                     if(resultSet.next()) {
266                         res = resultSet.getTimestamp(1).getTime();
267                     }
268                 }
269             }
270         }
271         if(res == -1) {
272             res = System.currentTimeMillis();
273         }
274         return res;
275     }
276 }