/** * AnalyzerBeans * Copyright (C) 2014 Neopost - Customer Information Management * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU * Lesser General Public License, as published by the Free Software Foundation. * * This program 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. * * You should have received a copy of the GNU Lesser General Public License * along with this distribution; if not, write to: * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ package org.eobjects.analyzer.connection; import java.io.IOException; import java.io.ObjectInputStream; import java.sql.Connection; import java.sql.Driver; import java.sql.DriverManager; import java.sql.SQLException; import java.util.Arrays; import java.util.Enumeration; import java.util.List; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.sql.DataSource; import org.apache.commons.dbcp.BasicDataSource; import org.eobjects.analyzer.util.ReadObjectBuilder; import org.eobjects.analyzer.util.StringUtils; import org.apache.metamodel.UpdateableDataContext; import org.apache.metamodel.jdbc.JdbcDataContext; import org.apache.metamodel.schema.TableType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Datastore implementation for JDBC based connections. Connections can either * be based on JDBC urls or JNDI urls. */ public class JdbcDatastore extends UsageAwareDatastore<UpdateableDataContext> implements UpdateableDatastore, UsernameDatastore { private static final long serialVersionUID = 1L; public static final String SYSTEM_PROPERTY_CONNECTION_POOL_MAX_SIZE = "datastore.jdbc.connection.pool.max.size"; public static final String SYSTEM_PROPERTY_CONNECTION_POOL_MIN_EVICTABLE_IDLE_TIME_MILLIS = "datastore.jdbc.connection.pool.idle.timeout"; public static final String SYSTEM_PROPERTY_CONNECTION_POOL_TIME_BETWEEN_EVICTION_RUNS_MILLIS = "datastore.jdbc.connection.pool.eviction.period.millis"; private static final Logger logger = LoggerFactory.getLogger(JdbcDatastore.class); private final String _jdbcUrl; private final String _username; private final String _password; private final String _driverClass; private final boolean _multipleConnections; private final String _datasourceJndiUrl; private final TableType[] _tableTypes; private final String _catalogName; private JdbcDatastore(String name, String jdbcUrl, String driverClass, String username, String password, String datasourceJndiUrl, boolean multipleConnections, TableType[] tableTypes, String catalogName) { super(name); _jdbcUrl = jdbcUrl; _driverClass = driverClass; _username = username; _password = password; _datasourceJndiUrl = datasourceJndiUrl; _multipleConnections = multipleConnections; _tableTypes = tableTypes; _catalogName = catalogName; } public JdbcDatastore(String name, String jdbcUrl, String driverClass) { this(name, jdbcUrl, driverClass, null, null, true); } public JdbcDatastore(String name, String jdbcUrl, String driverClass, String username, String password, boolean multipleConnections, TableType[] tableTypes, String catalogName) { this(name, jdbcUrl, driverClass, username, password, null, multipleConnections, tableTypes, catalogName); } public JdbcDatastore(String name, String jdbcUrl, String driverClass, String username, String password, boolean multipleConnections) { this(name, jdbcUrl, driverClass, username, password, multipleConnections, null, null); } public JdbcDatastore(String name, String datasourceJndiUrl) { this(name, datasourceJndiUrl, (TableType[]) null, null); } public JdbcDatastore(String name, String datasourceJndiUrl, TableType[] tableTypes, String catalogName) { this(name, null, null, null, null, datasourceJndiUrl, false, tableTypes, catalogName); } private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { ReadObjectBuilder.create(this, JdbcDatastore.class).readObject(stream); } @Override public UpdateableDatastoreConnection openConnection() { DatastoreConnection connection = super.openConnection(); return (UpdateableDatastoreConnection) connection; } /** * Alternative constructor usable only for in-memory (ie. non-persistent) * datastores, because the datastore will not be able to create new * connections. * * @param name * @param dc */ public JdbcDatastore(String name, UpdateableDataContext dc) { this(name, null, null, null, null, null, false, null, null); setDataContextProvider(new UpdateableDatastoreConnectionImpl<UpdateableDataContext>(dc, this)); } @Override protected void decorateIdentity(List<Object> identifiers) { super.decorateIdentity(identifiers); identifiers.add(_driverClass); identifiers.add(_jdbcUrl); identifiers.add(_datasourceJndiUrl); identifiers.add(_username); identifiers.add(_password); identifiers.add(_multipleConnections); identifiers.add(getTableTypes()); } public boolean isMultipleConnections() { return _multipleConnections; } public TableType[] getTableTypes() { final TableType[] tableTypes; if (_tableTypes == null) { tableTypes = TableType.DEFAULT_TABLE_TYPES; } else { tableTypes = _tableTypes; } return Arrays.copyOf(tableTypes, tableTypes.length); } public String getCatalogName() { return _catalogName; } public String getJdbcUrl() { return _jdbcUrl; } @Override public String getUsername() { return _username; } public String getPassword() { return _password; } public String getDriverClass() { return _driverClass; } public String getDatasourceJndiUrl() { return _datasourceJndiUrl; } public Connection createConnection() throws IllegalStateException { initializeDriver(); try { if (_username != null && _password != null) { return DriverManager.getConnection(_jdbcUrl, _username, _password); } else { return DriverManager.getConnection(_jdbcUrl); } } catch (SQLException e) { throw new IllegalStateException("Could not create connection", e); } } public DataSource createDataSource() { initializeDriver(); BasicDataSource ds = new BasicDataSource(); ds.setDefaultAutoCommit(false); ds.setUrl(_jdbcUrl); ds.setMaxActive(getSystemPropertyValue(SYSTEM_PROPERTY_CONNECTION_POOL_MAX_SIZE, -1)); ds.setMinEvictableIdleTimeMillis(getSystemPropertyValue( SYSTEM_PROPERTY_CONNECTION_POOL_MIN_EVICTABLE_IDLE_TIME_MILLIS, 500)); ds.setTimeBetweenEvictionRunsMillis(getSystemPropertyValue( SYSTEM_PROPERTY_CONNECTION_POOL_TIME_BETWEEN_EVICTION_RUNS_MILLIS, 1000)); if (_username != null && _password != null) { ds.setUsername(_username); ds.setPassword(_password); } return ds; } private int getSystemPropertyValue(String property, int defaultValue) { String str = System.getProperty(property); if (str == null) { return defaultValue; } try { return Integer.parseInt(str); } catch (NumberFormatException e) { logger.debug("Failed to parse system property '{}': '{}'", property, str); return defaultValue; } } private void initializeDriver() { if (_jdbcUrl == null) { throw new IllegalStateException("JDBC URL is null, cannot create connection!"); } logger.debug("Determining if driver initialization is nescesary"); // it's best to avoid initializing the driver, so we do this check. // It may already have been initialized and Class.forName(...) does // not always work if the driver is in a different classloader boolean installDriver = true; Enumeration<Driver> drivers = DriverManager.getDrivers(); while (drivers.hasMoreElements()) { Driver driver = drivers.nextElement(); try { if (driver.acceptsURL(_jdbcUrl)) { installDriver = false; break; } } catch (Exception e) { logger.warn("Driver threw exception when acceptURL(...) was invoked", e); } } if (installDriver) { try { Class.forName(_driverClass); } catch (ClassNotFoundException e) { throw new IllegalStateException("Could not initialize JDBC driver", e); } } } @Override protected UsageAwareDatastoreConnection<UpdateableDataContext> createDatastoreConnection() { if (StringUtils.isNullOrEmpty(_datasourceJndiUrl)) { if (isMultipleConnections()) { final DataSource dataSource = createDataSource(); return new DataSourceDatastoreConnection(dataSource, getTableTypes(), _catalogName, this); } else { final Connection connection = createConnection(); final UpdateableDataContext dataContext = new JdbcDataContext(connection, getTableTypes(), _catalogName); return new UpdateableDatastoreConnectionImpl<UpdateableDataContext>(dataContext, this); } } else { try { Context initialContext = getJndiNamingContext(); DataSource dataSource = (DataSource) initialContext.lookup(_datasourceJndiUrl); return new DataSourceDatastoreConnection(dataSource, getTableTypes(), _catalogName, this); } catch (Exception e) { logger.error("Could not retrieve DataSource '{}'", _datasourceJndiUrl); throw new IllegalStateException(e); } } } protected Context getJndiNamingContext() throws NamingException { return new InitialContext(); } @Override public PerformanceCharacteristics getPerformanceCharacteristics() { return new PerformanceCharacteristicsImpl(true, false); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("JdbcDatastore[name="); sb.append(getName()); if (_jdbcUrl != null) { sb.append(",url="); sb.append(_jdbcUrl); } else { sb.append(",jndi="); sb.append(_datasourceJndiUrl); } sb.append("]"); return sb.toString(); } }