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