1   /**
2    * Copyright (c) 2000-2009 Liferay, Inc. All rights reserved.
3    *
4    * Permission is hereby granted, free of charge, to any person obtaining a copy
5    * of this software and associated documentation files (the "Software"), to deal
6    * in the Software without restriction, including without limitation the rights
7    * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8    * copies of the Software, and to permit persons to whom the Software is
9    * furnished to do so, subject to the following conditions:
10   *
11   * The above copyright notice and this permission notice shall be included in
12   * all copies or substantial portions of the Software.
13   *
14   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20   * SOFTWARE.
21   */
22  
23  package com.liferay.counter.service.persistence;
24  
25  import com.liferay.counter.model.Counter;
26  import com.liferay.counter.model.CounterHolder;
27  import com.liferay.counter.model.CounterRegister;
28  import com.liferay.portal.SystemException;
29  import com.liferay.portal.kernel.concurrent.CompeteLatch;
30  import com.liferay.portal.kernel.dao.jdbc.DataAccess;
31  import com.liferay.portal.kernel.dao.orm.ObjectNotFoundException;
32  import com.liferay.portal.service.persistence.impl.BasePersistenceImpl;
33  import com.liferay.portal.util.PropsValues;
34  
35  import java.sql.Connection;
36  import java.sql.PreparedStatement;
37  import java.sql.ResultSet;
38  import java.sql.SQLException;
39  
40  import java.util.ArrayList;
41  import java.util.List;
42  import java.util.Map;
43  import java.util.concurrent.ConcurrentHashMap;
44  
45  /**
46   * <a href="CounterPersistence.java.html"><b><i>View Source</i></b></a>
47   *
48   * @author Brian Wing Shun Chan
49   * @author Harry Mark
50   * @author Michael Young
51   * @author Shuyang Zhou
52   *
53   */
54  public class CounterPersistence extends BasePersistenceImpl {
55  
56      public static int getCounterIncrement() {
57          return PropsValues.COUNTER_INCREMENT;
58      }
59  
60      public List<String> getNames() throws SystemException {
61          Connection con = null;
62          PreparedStatement ps = null;
63          ResultSet rs = null;
64  
65          try {
66              con = getConnection();
67  
68              ps = con.prepareStatement(_SQL_SELECT_NAMES);
69  
70              rs = ps.executeQuery();
71  
72              List<String> list = new ArrayList<String>();
73  
74              while (rs.next()) {
75                  list.add(rs.getString(1));
76              }
77  
78              return list;
79          }
80          catch (SQLException sqle) {
81              throw processException(sqle);
82          }
83          finally {
84              DataAccess.cleanUp(con, ps, rs);
85          }
86      }
87  
88      public long increment() throws SystemException {
89          return increment(_NAME);
90      }
91  
92      public long increment(String name) throws SystemException {
93          return increment(name, _MINIMUM_INCREMENT_SIZE);
94      }
95  
96      public long increment(String name, int size) throws SystemException {
97          if (size < _MINIMUM_INCREMENT_SIZE) {
98              size = _MINIMUM_INCREMENT_SIZE;
99          }
100 
101         CounterRegister register = getCounterRegister(name);
102 
103         return competeIncrement(register, size);
104     }
105 
106     public void rename(String oldName, String newName) throws SystemException {
107         CounterRegister register = getCounterRegister(oldName);
108 
109         synchronized (register) {
110             if (_registerLookup.containsKey(newName)) {
111                 throw new SystemException(
112                     "Cannot rename " + oldName + " to " + newName);
113             }
114 
115             Connection con = null;
116             PreparedStatement ps = null;
117 
118             try {
119                 con = getConnection();
120 
121                 ps = con.prepareStatement(_SQL_UPDATE_NAME_BY_NAME);
122 
123                 ps.setString(1, newName);
124                 ps.setString(2, oldName);
125 
126                 ps.executeUpdate();
127             }
128             catch (ObjectNotFoundException onfe) {
129             }
130             catch (Exception e) {
131                 throw processException(e);
132             }
133             finally {
134                 DataAccess.cleanUp(con, ps);
135             }
136 
137             register.setName(newName);
138 
139             _registerLookup.put(newName, register);
140             _registerLookup.remove(oldName);
141         }
142     }
143 
144     public void reset(String name) throws SystemException {
145         CounterRegister register = getCounterRegister(name);
146 
147         synchronized (register) {
148             Connection con = null;
149             PreparedStatement ps = null;
150 
151             try {
152                 con = getConnection();
153 
154                 ps = con.prepareStatement(_SQL_DELETE_BY_NAME);
155 
156                 ps.setString(1, name);
157 
158                 ps.executeUpdate();
159             }
160             catch (ObjectNotFoundException onfe) {
161             }
162             catch (Exception e) {
163                 throw processException(e);
164             }
165             finally {
166                 DataAccess.cleanUp(con, ps);
167             }
168 
169             _registerLookup.remove(name);
170         }
171     }
172 
173     public void reset(String name, long size) throws SystemException {
174         CounterRegister register = createCounterRegister(name, size);
175 
176         _registerLookup.put(name, register);
177     }
178 
179     protected CounterRegister createCounterRegister(String name)
180         throws SystemException {
181 
182         return createCounterRegister(name, -1);
183     }
184 
185     protected CounterRegister createCounterRegister(String name, long size)
186         throws SystemException {
187 
188         long rangeMin = -1;
189         long rangeMax = -1;
190 
191         Connection con = null;
192         PreparedStatement ps = null;
193         ResultSet rs = null;
194 
195         try {
196             con = getConnection();
197 
198             ps = con.prepareStatement(_SQL_SELECT_ID_BY_NAME);
199 
200             ps.setString(1, name);
201 
202             rs = ps.executeQuery();
203 
204             if (rs.next()) {
205                 rangeMin = rs.getLong(1);
206                 rangeMax = rangeMin + PropsValues.COUNTER_INCREMENT;
207 
208                 ps.close();
209 
210                 ps = con.prepareStatement(_SQL_UPDATE_ID_BY_NAME);
211 
212                 ps.setLong(1, rangeMax);
213                 ps.setString(2, name);
214             }
215             else {
216                 rangeMin = _DEFAULT_CURRENT_ID;
217                 rangeMax = rangeMin + PropsValues.COUNTER_INCREMENT;
218 
219                 ps.close();
220 
221                 ps = con.prepareStatement(_SQL_INSERT);
222 
223                 ps.setString(1, name);
224                 ps.setLong(2, rangeMax);
225             }
226 
227             ps.executeUpdate();
228         }
229         catch (Exception e) {
230             throw processException(e);
231         }
232         finally {
233             DataAccess.cleanUp(con, ps, rs);
234         }
235 
236         if (size > rangeMin) {
237             rangeMin = size;
238         }
239 
240         CounterRegister register = new CounterRegister(
241             name, rangeMin, rangeMax, PropsValues.COUNTER_INCREMENT);
242 
243         return register;
244     }
245 
246     protected Connection getConnection() throws SQLException {
247         Connection con = getDataSource().getConnection();
248 
249         con.setAutoCommit(true);
250 
251         return con;
252     }
253 
254     protected CounterRegister getCounterRegister(String name)
255         throws SystemException {
256 
257         CounterRegister register = _registerLookup.get(name);
258 
259         if (register != null) {
260             return register;
261         }
262         else {
263             synchronized (_registerLookup) {
264 
265                 // Double check
266 
267                 register = _registerLookup.get(name);
268 
269                 if (register == null) {
270                     register = createCounterRegister(name);
271 
272                     _registerLookup.put(name, register);
273                 }
274 
275                 return register;
276             }
277         }
278     }
279 
280     private long competeIncrement(CounterRegister register, int size)
281         throws SystemException {
282 
283         CounterHolder holder = register.getCounterHolder();
284 
285         // Try to use the fast path
286 
287         long newValue = holder.addAndGet(size);
288 
289         if (newValue <= holder.getRangeMax()) {
290             return newValue;
291         }
292 
293         // Use the slow path
294 
295         CompeteLatch latch = register.getCompeteLatch();
296 
297         if (!latch.compete()) {
298 
299             // Loser thread has to wait for the winner thread to finish its job
300 
301             latch.await();
302 
303             // Compete again
304 
305             return competeIncrement(register, size);
306         }
307 
308         // Winner thread
309 
310         Connection con = null;
311         PreparedStatement ps = null;
312         ResultSet rs = null;
313 
314         try {
315 
316             // Double check
317 
318             holder = register.getCounterHolder();
319             newValue = holder.addAndGet(size);
320 
321             if (newValue > holder.getRangeMax()) {
322                 con = getConnection();
323 
324                 ps = con.prepareStatement(_SQL_SELECT_ID_BY_NAME);
325 
326                 ps.setString(1, register.getName());
327 
328                 rs = ps.executeQuery();
329 
330                 rs.next();
331 
332                 long currentId = rs.getLong(1);
333 
334                 newValue = currentId + 1;
335                 long rangeMax = currentId + register.getRangeSize();
336 
337                 ps.close();
338 
339                 ps = con.prepareStatement(_SQL_UPDATE_ID_BY_NAME);
340 
341                 ps.setLong(1, rangeMax);
342                 ps.setString(2, register.getName());
343 
344                 ps.executeUpdate();
345 
346                 register.setCounterHolder(
347                     new CounterHolder(newValue, rangeMax));
348             }
349         }
350         catch (Exception e) {
351             throw processException(e);
352         }
353         finally {
354             DataAccess.cleanUp(con, ps, rs);
355 
356             // Winner thread opens the latch so that loser threads can continue
357 
358             latch.done();
359         }
360 
361         return newValue;
362     }
363 
364     private static final int _DEFAULT_CURRENT_ID = 0;
365 
366     private static final int _MINIMUM_INCREMENT_SIZE = 1;
367 
368     private static final String _NAME = Counter.class.getName();
369 
370     private static final String _SQL_DELETE_BY_NAME =
371         "delete from Counter where name = ?";
372 
373     private static final String _SQL_INSERT =
374         "insert into Counter(name, currentId) values (?, ?)";
375 
376     private static final String _SQL_SELECT_ID_BY_NAME =
377         "select currentId from Counter where name = ?";
378 
379     private static final String _SQL_SELECT_NAMES =
380         "select name from Counter order by name asc";
381 
382     private static final String _SQL_UPDATE_ID_BY_NAME =
383         "update Counter set currentId = ? where name = ?";
384 
385     private static final String _SQL_UPDATE_NAME_BY_NAME =
386         "update Counter set name = ? where name = ?";
387 
388     private static final Map<String, CounterRegister> _registerLookup =
389         new ConcurrentHashMap<String, CounterRegister>();
390 
391 }