rlock: add JavaDoc
[ccsdk/features.git] / lib / rlock / src / main / java / org / onap / ccsdk / features / lib / rlock / LockHelperImpl.java
1 package org.onap.ccsdk.features.lib.rlock;
2
3 import java.util.ArrayList;
4 import java.util.Collection;
5 import java.util.Collections;
6 import java.util.Date;
7 import java.util.List;
8 import javax.sql.DataSource;
9 import org.slf4j.Logger;
10 import org.slf4j.LoggerFactory;
11
12 /**
13  * <p>
14  * Implementation of the locking service, providing <i>distributed</i> locking functionality. It is
15  * done using a table in SQL Database as a single synchronization point. Hence, for this
16  * implementation, it is required that all participating threads in all participating applications
17  * access the same database instance (or a distributed database that looks like one database
18  * instance).
19  * </p>
20  * <p>
21  * The following table is required in the database:
22  * </p>
23  *
24  * <pre style="color:darkblue;">
25  * CREATE TABLE IF NOT EXISTS `resource_lock` (
26  *   `resource_lock_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
27  *   `resource_name` varchar(256),
28  *   `lock_holder` varchar(100) NOT NULL,
29  *   `lock_count` smallint(6) NOT NULL,
30  *   `lock_time` datetime NOT NULL,
31  *   `expiration_time` datetime NOT NULL,
32  *   PRIMARY KEY (`resource_lock_id`),
33  *   UNIQUE KEY `IX1_RESOURCE_LOCK` (`resource_name`)
34  * );
35  * </pre>
36  * <p>
37  * The implementation tries to insert records in the table for all the requested resources. If there
38  * are already records for any of the resources, it fails and then makes several more tries before
39  * giving up and throwing {@link ResourceLockedException}.
40  * </p>
41  * <p>
42  * The class has 2 configurable parameters:
43  * <ul>
44  * <li><tt><b>retryCount</b></tt>: the numbers of retries, when locking a resource, default 20</li>
45  * <li><tt><b>lockWait</b></tt>: the time between each retry (in seconds), default 5 seconds</li>
46  * </ul>
47  * The total time before locking fails would be <tt>retryCount * lockWait</tt> seconds.
48  * </p>
49  *
50  * @see LockHelper
51  * @see SynchronizedFunction
52  * @see ResourceLockedException
53  */
54 public class LockHelperImpl implements LockHelper {
55
56     private static final Logger log = LoggerFactory.getLogger(LockHelperImpl.class);
57
58     private int retryCount = 20;
59     private int lockWait = 5; // Seconds
60
61     private DataSource dataSource;
62
63     @Override
64     public void lock(String resourceName, String lockRequester, int lockTimeout /* Seconds */) {
65         lock(Collections.singleton(resourceName), lockRequester, lockTimeout);
66     }
67
68     @Override
69     public void unlock(String resourceName, boolean force) {
70         unlock(Collections.singleton(resourceName), force);
71     }
72
73     @Override
74     public void lock(Collection<String> resourceNameList, String lockRequester, int lockTimeout /* Seconds */) {
75         for (int i = 0; true; i++) {
76             try {
77                 tryLock(resourceNameList, lockRequester, lockTimeout);
78                 log.info("Resources locked: " + resourceNameList);
79                 return;
80             } catch (ResourceLockedException e) {
81                 if (i > retryCount) {
82                     throw e;
83                 }
84                 try {
85                     Thread.sleep(lockWait * 1000L);
86                 } catch (InterruptedException ex) {
87                 }
88             }
89         }
90     }
91
92     @Override
93     public void unlock(Collection<String> lockNames, boolean force) {
94         if (lockNames == null || lockNames.size() == 0) {
95             return;
96         }
97
98         try (ResourceLockDao resourceLockDao = new ResourceLockDao(dataSource)) {
99             try {
100                 for (String name : lockNames) {
101                     ResourceLock l = resourceLockDao.getByResourceName(name);
102                     if (l != null) {
103                         if (force || l.lockCount == 1) {
104                             resourceLockDao.delete(l.id);
105                         } else {
106                             resourceLockDao.decrementLockCount(l.id);
107                         }
108                     }
109                 }
110                 resourceLockDao.commit();
111                 log.info("Resources unlocked: " + lockNames);
112             } catch (Exception e) {
113                 resourceLockDao.rollback();
114             }
115         }
116     }
117
118     public void tryLock(Collection<String> resourceNameList, String lockRequester, int lockTimeout /* Seconds */) {
119         if (resourceNameList == null || resourceNameList.isEmpty()) {
120             return;
121         }
122
123         lockRequester = generateLockRequester(lockRequester, 100);
124
125         // First check if all requested records are available to lock
126
127         Date now = new Date();
128
129         try (ResourceLockDao resourceLockDao = new ResourceLockDao(dataSource)) {
130             try {
131                 List<ResourceLock> dbLockList = new ArrayList<>();
132                 List<String> insertLockNameList = new ArrayList<>();
133                 for (String name : resourceNameList) {
134                     ResourceLock l = resourceLockDao.getByResourceName(name);
135
136                     boolean canLock = l == null || now.getTime() > l.expirationTime.getTime()
137                             || lockRequester != null && lockRequester.equals(l.lockHolder) || l.lockCount <= 0;
138                     if (!canLock) {
139                         throw new ResourceLockedException(l.resourceName, l.lockHolder, lockRequester);
140                     }
141
142                     if (l != null) {
143                         if (now.getTime() > l.expirationTime.getTime() || l.lockCount <= 0) {
144                             l.lockCount = 0;
145                         }
146                         dbLockList.add(l);
147                     } else {
148                         insertLockNameList.add(name);
149                     }
150                 }
151
152                 // Update the lock info in DB
153                 for (ResourceLock l : dbLockList) {
154                     resourceLockDao.update(l.id, lockRequester, now, new Date(now.getTime() + lockTimeout * 1000),
155                             l.lockCount + 1);
156                 }
157
158                 // Insert records for those that are not yet there
159                 for (String lockName : insertLockNameList) {
160                     ResourceLock l = new ResourceLock();
161                     l.resourceName = lockName;
162                     l.lockHolder = lockRequester;
163                     l.lockTime = now;
164                     l.expirationTime = new Date(now.getTime() + lockTimeout * 1000);
165                     l.lockCount = 1;
166
167                     try {
168                         resourceLockDao.add(l);
169                     } catch (Exception e) {
170                         throw new ResourceLockedException(l.resourceName, "unknown", lockRequester);
171                     }
172                 }
173
174                 resourceLockDao.commit();
175
176             } catch (Exception e) {
177                 resourceLockDao.rollback();
178                 throw e;
179             }
180         }
181     }
182
183     private static String generateLockRequester(String name, int maxLength) {
184         if (name == null) {
185             name = "";
186         }
187         int l1 = name.length();
188         String tname = Thread.currentThread().getName();
189         int l2 = tname.length();
190         if (l1 + l2 + 1 > maxLength) {
191             int maxl1 = maxLength / 2;
192             if (l1 > maxl1) {
193                 name = name.substring(0, maxl1);
194                 l1 = maxl1;
195             }
196             int maxl2 = maxLength - l1 - 1;
197             if (l2 > maxl2) {
198                 tname = tname.substring(0, 6) + "..." + tname.substring(l2 - maxl2 + 9);
199             }
200         }
201         return tname + '-' + name;
202     }
203
204     public void setRetryCount(int retryCount) {
205         this.retryCount = retryCount;
206     }
207
208     public void setLockWait(int lockWait) {
209         this.lockWait = lockWait;
210     }
211
212     public void setDataSource(DataSource dataSource) {
213         this.dataSource = dataSource;
214     }
215 }