/*******************************************************************************
* This file is part of OpenNMS(R).
*
* Copyright (C) 2006-2011 The OpenNMS Group, Inc.
* OpenNMS(R) is Copyright (C) 1999-2011 The OpenNMS Group, Inc.
*
* OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc.
*
* OpenNMS(R) is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* OpenNMS(R) 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenNMS(R). If not, see:
* http://www.gnu.org/licenses/
*
* For more information contact:
* OpenNMS(R) Licensing <license@opennms.org>
* http://www.opennms.org/
* http://www.opennms.com/
*******************************************************************************/
package org.opennms.netmgt.config;
import java.beans.PropertyVetoException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.sql.DataSource;
import org.apache.commons.io.IOUtils;
import org.exolab.castor.xml.MarshalException;
import org.exolab.castor.xml.ValidationException;
import org.opennms.core.utils.ConfigFileConstants;
import org.opennms.core.utils.LogUtils;
import org.opennms.core.xml.CastorUtils;
import org.opennms.netmgt.config.opennmsDataSources.ConnectionPool;
import org.opennms.netmgt.config.opennmsDataSources.DataSourceConfiguration;
import org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy;
/**
* <p>
* This is the singleton class used to load the OpenNMS database configuration
* from the opennms-database.xml. This provides convenience methods to create
* database connections to the database configured in this default xml
* </p>
*
* <p>
* <strong>Note: </strong>Users of this class should make sure the
* <em>init()</em> is called before calling any other method to ensure the
* config is loaded before accessing other convenience methods
* </p>
*
* @author <a href="mailto:weave@oculan.com">Brian Weaver </a>
*/
public final class DataSourceFactory implements DataSource {
private static final Class<?> DEFAULT_FACTORY_CLASS = C3P0ConnectionFactory.class;
/**
* The singleton instance of this factory
*/
private static DataSource m_singleton = null;
private static final Map<String, DataSource> m_dataSources = new ConcurrentHashMap<String, DataSource>();
private static final List<Runnable> m_closers = new LinkedList<Runnable>();
/**
* Load the config from the default config file and create the singleton
* instance of this factory.
*
* @exception java.io.IOException
* Thrown if the specified config file cannot be read
* @exception org.exolab.castor.xml.MarshalException
* Thrown if the file does not conform to the schema.
* @exception org.exolab.castor.xml.ValidationException
* Thrown if the contents do not match the required schema.
* @throws java.sql.SQLException if any.
* @throws java.beans.PropertyVetoException if any.
* @throws java.io.IOException if any.
* @throws org.exolab.castor.xml.MarshalException if any.
* @throws org.exolab.castor.xml.ValidationException if any.
* @throws java.lang.ClassNotFoundException if any.
*/
public static synchronized void init() throws IOException, MarshalException, ValidationException, ClassNotFoundException, PropertyVetoException, SQLException {
if (!isLoaded("opennms")) {
init("opennms");
}
}
/**
* <p>init</p>
*
* @param dsName a {@link java.lang.String} object.
* @throws java.io.IOException if any.
* @throws org.exolab.castor.xml.MarshalException if any.
* @throws org.exolab.castor.xml.ValidationException if any.
* @throws java.lang.ClassNotFoundException if any.
* @throws java.beans.PropertyVetoException if any.
* @throws java.sql.SQLException if any.
*/
public static synchronized void init(final String dsName) throws IOException, MarshalException, ValidationException, ClassNotFoundException, PropertyVetoException, SQLException {
if (isLoaded(dsName)) {
// init already called - return
// to reload, reload() will need to be called
return;
}
String factoryClass = null;
final File cfgFile = ConfigFileConstants.getFile(ConfigFileConstants.OPENNMS_DATASOURCE_CONFIG_FILE_NAME);
DataSourceConfiguration dsc = null;
ConnectionPool connectionPool = null;
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream(cfgFile);
dsc = CastorUtils.unmarshal(DataSourceConfiguration.class, fileInputStream);
connectionPool = dsc.getConnectionPool();
if (connectionPool != null) {
factoryClass = connectionPool.getFactory();
}
} finally {
IOUtils.closeQuietly(fileInputStream);
}
final String configPath = cfgFile.getPath();
ClosableDataSource dataSource = null;
final String defaultClassName = DEFAULT_FACTORY_CLASS.getName();
try {
final Class<?> clazz = Class.forName(factoryClass);
final Constructor<?> constructor = clazz.getConstructor(new Class<?>[] { String.class, String.class });
dataSource = (ClosableDataSource)constructor.newInstance(new Object[] { configPath, dsName });
} catch (final Throwable t) {
LogUtils.debugf(DataSourceFactory.class, t, "Unable to load %s, falling back to the default dataSource (%s)", factoryClass, defaultClassName);
try {
final Constructor<?> constructor = ((Class<?>) DEFAULT_FACTORY_CLASS).getConstructor(new Class<?>[] { String.class, String.class });
dataSource = (ClosableDataSource)constructor.newInstance(new Object[] { configPath, dsName });
} catch (final Throwable cause) {
LogUtils.errorf(DataSourceFactory.class, cause, "Unable to load %s.", DEFAULT_FACTORY_CLASS.getName());
throw new SQLException("Unable to load " + defaultClassName + ".", cause);
}
}
final ClosableDataSource runnableDs = dataSource;
m_closers.add(new Runnable() {
public void run() {
try {
runnableDs.close();
} catch (final Throwable cause) {
LogUtils.infof(DataSourceFactory.class, cause, "Unable to close datasource %s.", dsName);
}
}
});
if (connectionPool != null) {
dataSource.setIdleTimeout(connectionPool.getIdleTimeout());
dataSource.setLoginTimeout(connectionPool.getLoginTimeout());
dataSource.setMinPool(connectionPool.getMinPool());
dataSource.setMaxPool(connectionPool.getMaxPool());
dataSource.setMaxSize(connectionPool.getMaxSize());
}
// Springframework provided proxies that make working with transactions much easier
final LazyConnectionDataSourceProxy lazyProxy = new LazyConnectionDataSourceProxy(dataSource);
setInstance(dsName, lazyProxy);
}
private static synchronized boolean isLoaded(final String dsName) {
return m_dataSources.containsKey(dsName);
}
/**
* <p>
* Return the singleton instance of this factory. This is the instance of
* the factory that was last created when the <code>
* init</code> or
* <code>reload</code> method was invoked. The instance will not change
* unless a <code>reload</code> method is invoked.
* </p>
*
* @return The current factory instance.
* @throws java.lang.IllegalStateException
* Thrown if the factory has not yet been initialized.
*/
public static DataSource getInstance() {
return getInstance("opennms");
}
/**
* <p>getInstance</p>
*
* @param name a {@link java.lang.String} object.
* @return a {@link javax.sql.DataSource} object.
*/
public static DataSource getInstance(final String name) {
final DataSource dataSource = getDataSource(name);
if (dataSource == null) {
throw new IllegalArgumentException("Unable to locate data source named " + name + ". Does this need to be init'd?");
} else {
return dataSource;
}
}
/**
* Return a new database connection to the database configured in the
* <tt>opennms-database.xml</tt>. The database connection is not managed
* by the factory and must be release by the caller by using the
* <code>close</code> method.
*
* @return a new database connection to the database configured in the
* <tt>opennms-database.xml</tt>
* @throws java.sql.SQLException
* Thrown if there is an error opening the connection to the
* database.
*/
@Override
public Connection getConnection() throws SQLException {
return getConnection("opennms");
}
/**
* <p>getConnection</p>
*
* @param dsName a {@link java.lang.String} object.
* @return a {@link java.sql.Connection} object.
* @throws java.sql.SQLException if any.
*/
public Connection getConnection(final String dsName) throws SQLException {
return getDataSource(dsName).getConnection();
}
/**
* <p>setInstance</p>
*
* @param singleton a {@link javax.sql.DataSource} object.
*/
public static void setInstance(final DataSource singleton) {
m_singleton=singleton;
setInstance("opennms", singleton);
}
/**
* <p>setInstance</p>
*
* @param dsName a {@link java.lang.String} object.
* @param singleton a {@link javax.sql.DataSource} object.
*/
public static synchronized void setInstance(final String dsName, final DataSource singleton) {
m_dataSources.put(dsName,singleton);
}
/**
* Return the datasource configured for the database
*
* @return the datasource configured for the database
*/
public static DataSource getDataSource() {
return getDataSource("opennms");
}
/**
* <p>getDataSource</p>
*
* @param dsName a {@link java.lang.String} object.
* @return a {@link javax.sql.DataSource} object.
*/
public static synchronized DataSource getDataSource(final String dsName) {
return m_dataSources.get(dsName);
}
/** {@inheritDoc} */
@Override
public Connection getConnection(final String username, final String password) throws SQLException {
return getConnection();
}
/**
* <p>getLogWriter</p>
*
* @return a {@link java.io.PrintWriter} object.
* @throws java.sql.SQLException if any.
*/
@Override
public PrintWriter getLogWriter() throws SQLException {
return m_singleton.getLogWriter();
}
/**
* <p>getLogWriter</p>
*
* @param dsName a {@link java.lang.String} object.
* @return a {@link java.io.PrintWriter} object.
* @throws java.sql.SQLException if any.
*/
public PrintWriter getLogWriter(final String dsName) throws SQLException {
return getDataSource(dsName).getLogWriter();
}
/** {@inheritDoc} */
@Override
public void setLogWriter(final PrintWriter out) throws SQLException {
setLogWriter("opennms", out);
}
/**
* <p>setLogWriter</p>
*
* @param dsName a {@link java.lang.String} object.
* @param out a {@link java.io.PrintWriter} object.
* @throws java.sql.SQLException if any.
*/
public void setLogWriter(final String dsName, final PrintWriter out) throws SQLException {
getDataSource(dsName).setLogWriter(out);
}
/** {@inheritDoc} */
@Override
public void setLoginTimeout(final int seconds) throws SQLException {
setLoginTimeout("opennms", seconds);
}
/**
* <p>setLoginTimeout</p>
*
* @param dsName a {@link java.lang.String} object.
* @param seconds a int.
* @throws java.sql.SQLException if any.
*/
public void setLoginTimeout(final String dsName, final int seconds) throws SQLException {
getDataSource(dsName).setLoginTimeout(seconds);
}
/**
* <p>getLoginTimeout</p>
*
* @return a int.
* @throws java.sql.SQLException if any.
*/
@Override
public int getLoginTimeout() throws SQLException {
return getLoginTimeout("opennms");
}
/**
* <p>getLoginTimeout</p>
*
* @param dsName a {@link java.lang.String} object.
* @return a int.
* @throws java.sql.SQLException if any.
*/
public int getLoginTimeout(final String dsName) throws SQLException {
return getDataSource(dsName).getLoginTimeout();
}
/**
* <p>close</p>
*
* @throws java.sql.SQLException if any.
*/
public static synchronized void close() throws SQLException {
for(Runnable closer : m_closers) {
closer.run();
}
m_closers.clear();
m_dataSources.clear();
}
/**
* <p>unwrap</p>
*
* @param iface a {@link java.lang.Class} object.
* @param <T> a T object.
* @return a T object.
* @throws java.sql.SQLException if any.
*/
@Override
public <T> T unwrap(final Class<T> iface) throws SQLException {
return null; //TODO
}
/**
* <p>isWrapperFor</p>
*
* @param iface a {@link java.lang.Class} object.
* @return a boolean.
* @throws java.sql.SQLException if any.
*/
@Override
public boolean isWrapperFor(final Class<?> iface) throws SQLException {
return false; //TODO
}
}