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