/**
* 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.portal.configuration.persistence;
import com.liferay.portal.configuration.persistence.listener.ConfigurationModelListener;
import com.liferay.portal.configuration.persistence.listener.ConfigurationModelListenerProvider;
import com.liferay.portal.kernel.dao.db.DB;
import com.liferay.portal.kernel.dao.db.DBManagerUtil;
import com.liferay.portal.kernel.io.ReaderInputStream;
import com.liferay.portal.kernel.io.unsync.UnsyncByteArrayOutputStream;
import com.liferay.portal.kernel.util.HashMapDictionary;
import com.liferay.portal.kernel.util.ReflectionUtil;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringReader;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Collections;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.sql.DataSource;
import org.apache.felix.cm.NotCachablePersistenceManager;
import org.apache.felix.cm.PersistenceManager;
import org.apache.felix.cm.file.ConfigurationHandler;
import org.osgi.framework.Constants;
import org.osgi.service.cm.ConfigurationAdmin;
/**
* @author Raymond Augé
* @author Sampsa Sohlman
*/
public class ConfigurationPersistenceManager
implements NotCachablePersistenceManager, PersistenceManager,
ReloadablePersistenceManager {
@Override
public void delete(final String pid) throws IOException {
if (System.getSecurityManager() != null) {
try {
AccessController.doPrivileged(
new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws Exception {
doDelete(pid);
return null;
}
});
}
catch (PrivilegedActionException pae) {
throw (IOException)pae.getException();
}
}
else {
doDelete(pid);
}
}
@Override
public boolean exists(String pid) {
Lock lock = _readWriteLock.readLock();
try {
lock.lock();
return _dictionaries.containsKey(pid);
}
finally {
lock.unlock();
}
}
@Override
public Enumeration<?> getDictionaries() {
Lock lock = _readWriteLock.readLock();
try {
lock.lock();
return Collections.enumeration(_dictionaries.values());
}
finally {
lock.unlock();
}
}
@Override
public Dictionary<?, ?> load(String pid) {
Lock lock = _readWriteLock.readLock();
try {
lock.lock();
return _dictionaries.get(pid);
}
finally {
lock.unlock();
}
}
@Override
public void reload(String pid) throws IOException {
Lock lock = _readWriteLock.writeLock();
try {
lock.lock();
_dictionaries.remove(pid);
if (hasPid(pid)) {
Dictionary<?, ?> dictionary = getDictionary(pid);
_dictionaries.put(pid, dictionary);
}
}
finally {
lock.unlock();
}
}
public void setDataSource(DataSource dataSource) {
_dataSource = dataSource;
}
public void start() {
Lock readLock = _readWriteLock.readLock();
Lock writeLock = _readWriteLock.writeLock();
try {
readLock.lock();
if (!hasConfigurationTable()) {
readLock.unlock();
writeLock.lock();
try {
createConfigurationTable();
}
finally {
readLock.lock();
writeLock.unlock();
}
}
populateDictionaries();
}
finally {
readLock.unlock();
}
}
public void stop() {
_dictionaries.clear();
}
@Override
public void store(
final String pid,
@SuppressWarnings("rawtypes") final Dictionary dictionary)
throws IOException {
if (System.getSecurityManager() != null) {
try {
AccessController.doPrivileged(
new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws Exception {
doStore(pid, dictionary);
return null;
}
});
}
catch (PrivilegedActionException pae) {
throw (IOException)pae.getException();
}
}
else {
doStore(pid, dictionary);
}
}
protected String buildSQL(String sql) throws IOException {
DB db = DBManagerUtil.getDB();
return db.buildSQL(sql);
}
protected void cleanUp(
Connection connection, Statement statement, ResultSet resultSet) {
try {
if (resultSet != null) {
resultSet.close();
}
}
catch (SQLException sqle) {
ReflectionUtil.throwException(sqle);
}
finally {
try {
if (statement != null) {
statement.close();
}
}
catch (SQLException sqle) {
ReflectionUtil.throwException(sqle);
}
finally {
try {
if (connection != null) {
connection.close();
}
}
catch (SQLException sqle) {
ReflectionUtil.throwException(sqle);
}
}
}
}
protected void createConfigurationTable() {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
connection = _dataSource.getConnection();
statement = connection.createStatement();
statement.executeUpdate(
buildSQL(
"create table Configuration_ (configurationId " +
"VARCHAR(255) not null primary key, dictionary TEXT)"));
}
catch (IOException | SQLException e) {
ReflectionUtil.throwException(e);
}
finally {
cleanUp(connection, statement, resultSet);
}
}
protected void deleteFromDatabase(String pid) throws IOException {
Connection connection = null;
PreparedStatement preparedStatement = null;
try {
connection = _dataSource.getConnection();
preparedStatement = prepareStatement(
connection,
"delete from Configuration_ where configurationId = ?");
preparedStatement.setString(1, pid);
preparedStatement.executeUpdate();
}
catch (SQLException sqle) {
throw new IOException(sqle);
}
finally {
cleanUp(connection, preparedStatement, null);
}
}
protected void doDelete(String pid) throws IOException {
ConfigurationModelListener configurationModelListener = null;
if (!pid.endsWith("factory") && hasPid(pid)) {
Dictionary dictionary = getDictionary(pid);
String pidKey = (String)dictionary.get(
ConfigurationAdmin.SERVICE_FACTORYPID);
if (pidKey == null) {
pidKey = (String)dictionary.get(Constants.SERVICE_PID);
}
if (pidKey == null) {
pidKey = pid;
}
configurationModelListener =
ConfigurationModelListenerProvider.
getConfigurationModelListener(pidKey);
}
if (configurationModelListener != null) {
configurationModelListener.onBeforeDelete(pid);
}
Lock lock = _readWriteLock.writeLock();
try {
lock.lock();
Dictionary<?, ?> dictionary = _dictionaries.remove(pid);
if ((dictionary != null) && hasPid(pid)) {
deleteFromDatabase(pid);
}
}
finally {
lock.unlock();
}
if (configurationModelListener != null) {
configurationModelListener.onAfterDelete(pid);
}
}
protected void doStore(
String pid, @SuppressWarnings("rawtypes") Dictionary dictionary)
throws IOException {
ConfigurationModelListener configurationModelListener = null;
if (!pid.endsWith("factory") &&
(dictionary.get("_felix_.cm.newConfiguration") == null)) {
String pidKey = (String)dictionary.get(
ConfigurationAdmin.SERVICE_FACTORYPID);
if (pidKey == null) {
pidKey = pid;
}
configurationModelListener =
ConfigurationModelListenerProvider.
getConfigurationModelListener(pidKey);
}
if (configurationModelListener != null) {
configurationModelListener.onBeforeSave(pid, dictionary);
}
Lock lock = _readWriteLock.writeLock();
try {
lock.lock();
storeInDatabase(pid, dictionary);
_dictionaries.put(pid, _copyDictionary(dictionary));
}
finally {
lock.unlock();
}
if (configurationModelListener != null) {
configurationModelListener.onAfterSave(pid, dictionary);
}
}
protected Dictionary<?, ?> getDictionary(String pid) throws IOException {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = _dataSource.getConnection();
preparedStatement = prepareStatement(
connection,
"select dictionary from Configuration_ where configurationId " +
"= ?");
preparedStatement.setString(1, pid);
resultSet = preparedStatement.executeQuery();
if (resultSet.next()) {
return toDictionary(resultSet.getString(1));
}
return _emptyDictionary;
}
catch (SQLException sqle) {
return ReflectionUtil.throwException(sqle);
}
finally {
cleanUp(connection, preparedStatement, resultSet);
}
}
protected boolean hasConfigurationTable() {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = _dataSource.getConnection();
preparedStatement = prepareStatement(
connection, "select count(*) from Configuration_");
resultSet = preparedStatement.executeQuery();
int count = 0;
if (resultSet.next()) {
count = resultSet.getInt(1);
}
if (count >= 0) {
return true;
}
return false;
}
catch (IOException | SQLException e) {
return false;
}
finally {
cleanUp(connection, preparedStatement, resultSet);
}
}
protected boolean hasPid(String pid) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = _dataSource.getConnection();
preparedStatement = prepareStatement(
connection,
"select count(*) from Configuration_ where configurationId = " +
"?");
preparedStatement.setString(1, pid);
resultSet = preparedStatement.executeQuery();
int count = 0;
if (resultSet.next()) {
count = resultSet.getInt(1);
}
if (count > 0) {
return true;
}
return false;
}
catch (IOException | SQLException e) {
return ReflectionUtil.throwException(e);
}
finally {
cleanUp(connection, preparedStatement, resultSet);
}
}
protected void populateDictionaries() {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = _dataSource.getConnection();
preparedStatement = connection.prepareStatement(
buildSQL(
"select configurationId, dictionary from Configuration_ " +
"ORDER BY configurationId ASC"),
ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
String pid = resultSet.getString(1);
String dictionaryString = resultSet.getString(2);
_dictionaries.putIfAbsent(pid, toDictionary(dictionaryString));
}
}
catch (IOException | SQLException e) {
ReflectionUtil.throwException(e);
}
finally {
cleanUp(connection, preparedStatement, resultSet);
}
}
protected PreparedStatement prepareStatement(
Connection connection, String sql)
throws IOException, SQLException {
return connection.prepareStatement(buildSQL(sql));
}
protected void store(ResultSet resultSet, Dictionary<?, ?> dictionary)
throws IOException, SQLException {
OutputStream outputStream = new UnsyncByteArrayOutputStream();
ConfigurationHandler.write(outputStream, dictionary);
resultSet.updateString(2, outputStream.toString());
}
protected void storeInDatabase(String pid, Dictionary<?, ?> dictionary)
throws IOException {
UnsyncByteArrayOutputStream outputStream =
new UnsyncByteArrayOutputStream();
ConfigurationHandler.write(outputStream, dictionary);
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = _dataSource.getConnection();
connection.setAutoCommit(false);
preparedStatement = connection.prepareStatement(
buildSQL(
"update Configuration_ set dictionary = ? where " +
"configurationId = ?"));
preparedStatement.setString(1, outputStream.toString());
preparedStatement.setString(2, pid);
if (preparedStatement.executeUpdate() == 0) {
preparedStatement = prepareStatement(
connection,
"insert into Configuration_ (configurationId, " +
"dictionary) values (?, ?)");
preparedStatement.setString(1, pid);
preparedStatement.setString(2, outputStream.toString());
preparedStatement.executeUpdate();
}
connection.commit();
}
catch (SQLException sqle) {
ReflectionUtil.throwException(sqle);
}
finally {
cleanUp(connection, preparedStatement, resultSet);
outputStream.close();
}
}
protected Dictionary<?, ?> toDictionary(String dictionaryString)
throws IOException {
InputStream inputStream = new ReaderInputStream(
new StringReader(dictionaryString));
return ConfigurationHandler.read(inputStream);
}
private Dictionary<?, ?> _copyDictionary(Dictionary<?, ?> dictionary) {
Dictionary newDictionary = new HashMapDictionary<>();
Enumeration<?> keys = dictionary.keys();
while (keys.hasMoreElements()) {
Object key = keys.nextElement();
newDictionary.put(key, dictionary.get(key));
}
return newDictionary;
}
private static final Dictionary<?, ?> _emptyDictionary = new Hashtable<>();
private DataSource _dataSource;
private final ConcurrentMap<String, Dictionary<?, ?>> _dictionaries =
new ConcurrentHashMap<>();
private final ReadWriteLock _readWriteLock = new ReentrantReadWriteLock(
true);
}