001    /**
002     * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.counter.service.persistence.impl;
016    
017    import com.liferay.counter.model.Counter;
018    import com.liferay.counter.model.CounterHolder;
019    import com.liferay.counter.model.CounterRegister;
020    import com.liferay.counter.model.impl.CounterImpl;
021    import com.liferay.counter.service.persistence.CounterFinder;
022    import com.liferay.portal.kernel.cache.CacheRegistryItem;
023    import com.liferay.portal.kernel.cache.CacheRegistryUtil;
024    import com.liferay.portal.kernel.concurrent.CompeteLatch;
025    import com.liferay.portal.kernel.dao.jdbc.DataAccess;
026    import com.liferay.portal.kernel.dao.orm.LockMode;
027    import com.liferay.portal.kernel.dao.orm.ObjectNotFoundException;
028    import com.liferay.portal.kernel.dao.orm.Session;
029    import com.liferay.portal.kernel.exception.SystemException;
030    import com.liferay.portal.kernel.util.CharPool;
031    import com.liferay.portal.kernel.util.GetterUtil;
032    import com.liferay.portal.kernel.util.PropsKeys;
033    import com.liferay.portal.model.Dummy;
034    import com.liferay.portal.service.persistence.impl.BasePersistenceImpl;
035    import com.liferay.portal.util.PropsUtil;
036    import com.liferay.portal.util.PropsValues;
037    
038    import java.sql.Connection;
039    import java.sql.PreparedStatement;
040    import java.sql.ResultSet;
041    import java.sql.SQLException;
042    
043    import java.util.ArrayList;
044    import java.util.List;
045    import java.util.Map;
046    import java.util.concurrent.ConcurrentHashMap;
047    
048    /**
049     * @author Brian Wing Shun Chan
050     * @author Harry Mark
051     * @author Michael Young
052     * @author Shuyang Zhou
053     * @author Edward Han
054     */
055    public class CounterFinderImpl
056            extends BasePersistenceImpl<Dummy>
057            implements CacheRegistryItem, CounterFinder {
058    
059            @Override
060            public void afterPropertiesSet() {
061                    CacheRegistryUtil.register(this);
062            }
063    
064            @Override
065            public List<String> getNames() {
066                    Connection connection = null;
067                    PreparedStatement preparedStatement = null;
068                    ResultSet resultSet = null;
069    
070                    try {
071                            connection = getConnection();
072    
073                            preparedStatement = connection.prepareStatement(_SQL_SELECT_NAMES);
074    
075                            resultSet = preparedStatement.executeQuery();
076    
077                            List<String> list = new ArrayList<>();
078    
079                            while (resultSet.next()) {
080                                    list.add(resultSet.getString(1));
081                            }
082    
083                            return list;
084                    }
085                    catch (SQLException sqle) {
086                            throw processException(sqle);
087                    }
088                    finally {
089                            DataAccess.cleanUp(connection, preparedStatement, resultSet);
090                    }
091            }
092    
093            @Override
094            public String getRegistryName() {
095                    return CounterFinderImpl.class.getName();
096            }
097    
098            @Override
099            public long increment() {
100                    return increment(_NAME);
101            }
102    
103            @Override
104            public long increment(String name) {
105                    return increment(name, _MINIMUM_INCREMENT_SIZE);
106            }
107    
108            @Override
109            public long increment(String name, int size) {
110                    if (size < _MINIMUM_INCREMENT_SIZE) {
111                            size = _MINIMUM_INCREMENT_SIZE;
112                    }
113    
114                    CounterRegister counterRegister = getCounterRegister(name);
115    
116                    return _competeIncrement(counterRegister, size);
117            }
118    
119            @Override
120            public void invalidate() {
121                    _counterRegisterMap.clear();
122            }
123    
124            @Override
125            public void rename(String oldName, String newName) {
126                    CounterRegister counterRegister = getCounterRegister(oldName);
127    
128                    synchronized (counterRegister) {
129                            if (_counterRegisterMap.containsKey(newName)) {
130                                    throw new SystemException(
131                                            "Cannot rename " + oldName + " to " + newName);
132                            }
133    
134                            Connection connection = null;
135                            PreparedStatement preparedStatement = null;
136    
137                            try {
138                                    connection = getConnection();
139    
140                                    preparedStatement = connection.prepareStatement(
141                                            _SQL_UPDATE_NAME_BY_NAME);
142    
143                                    preparedStatement.setString(1, newName);
144                                    preparedStatement.setString(2, oldName);
145    
146                                    preparedStatement.executeUpdate();
147                            }
148                            catch (ObjectNotFoundException onfe) {
149                            }
150                            catch (Exception e) {
151                                    throw processException(e);
152                            }
153                            finally {
154                                    DataAccess.cleanUp(connection, preparedStatement);
155                            }
156    
157                            counterRegister.setName(newName);
158    
159                            _counterRegisterMap.put(newName, counterRegister);
160                            _counterRegisterMap.remove(oldName);
161                    }
162            }
163    
164            @Override
165            public void reset(String name) {
166                    CounterRegister counterRegister = getCounterRegister(name);
167    
168                    synchronized (counterRegister) {
169                            Session session = null;
170    
171                            try {
172                                    session = openSession();
173    
174                                    Counter counter = (Counter)session.get(CounterImpl.class, name);
175    
176                                    session.delete(counter);
177    
178                                    session.flush();
179                            }
180                            catch (ObjectNotFoundException onfe) {
181                            }
182                            catch (Exception e) {
183                                    throw processException(e);
184                            }
185                            finally {
186                                    closeSession(session);
187                            }
188    
189                            _counterRegisterMap.remove(name);
190                    }
191            }
192    
193            @Override
194            public void reset(String name, long size) {
195                    CounterRegister counterRegister = createCounterRegister(name, size);
196    
197                    _counterRegisterMap.put(name, counterRegister);
198            }
199    
200            protected CounterRegister createCounterRegister(String name) {
201                    return createCounterRegister(name, -1);
202            }
203    
204            protected CounterRegister createCounterRegister(String name, long size) {
205                    long rangeMin = -1;
206                    int rangeSize = getRangeSize(name);
207    
208                    Connection connection = null;
209                    PreparedStatement preparedStatement = null;
210                    ResultSet resultSet = null;
211    
212                    try {
213                            connection = getConnection();
214    
215                            preparedStatement = connection.prepareStatement(
216                                    _SQL_SELECT_ID_BY_NAME);
217    
218                            preparedStatement.setString(1, name);
219    
220                            resultSet = preparedStatement.executeQuery();
221    
222                            if (!resultSet.next()) {
223                                    rangeMin = _DEFAULT_CURRENT_ID;
224    
225                                    if (size > rangeMin) {
226                                            rangeMin = size;
227                                    }
228    
229                                    resultSet.close();
230                                    preparedStatement.close();
231    
232                                    preparedStatement = connection.prepareStatement(_SQL_INSERT);
233    
234                                    preparedStatement.setString(1, name);
235                                    preparedStatement.setLong(2, rangeMin);
236    
237                                    preparedStatement.executeUpdate();
238                            }
239                    }
240                    catch (Exception e) {
241                            throw processException(e);
242                    }
243                    finally {
244                            DataAccess.cleanUp(connection, preparedStatement, resultSet);
245                    }
246    
247                    CounterHolder counterHolder = _obtainIncrement(name, rangeSize, size);
248    
249                    return new CounterRegister(name, counterHolder, rangeSize);
250            }
251    
252            protected Connection getConnection() throws SQLException {
253                    Connection connection = getDataSource().getConnection();
254    
255                    return connection;
256            }
257    
258            protected CounterRegister getCounterRegister(String name) {
259                    CounterRegister counterRegister = _counterRegisterMap.get(name);
260    
261                    if (counterRegister != null) {
262                            return counterRegister;
263                    }
264    
265                    synchronized (_counterRegisterMap) {
266    
267                            // Double check
268    
269                            counterRegister = _counterRegisterMap.get(name);
270    
271                            if (counterRegister == null) {
272                                    counterRegister = createCounterRegister(name);
273    
274                                    _counterRegisterMap.put(name, counterRegister);
275                            }
276    
277                            return counterRegister;
278                    }
279            }
280    
281            protected int getRangeSize(String name) {
282                    if (name.equals(_NAME)) {
283                            return PropsValues.COUNTER_INCREMENT;
284                    }
285    
286                    String incrementType = null;
287    
288                    int pos = name.indexOf(CharPool.POUND);
289    
290                    if (pos != -1) {
291                            incrementType = name.substring(0, pos);
292                    }
293                    else {
294                            incrementType = name;
295                    }
296    
297                    Integer rangeSize = _rangeSizeMap.get(incrementType);
298    
299                    if (rangeSize == null) {
300                            rangeSize = GetterUtil.getInteger(
301                                    PropsUtil.get(
302                                            PropsKeys.COUNTER_INCREMENT_PREFIX + incrementType),
303                                    PropsValues.COUNTER_INCREMENT);
304    
305                            _rangeSizeMap.put(incrementType, rangeSize);
306                    }
307    
308                    return rangeSize.intValue();
309            }
310    
311            private long _competeIncrement(CounterRegister counterRegister, int size) {
312                    CounterHolder counterHolder = counterRegister.getCounterHolder();
313    
314                    // Try to use the fast path
315    
316                    long newValue = counterHolder.addAndGet(size);
317    
318                    if (newValue <= counterHolder.getRangeMax()) {
319                            return newValue;
320                    }
321    
322                    // Use the slow path
323    
324                    CompeteLatch completeLatch = counterRegister.getCompeteLatch();
325    
326                    if (!completeLatch.compete()) {
327    
328                            // Loser thread has to wait for the winner thread to finish its job
329    
330                            try {
331                                    completeLatch.await();
332                            }
333                            catch (InterruptedException ie) {
334                                    throw processException(ie);
335                            }
336    
337                            // Compete again
338    
339                            return _competeIncrement(counterRegister, size);
340                    }
341    
342                    // Winner thread
343    
344                    try {
345    
346                            // Double check
347    
348                            counterHolder = counterRegister.getCounterHolder();
349                            newValue = counterHolder.addAndGet(size);
350    
351                            if (newValue > counterHolder.getRangeMax()) {
352                                    CounterHolder newCounterHolder = _obtainIncrement(
353                                            counterRegister.getName(), counterRegister.getRangeSize(),
354                                            0);
355    
356                                    newValue = newCounterHolder.addAndGet(size);
357    
358                                    counterRegister.setCounterHolder(newCounterHolder);
359                            }
360                    }
361                    catch (Exception e) {
362                            throw processException(e);
363                    }
364                    finally {
365    
366                            // Winner thread opens the latch so that loser threads can continue
367    
368                            completeLatch.done();
369                    }
370    
371                    return newValue;
372            }
373    
374            private CounterHolder _obtainIncrement(
375                    String counterName, long range, long size) {
376    
377                    Session session = null;
378    
379                    try {
380                            session = openSession();
381    
382                            Counter counter = (Counter)session.get(
383                                    CounterImpl.class, counterName, LockMode.UPGRADE);
384    
385                            long newValue = counter.getCurrentId();
386    
387                            if (size > newValue) {
388                                    newValue = size;
389                            }
390    
391                            long rangeMax = newValue + range;
392    
393                            counter.setCurrentId(rangeMax);
394    
395                            CounterHolder counterHolder = new CounterHolder(newValue, rangeMax);
396    
397                            session.saveOrUpdate(counter);
398    
399                            session.flush();
400    
401                            return counterHolder;
402                    }
403                    catch (Exception e) {
404                            throw processException(e);
405                    }
406                    finally {
407                            closeSession(session);
408                    }
409            }
410    
411            private static final int _DEFAULT_CURRENT_ID = 0;
412    
413            private static final int _MINIMUM_INCREMENT_SIZE = 1;
414    
415            private static final String _NAME = Counter.class.getName();
416    
417            private static final String _SQL_INSERT =
418                    "insert into Counter(name, currentId) values (?, ?)";
419    
420            private static final String _SQL_SELECT_ID_BY_NAME =
421                    "select currentId from Counter where name = ?";
422    
423            private static final String _SQL_SELECT_NAMES =
424                    "select name from Counter order by name asc";
425    
426            private static final String _SQL_UPDATE_NAME_BY_NAME =
427                    "update Counter set name = ? where name = ?";
428    
429            private final Map<String, CounterRegister> _counterRegisterMap =
430                    new ConcurrentHashMap<>();
431            private final Map<String, Integer> _rangeSizeMap =
432                    new ConcurrentHashMap<>();
433    
434    }