1   /**
2    * Copyright (c) 2000-2010 Liferay, Inc. All rights reserved.
3    *
4    * This library is free software; you can redistribute it and/or modify it under
5    * the terms of the GNU Lesser General Public License as published by the Free
6    * Software Foundation; either version 2.1 of the License, or (at your option)
7    * any later version.
8    *
9    * This library is distributed in the hope that it will be useful, but WITHOUT
10   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11   * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
12   * details.
13   */
14  
15  package com.liferay.counter.service.persistence;
16  
17  import com.liferay.counter.model.Counter;
18  import com.liferay.counter.model.CounterHolder;
19  import com.liferay.counter.model.CounterRegister;
20  import com.liferay.counter.model.impl.CounterImpl;
21  import com.liferay.portal.kernel.concurrent.CompeteLatch;
22  import com.liferay.portal.kernel.dao.jdbc.DataAccess;
23  import com.liferay.portal.kernel.dao.orm.LockMode;
24  import com.liferay.portal.kernel.dao.orm.ObjectNotFoundException;
25  import com.liferay.portal.kernel.dao.orm.Session;
26  import com.liferay.portal.kernel.exception.SystemException;
27  import com.liferay.portal.kernel.util.GetterUtil;
28  import com.liferay.portal.kernel.util.PropsKeys;
29  import com.liferay.portal.kernel.util.StringPool;
30  import com.liferay.portal.model.Dummy;
31  import com.liferay.portal.service.persistence.impl.BasePersistenceImpl;
32  import com.liferay.portal.util.PropsUtil;
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="CounterFinderImpl.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   * @author Edward Han
53   */
54  public class CounterFinderImpl
55      extends BasePersistenceImpl<Dummy> implements CounterFinder {
56  
57      public List<String> getNames() throws SystemException {
58          Connection connection = null;
59          PreparedStatement preparedStatement = null;
60          ResultSet resultSet = null;
61  
62          try {
63              connection = getConnection();
64  
65              preparedStatement = connection.prepareStatement(_SQL_SELECT_NAMES);
66  
67              resultSet = preparedStatement.executeQuery();
68  
69              List<String> list = new ArrayList<String>();
70  
71              while (resultSet.next()) {
72                  list.add(resultSet.getString(1));
73              }
74  
75              return list;
76          }
77          catch (SQLException sqle) {
78              throw processException(sqle);
79          }
80          finally {
81              DataAccess.cleanUp(connection, preparedStatement, resultSet);
82          }
83      }
84  
85      public long increment() throws SystemException {
86          return increment(_NAME);
87      }
88  
89      public long increment(String name) throws SystemException {
90          return increment(name, _MINIMUM_INCREMENT_SIZE);
91      }
92  
93      public long increment(String name, int size) throws SystemException {
94          if (size < _MINIMUM_INCREMENT_SIZE) {
95              size = _MINIMUM_INCREMENT_SIZE;
96          }
97  
98          CounterRegister counterRegister = getCounterRegister(name);
99  
100         return _competeIncrement(counterRegister, size);
101     }
102 
103     public void rename(String oldName, String newName) throws SystemException {
104         CounterRegister counterRegister = getCounterRegister(oldName);
105 
106         synchronized (counterRegister) {
107             if (_counterRegisterMap.containsKey(newName)) {
108                 throw new SystemException(
109                     "Cannot rename " + oldName + " to " + newName);
110             }
111 
112             Connection connection = null;
113             PreparedStatement preparedStatement = null;
114 
115             try {
116                 connection = getConnection();
117 
118                 preparedStatement = connection.prepareStatement(
119                     _SQL_UPDATE_NAME_BY_NAME);
120 
121                 preparedStatement.setString(1, newName);
122                 preparedStatement.setString(2, oldName);
123 
124                 preparedStatement.executeUpdate();
125             }
126             catch (ObjectNotFoundException onfe) {
127             }
128             catch (Exception e) {
129                 throw processException(e);
130             }
131             finally {
132                 DataAccess.cleanUp(connection, preparedStatement);
133             }
134 
135             counterRegister.setName(newName);
136 
137             _counterRegisterMap.put(newName, counterRegister);
138             _counterRegisterMap.remove(oldName);
139         }
140     }
141 
142     public void reset(String name) throws SystemException {
143         CounterRegister counterRegister = getCounterRegister(name);
144 
145         synchronized (counterRegister) {
146             Session session = null;
147 
148             try {
149                 session = openSession();
150 
151                 Counter counter = (Counter)session.get(CounterImpl.class, name);
152 
153                 session.delete(counter);
154 
155                 session.flush();
156             }
157             catch (ObjectNotFoundException onfe) {
158             }
159             catch (Exception e) {
160                 throw processException(e);
161             }
162             finally {
163                 closeSession(session);
164             }
165 
166             _counterRegisterMap.remove(name);
167         }
168     }
169 
170     public void reset(String name, long size) throws SystemException {
171         CounterRegister counterRegister = createCounterRegister(name, size);
172 
173         _counterRegisterMap.put(name, counterRegister);
174     }
175 
176     protected CounterRegister createCounterRegister(String name)
177         throws SystemException {
178 
179         return createCounterRegister(name, -1);
180     }
181 
182     protected CounterRegister createCounterRegister(String name, long size)
183         throws SystemException {
184 
185         long rangeMin = -1;
186         int rangeSize = getRangeSize(name);
187 
188         Connection connection = null;
189         PreparedStatement preparedStatement = null;
190         ResultSet resultSet = null;
191 
192         try {
193             connection = getConnection();
194 
195             preparedStatement = connection.prepareStatement(
196                 _SQL_SELECT_ID_BY_NAME);
197 
198             preparedStatement.setString(1, name);
199 
200             resultSet = preparedStatement.executeQuery();
201 
202             if (!resultSet.next()) {
203                 rangeMin = _DEFAULT_CURRENT_ID;
204 
205                 if (size > rangeMin) {
206                     rangeMin = size;
207                 }
208 
209                 resultSet.close();
210                 preparedStatement.close();
211 
212                 preparedStatement = connection.prepareStatement(_SQL_INSERT);
213 
214                 preparedStatement.setString(1, name);
215                 preparedStatement.setLong(2, rangeMin);
216 
217                 preparedStatement.executeUpdate();
218             }
219         }
220         catch (Exception e) {
221             throw processException(e);
222         }
223         finally {
224             DataAccess.cleanUp(connection, preparedStatement, resultSet);
225         }
226 
227         CounterHolder counterHolder = _obtainIncrement(name, rangeSize, size);
228 
229         return new CounterRegister(name, counterHolder, rangeSize);
230     }
231 
232     protected Connection getConnection() throws SQLException {
233         Connection connection = getDataSource().getConnection();
234 
235         return connection;
236     }
237 
238     protected CounterRegister getCounterRegister(String name)
239         throws SystemException {
240 
241         CounterRegister counterRegister = _counterRegisterMap.get(name);
242 
243         if (counterRegister != null) {
244             return counterRegister;
245         }
246         else {
247             synchronized (_counterRegisterMap) {
248 
249                 // Double check
250 
251                 counterRegister = _counterRegisterMap.get(name);
252 
253                 if (counterRegister == null) {
254                     counterRegister = createCounterRegister(name);
255 
256                     _counterRegisterMap.put(name, counterRegister);
257                 }
258 
259                 return counterRegister;
260             }
261         }
262     }
263 
264     protected int getRangeSize(String name) {
265         if (name.equals(_NAME)) {
266             return PropsValues.COUNTER_INCREMENT;
267         }
268 
269         String incrementType = null;
270 
271         int pos = name.indexOf(StringPool.POUND);
272 
273         if (pos != -1) {
274             incrementType = name.substring(0, pos);
275         }
276         else {
277             incrementType = name;
278         }
279 
280         Integer rangeSize = _rangeSizeMap.get(incrementType);
281 
282         if (rangeSize == null) {
283             rangeSize = GetterUtil.getInteger(
284                 PropsUtil.get(
285                     PropsKeys.COUNTER_INCREMENT_PREFIX + incrementType),
286                 PropsValues.COUNTER_INCREMENT);
287 
288             _rangeSizeMap.put(incrementType, rangeSize);
289         }
290 
291         return rangeSize.intValue();
292     }
293 
294     private long _competeIncrement(CounterRegister counterRegister, int size)
295         throws SystemException {
296 
297         CounterHolder counterHolder = counterRegister.getCounterHolder();
298 
299         // Try to use the fast path
300 
301         long newValue = counterHolder.addAndGet(size);
302 
303         if (newValue <= counterHolder.getRangeMax()) {
304             return newValue;
305         }
306 
307         // Use the slow path
308 
309         CompeteLatch completeLatch = counterRegister.getCompeteLatch();
310 
311         if (!completeLatch.compete()) {
312 
313             // Loser thread has to wait for the winner thread to finish its job
314 
315             completeLatch.await();
316 
317             // Compete again
318 
319             return _competeIncrement(counterRegister, size);
320         }
321 
322         // Winner thread
323 
324         try {
325 
326             // Double check
327 
328             counterHolder = counterRegister.getCounterHolder();
329             newValue = counterHolder.addAndGet(size);
330 
331             if (newValue > counterHolder.getRangeMax()) {
332                 CounterHolder newCounterHolder = _obtainIncrement(
333                     counterRegister.getName(), counterRegister.getRangeSize(),
334                     0);
335 
336                 newValue = newCounterHolder.addAndGet(size);
337 
338                 counterRegister.setCounterHolder(newCounterHolder);
339             }
340         }
341         catch (Exception e) {
342             throw processException(e);
343         }
344         finally {
345 
346             // Winner thread opens the latch so that loser threads can continue
347 
348             completeLatch.done();
349         }
350 
351         return newValue;
352     }
353 
354     private CounterHolder _obtainIncrement(
355             String counterName, long range, long size)
356         throws SystemException {
357 
358         Session session = null;
359 
360         try {
361             session = openSession();
362 
363             Counter counter = (Counter)session.get(
364                 CounterImpl.class, counterName, LockMode.UPGRADE);
365 
366             long newValue = counter.getCurrentId();
367 
368             if (size > newValue) {
369                 newValue = size;
370             }
371 
372             long rangeMax = newValue + range;
373 
374             counter.setCurrentId(rangeMax);
375 
376             CounterHolder counterHolder = new CounterHolder(newValue, rangeMax);
377 
378             session.saveOrUpdate(counter);
379 
380             session.flush();
381 
382             return counterHolder;
383         }
384         catch (Exception e) {
385             throw processException(e);
386         }
387         finally {
388             closeSession(session);
389         }
390     }
391 
392     private static final int _DEFAULT_CURRENT_ID = 0;
393 
394     private static final int _MINIMUM_INCREMENT_SIZE = 1;
395 
396     private static final String _NAME = Counter.class.getName();
397 
398     private static final String _SQL_INSERT =
399         "insert into Counter(name, currentId) values (?, ?)";
400 
401     private static final String _SQL_SELECT_ID_BY_NAME =
402         "select currentId from Counter where name = ?";
403 
404     private static final String _SQL_SELECT_NAMES =
405         "select name from Counter order by name asc";
406 
407     private static final String _SQL_UPDATE_NAME_BY_NAME =
408         "update Counter set name = ? where name = ?";
409 
410     private Map<String, CounterRegister> _counterRegisterMap =
411         new ConcurrentHashMap<String, CounterRegister>();
412     private Map<String, Integer> _rangeSizeMap =
413         new ConcurrentHashMap<String, Integer>();
414 
415 }