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.model.Dummy;
33  import com.liferay.portal.service.persistence.impl.BasePersistenceImpl;
34  import com.liferay.portal.util.PropsValues;
35  
36  import java.sql.Connection;
37  import java.sql.PreparedStatement;
38  import java.sql.ResultSet;
39  import java.sql.SQLException;
40  
41  import java.util.ArrayList;
42  import java.util.List;
43  import java.util.Map;
44  import java.util.concurrent.ConcurrentHashMap;
45  
46  /**
47   * <a href="CounterPersistenceImpl.java.html"><b><i>View Source</i></b></a>
48   *
49   * @author Brian Wing Shun Chan
50   * @author Harry Mark
51   * @author Michael Young
52   * @author Shuyang Zhou
53   */
54  public class CounterPersistenceImpl
55      extends BasePersistenceImpl<Dummy> implements CounterPersistence {
56  
57      public List<String> getNames() throws SystemException {
58          Connection connection = null;
59          PreparedStatement ps = null;
60          ResultSet rs = null;
61  
62          try {
63              connection = getConnection();
64  
65              ps = connection.prepareStatement(_SQL_SELECT_NAMES);
66  
67              rs = ps.executeQuery();
68  
69              List<String> list = new ArrayList<String>();
70  
71              while (rs.next()) {
72                  list.add(rs.getString(1));
73              }
74  
75              return list;
76          }
77          catch (SQLException sqle) {
78              throw processException(sqle);
79          }
80          finally {
81              DataAccess.cleanUp(connection, ps, rs);
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 register = getCounterRegister(name);
99  
100         return competeIncrement(register, size);
101     }
102 
103     public void rename(String oldName, String newName) throws SystemException {
104         CounterRegister register = getCounterRegister(oldName);
105 
106         synchronized (register) {
107             if (_registerLookup.containsKey(newName)) {
108                 throw new SystemException(
109                     "Cannot rename " + oldName + " to " + newName);
110             }
111 
112             Connection connection = null;
113             PreparedStatement ps = null;
114 
115             try {
116                 connection = getConnection();
117 
118                 ps = connection.prepareStatement(_SQL_UPDATE_NAME_BY_NAME);
119 
120                 ps.setString(1, newName);
121                 ps.setString(2, oldName);
122 
123                 ps.executeUpdate();
124             }
125             catch (ObjectNotFoundException onfe) {
126             }
127             catch (Exception e) {
128                 throw processException(e);
129             }
130             finally {
131                 DataAccess.cleanUp(connection, ps);
132             }
133 
134             register.setName(newName);
135 
136             _registerLookup.put(newName, register);
137             _registerLookup.remove(oldName);
138         }
139     }
140 
141     public void reset(String name) throws SystemException {
142         CounterRegister register = getCounterRegister(name);
143 
144         synchronized (register) {
145             Connection connection = null;
146             PreparedStatement ps = null;
147 
148             try {
149                 connection = getConnection();
150 
151                 ps = connection.prepareStatement(_SQL_DELETE_BY_NAME);
152 
153                 ps.setString(1, name);
154 
155                 ps.executeUpdate();
156             }
157             catch (ObjectNotFoundException onfe) {
158             }
159             catch (Exception e) {
160                 throw processException(e);
161             }
162             finally {
163                 DataAccess.cleanUp(connection, ps);
164             }
165 
166             _registerLookup.remove(name);
167         }
168     }
169 
170     public void reset(String name, long size) throws SystemException {
171         CounterRegister register = createCounterRegister(name, size);
172 
173         _registerLookup.put(name, register);
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         long rangeMax = -1;
187 
188         Connection connection = null;
189         PreparedStatement ps = null;
190         ResultSet rs = null;
191 
192         try {
193             connection = getConnection();
194 
195             ps = connection.prepareStatement(_SQL_SELECT_ID_BY_NAME);
196 
197             ps.setString(1, name);
198 
199             rs = ps.executeQuery();
200 
201             if (rs.next()) {
202                 rangeMin = rs.getLong(1);
203                 rangeMax = rangeMin + PropsValues.COUNTER_INCREMENT;
204 
205                 rs.close();
206                 ps.close();
207 
208                 ps = connection.prepareStatement(_SQL_UPDATE_ID_BY_NAME);
209 
210                 ps.setLong(1, rangeMax);
211                 ps.setString(2, name);
212             }
213             else {
214                 rangeMin = _DEFAULT_CURRENT_ID;
215                 rangeMax = rangeMin + PropsValues.COUNTER_INCREMENT;
216 
217                 rs.close();
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                 rs.close();
337                 ps.close();
338 
339                 ps = connection.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(connection, 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 }