/**
* Copyright (c) 2000-present Liferay, Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*/
package com.liferay.counter.service.persistence.impl;
import com.liferay.counter.kernel.model.Counter;
import com.liferay.counter.kernel.service.persistence.CounterFinder;
import com.liferay.counter.model.CounterHolder;
import com.liferay.counter.model.CounterRegister;
import com.liferay.counter.model.impl.CounterImpl;
import com.liferay.portal.kernel.cache.CacheRegistryItem;
import com.liferay.portal.kernel.concurrent.CompeteLatch;
import com.liferay.portal.kernel.dao.jdbc.DataAccess;
import com.liferay.portal.kernel.dao.orm.LockMode;
import com.liferay.portal.kernel.dao.orm.ObjectNotFoundException;
import com.liferay.portal.kernel.dao.orm.Session;
import com.liferay.portal.kernel.exception.SystemException;
import com.liferay.portal.kernel.model.Dummy;
import com.liferay.portal.kernel.service.persistence.impl.BasePersistenceImpl;
import com.liferay.portal.kernel.util.CharPool;
import com.liferay.portal.kernel.util.GetterUtil;
import com.liferay.portal.kernel.util.PropsKeys;
import com.liferay.portal.util.PropsUtil;
import com.liferay.portal.util.PropsValues;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author Brian Wing Shun Chan
* @author Harry Mark
* @author Michael Young
* @author Shuyang Zhou
* @author Edward Han
*/
public class CounterFinderImpl
extends BasePersistenceImpl<Dummy>
implements CacheRegistryItem, CounterFinder {
@Override
public List<String> getNames() {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = getConnection();
preparedStatement = connection.prepareStatement(_SQL_SELECT_NAMES);
resultSet = preparedStatement.executeQuery();
List<String> list = new ArrayList<>();
while (resultSet.next()) {
list.add(resultSet.getString(1));
}
return list;
}
catch (SQLException sqle) {
throw processException(sqle);
}
finally {
DataAccess.cleanUp(connection, preparedStatement, resultSet);
}
}
@Override
public String getRegistryName() {
return CounterFinderImpl.class.getName();
}
@Override
public long increment() {
return increment(_NAME);
}
@Override
public long increment(String name) {
return increment(name, _MINIMUM_INCREMENT_SIZE);
}
@Override
public long increment(String name, int size) {
if (size < _MINIMUM_INCREMENT_SIZE) {
size = _MINIMUM_INCREMENT_SIZE;
}
CounterRegister counterRegister = getCounterRegister(name);
return _competeIncrement(counterRegister, size);
}
@Override
public void invalidate() {
_counterRegisterMap.clear();
}
@Override
public void rename(String oldName, String newName) {
CounterRegister counterRegister = getCounterRegister(oldName);
synchronized (counterRegister) {
if (_counterRegisterMap.containsKey(newName)) {
throw new SystemException(
"Cannot rename " + oldName + " to " + newName);
}
Connection connection = null;
PreparedStatement preparedStatement = null;
try {
connection = getConnection();
preparedStatement = connection.prepareStatement(
_SQL_UPDATE_NAME_BY_NAME);
preparedStatement.setString(1, newName);
preparedStatement.setString(2, oldName);
preparedStatement.executeUpdate();
}
catch (ObjectNotFoundException onfe) {
}
catch (Exception e) {
throw processException(e);
}
finally {
DataAccess.cleanUp(connection, preparedStatement);
}
counterRegister.setName(newName);
_counterRegisterMap.put(newName, counterRegister);
_counterRegisterMap.remove(oldName);
}
}
@Override
public void reset(String name) {
CounterRegister counterRegister = getCounterRegister(name);
synchronized (counterRegister) {
Session session = null;
try {
session = openSession();
Counter counter = (Counter)session.get(CounterImpl.class, name);
session.delete(counter);
session.flush();
}
catch (ObjectNotFoundException onfe) {
}
catch (Exception e) {
throw processException(e);
}
finally {
closeSession(session);
}
_counterRegisterMap.remove(name);
}
}
@Override
public void reset(String name, long size) {
CounterRegister counterRegister = createCounterRegister(name, size);
_counterRegisterMap.put(name, counterRegister);
}
protected CounterRegister createCounterRegister(String name) {
return createCounterRegister(name, -1);
}
protected CounterRegister createCounterRegister(String name, long size) {
long rangeMin = -1;
int rangeSize = getRangeSize(name);
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = getConnection();
preparedStatement = connection.prepareStatement(
_SQL_SELECT_ID_BY_NAME);
preparedStatement.setString(1, name);
resultSet = preparedStatement.executeQuery();
if (!resultSet.next()) {
rangeMin = _DEFAULT_CURRENT_ID;
if (size > rangeMin) {
rangeMin = size;
}
resultSet.close();
preparedStatement.close();
preparedStatement = connection.prepareStatement(_SQL_INSERT);
preparedStatement.setString(1, name);
preparedStatement.setLong(2, rangeMin);
preparedStatement.executeUpdate();
}
}
catch (Exception e) {
throw processException(e);
}
finally {
DataAccess.cleanUp(connection, preparedStatement, resultSet);
}
CounterHolder counterHolder = _obtainIncrement(name, rangeSize, size);
return new CounterRegister(name, counterHolder, rangeSize);
}
protected Connection getConnection() throws SQLException {
Connection connection = getDataSource().getConnection();
return connection;
}
protected CounterRegister getCounterRegister(String name) {
CounterRegister counterRegister = _counterRegisterMap.get(name);
if (counterRegister != null) {
return counterRegister;
}
synchronized (_counterRegisterMap) {
// Double check
counterRegister = _counterRegisterMap.get(name);
if (counterRegister == null) {
counterRegister = createCounterRegister(name);
_counterRegisterMap.put(name, counterRegister);
}
return counterRegister;
}
}
protected int getRangeSize(String name) {
if (name.equals(_NAME)) {
return PropsValues.COUNTER_INCREMENT;
}
String incrementType = null;
int pos = name.indexOf(CharPool.POUND);
if (pos != -1) {
incrementType = name.substring(0, pos);
}
else {
incrementType = name;
}
Integer rangeSize = _rangeSizeMap.get(incrementType);
if (rangeSize == null) {
rangeSize = GetterUtil.getInteger(
PropsUtil.get(
PropsKeys.COUNTER_INCREMENT_PREFIX + incrementType),
PropsValues.COUNTER_INCREMENT);
_rangeSizeMap.put(incrementType, rangeSize);
}
return rangeSize.intValue();
}
private long _competeIncrement(CounterRegister counterRegister, int size) {
CounterHolder counterHolder = counterRegister.getCounterHolder();
// Try to use the fast path
long newValue = counterHolder.addAndGet(size);
if (newValue <= counterHolder.getRangeMax()) {
return newValue;
}
// Use the slow path
CompeteLatch competeLatch = counterRegister.getCompeteLatch();
if (!competeLatch.compete()) {
// Loser thread has to wait for the winner thread to finish its job
try {
competeLatch.await();
}
catch (InterruptedException ie) {
throw processException(ie);
}
// Compete again
return _competeIncrement(counterRegister, size);
}
// Winner thread
try {
// Double check
counterHolder = counterRegister.getCounterHolder();
newValue = counterHolder.addAndGet(size);
if (newValue > counterHolder.getRangeMax()) {
CounterHolder newCounterHolder = _obtainIncrement(
counterRegister.getName(), counterRegister.getRangeSize(),
0);
newValue = newCounterHolder.addAndGet(size);
counterRegister.setCounterHolder(newCounterHolder);
}
}
catch (Exception e) {
throw processException(e);
}
finally {
// Winner thread opens the latch so that loser threads can continue
competeLatch.done();
}
return newValue;
}
private CounterHolder _obtainIncrement(
String counterName, long range, long size) {
Session session = null;
try {
session = openSession();
Counter counter = (Counter)session.get(
CounterImpl.class, counterName, LockMode.UPGRADE);
long newValue = counter.getCurrentId();
if (size > newValue) {
newValue = size;
}
long rangeMax = newValue + range;
counter.setCurrentId(rangeMax);
CounterHolder counterHolder = new CounterHolder(newValue, rangeMax);
session.saveOrUpdate(counter);
session.flush();
return counterHolder;
}
catch (Exception e) {
throw processException(e);
}
finally {
closeSession(session);
}
}
private static final int _DEFAULT_CURRENT_ID = 0;
private static final int _MINIMUM_INCREMENT_SIZE = 1;
private static final String _NAME = Counter.class.getName();
private static final String _SQL_INSERT =
"insert into Counter(name, currentId) values (?, ?)";
private static final String _SQL_SELECT_ID_BY_NAME =
"select currentId from Counter where name = ?";
private static final String _SQL_SELECT_NAMES =
"select name from Counter order by name asc";
private static final String _SQL_UPDATE_NAME_BY_NAME =
"update Counter set name = ? where name = ?";
private final Map<String, CounterRegister> _counterRegisterMap =
new ConcurrentHashMap<>();
private final Map<String, Integer> _rangeSizeMap =
new ConcurrentHashMap<>();
}