/*******************************************************************************
* Copyright (c) 2009 Daniel Grout.
*
* GNU GENERAL PUBLIC LICENSE - Version 3
*
* This file is part of Report Runner (http://code.google.com/p/reportrunner).
*
* Report Runner 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.
*
* Report Runner 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 Report Runner. If not, see <http://www.gnu.org/licenses/>.
*
* Module: DatasourceServiceImpl.java
******************************************************************************/
package binky.reportrunner.service.impl;
import java.beans.PropertyVetoException;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import org.apache.commons.dbcp.BasicDataSource;
import org.apache.commons.digester.Digester;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.xml.sax.SAXException;
import binky.dan.utils.encryption.EncryptionException;
import binky.reportrunner.dao.ReportRunnerDao;
import binky.reportrunner.data.RunnerDataSource;
import binky.reportrunner.data.RunnerGroup;
import binky.reportrunner.service.DatasourceService;
import binky.reportrunner.service.misc.JDBCDriverDefinition;
import binky.reportrunner.service.misc.JDBCDrivers;
import binky.reportrunner.util.EncryptionUtil;
import com.googlecode.ehcache.annotations.Cacheable;
import com.googlecode.ehcache.annotations.TriggersRemove;
public class DatasourceServiceImpl implements DatasourceService {
private ReportRunnerDao<RunnerDataSource, String> dataSourceDao;
private ReportRunnerDao<RunnerGroup, String> groupDao;
private Map<String, DataSource> dataSources = new HashMap<String, DataSource>();;
private String secureKey;
private Logger logger = Logger.getLogger(DatasourceServiceImpl.class);
private DataSource getDs(RunnerDataSource runnerDs)
throws SecurityException, InstantiationException,
IllegalAccessException, ClassNotFoundException,
PropertyVetoException, NamingException, EncryptionException {
final String jndiDataSource = runnerDs.getJndiName();
if (StringUtils.isBlank(jndiDataSource)) {
EncryptionUtil enc = new EncryptionUtil();
logger.info("using dbcp pooled connection for: "
+ runnerDs.getDataSourceName());
String jdbcUser = runnerDs.getUsername();
if (StringUtils.isBlank(runnerDs.getPassword()))
throw new SecurityException("password is empty");
String jdbcPassword = enc
.decrpyt(secureKey, runnerDs.getPassword());
String jdbcUrl = runnerDs.getJdbcUrl();
String databaseDriver = runnerDs.getJdbcClass();
Class.forName(databaseDriver).newInstance();
BasicDataSource ds1 = new BasicDataSource();
ds1.setDriverClassName(databaseDriver);
ds1.setUrl(jdbcUrl);
ds1.setUsername(jdbcUser);
ds1.setPassword(jdbcPassword);
ds1.setInitialSize(runnerDs.getInitialPoolSize());
ds1.setMaxActive(runnerDs.getMaxPoolSize());
ds1.setRemoveAbandoned(true);
ds1.setRemoveAbandonedTimeout(600);
// do not want anything updating anything
ds1.setDefaultReadOnly(true);
ds1.setLogAbandoned(true);
ds1.setTestOnBorrow(true);
ds1.setTestOnReturn(true);
ds1.setTestWhileIdle(true);
// does this work across all RBMS? - no it doesn't
//ds1.setValidationQuery("select 1");
//ds1.setValidationQueryTimeout(300);
return ds1;
} else {
logger.info("getting datasource from JNDI url: " + jndiDataSource
+ " for " + runnerDs.getDataSourceName());
Context initContext = new InitialContext();
DataSource ds = (DataSource) initContext.lookup("java:/comp/env/"
+ jndiDataSource);
return ds;
}
}
public void purgeConnections(String dataSourceName) throws SQLException {
DataSource ds = dataSources.get(dataSourceName);
if (ds != null && ds instanceof BasicDataSource) {
dumpLogInfo(dataSourceName);
// reset the datasource
((BasicDataSource) ds).close();
}
}
private void dumpLogInfo(String dsName) throws SQLException {
// dump out a shed load of info about the datasource
logger.debug("Datasource info for " + dsName);
}
@Override
public String testDataSource(RunnerDataSource runnerDs) {
try {
//fix for issue 105 - when not editing password but just testing
if (runnerDs.getPassword()==null||runnerDs.getPassword().trim().isEmpty()) {
// see if ds already exists but we are hiding the password
RunnerDataSource pwget = this.dataSourceDao.get(runnerDs
.getDataSourceName());
if (pwget != null) {
logger.debug("supplied password was blank - using stored password (if any)");
runnerDs.setPassword(pwget.getPassword());
}
} else {
EncryptionUtil enc = new EncryptionUtil();
runnerDs.setPassword(enc.encrpyt(this.secureKey,
runnerDs.getPassword()));
}
DataSource ds = this.getDs(runnerDs);
Connection conn = ds.getConnection();
DatabaseMetaData meta = conn.getMetaData();
String information = meta.getDatabaseProductName() + ", "
+ meta.getDatabaseProductVersion();
conn.close();
if (ds instanceof BasicDataSource) {
((BasicDataSource) ds).close();
}
return information;
} catch (Exception e) {
if (e instanceof NullPointerException) {
logger.fatal(e.getMessage(), e);
}
logger.debug(e.getMessage());
return "ERROR - " + e.getClass().getSimpleName() + ": "
+ e.getMessage();
}
}
public DataSource getJDBCDataSource(RunnerDataSource runnerDs)
throws SQLException {
if (dataSources == null)
dataSources = new HashMap<String, DataSource>();
DataSource ds = dataSources.get(runnerDs.getDataSourceName());
if (ds == null) {
try {
logger.info("datasource not already loaded for: "
+ runnerDs.getDataSourceName());
ds = getDs(runnerDs);
logger.info("Stored datasource: "
+ runnerDs.getDataSourceName());
dataSources.put(runnerDs.getDataSourceName(), ds);
dumpLogInfo(runnerDs.getDataSourceName());
} catch (Exception e) {
logger.fatal(
"Unable to create datasource: "
+ runnerDs.getDataSourceName(), e);
}
}
return ds;
}
public void setDataSourceDao(
ReportRunnerDao<RunnerDataSource, String> dataSourceDao) {
this.dataSourceDao = dataSourceDao;
}
@TriggersRemove(cacheName = "dataSourceCache")
public void deleteDataSource(String dataSourceName) {
if (dataSources.get(dataSourceName) != null) {
dataSources.remove(dataSourceName);
}
dataSourceDao.delete(dataSourceName);
}
public RunnerDataSource getDataSource(String dataSourceName) {
RunnerDataSource ds = dataSourceDao.get(dataSourceName);
return ds;
}
@Cacheable(cacheName = "dataSourceCache")
public List<RunnerDataSource> listDataSources() {
return dataSourceDao.getAll();
}
@TriggersRemove(cacheName="dataSourceCache", removeAll=true)
public void saveUpdateDataSource(RunnerDataSource dataSource)
throws EncryptionException {
if (dataSources.get(dataSource.getDataSourceName()) != null) {
dataSources.remove(dataSource.getDataSourceName());
}
EncryptionUtil enc = new EncryptionUtil();
if (StringUtils.isNotBlank(dataSource.getPassword())) {
dataSource.setPassword(enc.encrpyt(secureKey,
dataSource.getPassword()));
} else {
RunnerDataSource ds = dataSourceDao.get(dataSource
.getDataSourceName());
if (ds != null)
dataSource.setPassword(ds.getPassword());
}
dataSourceDao.saveOrUpdate(dataSource);
}
public void setSecureKey(String secureKey) {
this.secureKey = secureKey;
}
public JDBCDrivers getJDBCDriverDefinitions() throws IOException,
SAXException {
InputStream in = DatasourceServiceImpl.class
.getResourceAsStream("/jdbcDrivers.xml");
Digester digester = new Digester();
digester.setValidating(false);
digester.addObjectCreate("jdbcDrivers", JDBCDrivers.class);
digester.addObjectCreate("jdbcDrivers/driver",
JDBCDriverDefinition.class);
digester.addBeanPropertySetter("jdbcDrivers/driver/label", "label");
digester.addBeanPropertySetter("jdbcDrivers/driver/url", "url");
digester.addBeanPropertySetter("jdbcDrivers/driver/driverName",
"driverName");
digester.addSetNext("jdbcDrivers/driver", "addDefinition");
JDBCDrivers drivers = (JDBCDrivers) digester.parse(in);
return drivers;
}
@Override
public List<RunnerDataSource> getDataSourcesForGroup(String groupName) {
logger.debug("getting datasources for group: " + groupName);
// List<RunnerDataSource> dsList =
// groupDao.get(groupName).getDataSources();
// some crazy ass shit going down here - the join returns an array for
// each row that would be returned by the db
// if i try prefixing the names query with select d (which makes sense
// to me) then it won't run
List<RunnerDataSource> dsList = new LinkedList<RunnerDataSource>();
Object holder = dataSourceDao.findByNamedQuery("findAllForGroup",
new String[] { groupName });
for (Object[] o : (ArrayList<Object[]>) holder) {
RunnerDataSource d = (RunnerDataSource) o[0];
logger.debug(d.getDataSourceName());
dsList.add(d);
}
logger.debug("got : " + dsList.size() + " data sources for group: "
+ groupName);
return dsList;
}
public ReportRunnerDao<RunnerGroup, String> getGroupDao() {
return groupDao;
}
public void setGroupDao(ReportRunnerDao<RunnerGroup, String> groupDao) {
this.groupDao = groupDao;
}
@Override
public void reEncryptPasswords(String newKey) throws EncryptionException {
EncryptionUtil enc = new EncryptionUtil();
logger.warn("re-encrypting the datasource passwords. I hope you copied the key from the UI as instructed!!");
for (RunnerDataSource ds : dataSourceDao.getAll()) {
if (!StringUtils.isEmpty(ds.getPassword())) {
String pw = enc.decrpyt(secureKey, ds.getPassword());
ds.setPassword(enc.encrpyt(newKey, pw));
dataSourceDao.saveOrUpdate(ds);
logger.debug("updated ds: " + ds.getDataSourceName());
}
}
logger.warn("re-encryption complete. Please ensure you update the properties file with the new key and restart the server");
}
}