1   /**
2    * Copyright (c) 2000-2009 Liferay, Inc. All rights reserved.
3    *
4    *
5    *
6    *
7    * The contents of this file are subject to the terms of the Liferay Enterprise
8    * Subscription License ("License"). You may not use this file except in
9    * compliance with the License. You can obtain a copy of the License by
10   * contacting Liferay, Inc. See the License for the specific language governing
11   * permissions and limitations under the License, including but not limited to
12   * distribution rights 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  public class CounterPersistence extends BasePersistenceImpl {
54  
55      public static int getCounterIncrement() {
56          return PropsValues.COUNTER_INCREMENT;
57      }
58  
59      public List<String> getNames() throws SystemException {
60          Connection connection = null;
61          PreparedStatement ps = null;
62          ResultSet rs = null;
63  
64          try {
65              connection = getConnection();
66  
67              ps = connection.prepareStatement(_SQL_SELECT_NAMES);
68  
69              rs = ps.executeQuery();
70  
71              List<String> list = new ArrayList<String>();
72  
73              while (rs.next()) {
74                  list.add(rs.getString(1));
75              }
76  
77              return list;
78          }
79          catch (SQLException sqle) {
80              throw processException(sqle);
81          }
82          finally {
83              DataAccess.cleanUp(connection, ps, rs);
84          }
85      }
86  
87      public long increment() throws SystemException {
88          return increment(_NAME);
89      }
90  
91      public long increment(String name) throws SystemException {
92          return increment(name, _MINIMUM_INCREMENT_SIZE);
93      }
94  
95      public long increment(String name, int size) throws SystemException {
96          if (size < _MINIMUM_INCREMENT_SIZE) {
97              size = _MINIMUM_INCREMENT_SIZE;
98          }
99  
100         CounterRegister register = getCounterRegister(name);
101 
102         return competeIncrement(register, size);
103     }
104 
105     public void rename(String oldName, String newName) throws SystemException {
106         CounterRegister register = getCounterRegister(oldName);
107 
108         synchronized (register) {
109             if (_registerLookup.containsKey(newName)) {
110                 throw new SystemException(
111                     "Cannot rename " + oldName + " to " + newName);
112             }
113 
114             Connection connection = null;
115             PreparedStatement ps = null;
116 
117             try {
118                 connection = getConnection();
119 
120                 ps = connection.prepareStatement(_SQL_UPDATE_NAME_BY_NAME);
121 
122                 ps.setString(1, newName);
123                 ps.setString(2, oldName);
124 
125                 ps.executeUpdate();
126             }
127             catch (ObjectNotFoundException onfe) {
128             }
129             catch (Exception e) {
130                 throw processException(e);
131             }
132             finally {
133                 DataAccess.cleanUp(connection, ps);
134             }
135 
136             register.setName(newName);
137 
138             _registerLookup.put(newName, register);
139             _registerLookup.remove(oldName);
140         }
141     }
142 
143     public void reset(String name) throws SystemException {
144         CounterRegister register = getCounterRegister(name);
145 
146         synchronized (register) {
147             Connection connection = null;
148             PreparedStatement ps = null;
149 
150             try {
151                 connection = getConnection();
152 
153                 ps = connection.prepareStatement(_SQL_DELETE_BY_NAME);
154 
155                 ps.setString(1, name);
156 
157                 ps.executeUpdate();
158             }
159             catch (ObjectNotFoundException onfe) {
160             }
161             catch (Exception e) {
162                 throw processException(e);
163             }
164             finally {
165                 DataAccess.cleanUp(connection, ps);
166             }
167 
168             _registerLookup.remove(name);
169         }
170     }
171 
172     public void reset(String name, long size) throws SystemException {
173         CounterRegister register = createCounterRegister(name, size);
174 
175         _registerLookup.put(name, register);
176     }
177 
178     protected CounterRegister createCounterRegister(String name)
179         throws SystemException {
180 
181         return createCounterRegister(name, -1);
182     }
183 
184     protected CounterRegister createCounterRegister(String name, long size)
185         throws SystemException {
186 
187         long rangeMin = -1;
188         long rangeMax = -1;
189 
190         Connection connection = null;
191         PreparedStatement ps = null;
192         ResultSet rs = null;
193 
194         try {
195             connection = getConnection();
196 
197             ps = connection.prepareStatement(_SQL_SELECT_ID_BY_NAME);
198 
199             ps.setString(1, name);
200 
201             rs = ps.executeQuery();
202 
203             if (rs.next()) {
204                 rangeMin = rs.getLong(1);
205                 rangeMax = rangeMin + PropsValues.COUNTER_INCREMENT;
206 
207                 ps.close();
208 
209                 ps = connection.prepareStatement(_SQL_UPDATE_ID_BY_NAME);
210 
211                 ps.setLong(1, rangeMax);
212                 ps.setString(2, name);
213             }
214             else {
215                 rangeMin = _DEFAULT_CURRENT_ID;
216                 rangeMax = rangeMin + PropsValues.COUNTER_INCREMENT;
217 
218                 ps.close();
219 
220                 ps = connection.prepareStatement(_SQL_INSERT);
221 
222                 ps.setString(1, name);
223                 ps.setLong(2, rangeMax);
224             }
225 
226             ps.executeUpdate();
227         }
228         catch (Exception e) {
229             throw processException(e);
230         }
231         finally {
232             DataAccess.cleanUp(connection, ps, rs);
233         }
234 
235         if (size > rangeMin) {
236             rangeMin = size;
237         }
238 
239         CounterRegister register = new CounterRegister(
240             name, rangeMin, rangeMax, PropsValues.COUNTER_INCREMENT);
241 
242         return register;
243     }
244 
245     protected Connection getConnection() throws SQLException {
246         Connection connection = getDataSource().getConnection();
247 
248         connection.setAutoCommit(true);
249 
250         return connection;
251     }
252 
253     protected CounterRegister getCounterRegister(String name)
254         throws SystemException {
255 
256         CounterRegister register = _registerLookup.get(name);
257 
258         if (register != null) {
259             return register;
260         }
261         else {
262             synchronized (_registerLookup) {
263 
264                 // Double check
265 
266                 register = _registerLookup.get(name);
267 
268                 if (register == null) {
269                     register = createCounterRegister(name);
270 
271                     _registerLookup.put(name, register);
272                 }
273 
274                 return register;
275             }
276         }
277     }
278 
279     private long competeIncrement(CounterRegister register, int size)
280         throws SystemException {
281 
282         CounterHolder holder = register.getCounterHolder();
283 
284         // Try to use the fast path
285 
286         long newValue = holder.addAndGet(size);
287 
288         if (newValue <= holder.getRangeMax()) {
289             return newValue;
290         }
291 
292         // Use the slow path
293 
294         CompeteLatch latch = register.getCompeteLatch();
295 
296         if (!latch.compete()) {
297 
298             // Loser thread has to wait for the winner thread to finish its job
299 
300             latch.await();
301 
302             // Compete again
303 
304             return competeIncrement(register, size);
305         }
306 
307         // Winner thread
308 
309         Connection connection = null;
310         PreparedStatement ps = null;
311         ResultSet rs = null;
312 
313         try {
314 
315             // Double check
316 
317             holder = register.getCounterHolder();
318             newValue = holder.addAndGet(size);
319 
320             if (newValue > holder.getRangeMax()) {
321                 connection = getConnection();
322 
323                 ps = connection.prepareStatement(_SQL_SELECT_ID_BY_NAME);
324 
325                 ps.setString(1, register.getName());
326 
327                 rs = ps.executeQuery();
328 
329                 rs.next();
330 
331                 long currentId = rs.getLong(1);
332 
333                 newValue = currentId + 1;
334                 long rangeMax = currentId + register.getRangeSize();
335 
336                 ps.close();
337 
338                 ps = connection.prepareStatement(_SQL_UPDATE_ID_BY_NAME);
339 
340                 ps.setLong(1, rangeMax);
341                 ps.setString(2, register.getName());
342 
343                 ps.executeUpdate();
344 
345                 register.setCounterHolder(
346                     new CounterHolder(newValue, rangeMax));
347             }
348         }
349         catch (Exception e) {
350             throw processException(e);
351         }
352         finally {
353             DataAccess.cleanUp(connection, ps, rs);
354 
355             // Winner thread opens the latch so that loser threads can continue
356 
357             latch.done();
358         }
359 
360         return newValue;
361     }
362 
363     private static final int _DEFAULT_CURRENT_ID = 0;
364 
365     private static final int _MINIMUM_INCREMENT_SIZE = 1;
366 
367     private static final String _NAME = Counter.class.getName();
368 
369     private static final String _SQL_DELETE_BY_NAME =
370         "delete from Counter where name = ?";
371 
372     private static final String _SQL_INSERT =
373         "insert into Counter(name, currentId) values (?, ?)";
374 
375     private static final String _SQL_SELECT_ID_BY_NAME =
376         "select currentId from Counter where name = ?";
377 
378     private static final String _SQL_SELECT_NAMES =
379         "select name from Counter order by name asc";
380 
381     private static final String _SQL_UPDATE_ID_BY_NAME =
382         "update Counter set currentId = ? where name = ?";
383 
384     private static final String _SQL_UPDATE_NAME_BY_NAME =
385         "update Counter set name = ? where name = ?";
386 
387     private static final Map<String, CounterRegister> _registerLookup =
388         new ConcurrentHashMap<String, CounterRegister>();
389 
390 }