blob: d41f154d6c78ebff2a86c2a92e89d09d6d5c19d6 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.openjpa.jdbc.schema;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import javax.sql.DataSource;
import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
import org.apache.openjpa.jdbc.sql.DBDictionary;
import org.apache.openjpa.lib.conf.Configurations;
import org.apache.openjpa.lib.jdbc.ConfiguringConnectionDecorator;
import org.apache.openjpa.lib.jdbc.ConnectionDecorator;
import org.apache.openjpa.lib.jdbc.DecoratingDataSource;
import org.apache.openjpa.lib.jdbc.DelegatingDataSource;
import org.apache.openjpa.lib.jdbc.JDBCEventConnectionDecorator;
import org.apache.openjpa.lib.jdbc.JDBCListener;
import org.apache.openjpa.lib.jdbc.LoggingConnectionDecorator;
import org.apache.openjpa.lib.log.Log;
import org.apache.openjpa.lib.util.J2DoPrivHelper;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.lib.util.Options;
import org.apache.openjpa.lib.util.StringUtil;
import org.apache.openjpa.util.ImplHelper;
import org.apache.openjpa.util.OpenJPAException;
import org.apache.openjpa.util.UserException;
/**
* Factory for {@link DataSource} objects. The factory uses the supplied
* configuration to obtain a 3rd-party datasource or to create one, and
* to setup prepared statement caching.
*
* @author Abe White
*/
public class DataSourceFactory {
private static final String DBCPBASICDATASOURCENAME = "org.apache.commons.dbcp2.BasicDataSource";
private static final Localizer _loc = Localizer.forPackage(DataSourceFactory.class);
protected static Localizer _eloc = Localizer.forPackage(DelegatingDataSource.class);
/**
* Create a datasource using the given configuration.
*/
public static DataSource newDataSource(JDBCConfiguration conf,
boolean factory2) {
String driver = (factory2) ? conf.getConnection2DriverName()
: conf.getConnectionDriverName();
if (StringUtil.isEmpty(driver))
throw new UserException(_loc.get("no-driver", conf)).
setFatal(true);
ClassLoader loader = conf.getClassResolverInstance().
getClassLoader(DataSourceFactory.class, null);
String props = (factory2) ? conf.getConnection2Properties()
: conf.getConnectionProperties();
try {
Class<?> driverClass;
try {
driverClass = Class.forName(driver, true, loader);
} catch (ClassNotFoundException cnfe) {
// try with the core class loader
driverClass = Class.forName(driver);
}
if (Driver.class.isAssignableFrom(driverClass)) {
DataSource rawDs = conf.newDriverDataSourceInstance();
if (rawDs instanceof DriverDataSource) {
DriverDataSource ds = (DriverDataSource)rawDs;
ds.setClassLoader(loader);
ds.setConnectionDriverName(driver);
ds.setConnectionProperties(Configurations.
parseProperties(props));
if (!factory2) {
ds.setConnectionFactoryProperties(Configurations.
parseProperties(conf.getConnectionFactoryProperties()));
ds.setConnectionURL(conf.getConnectionURL());
ds.setConnectionUserName(conf.getConnectionUserName());
ds.setConnectionPassword(conf.getConnectionPassword());
} else {
ds.setConnectionFactoryProperties
(Configurations.parseProperties(conf.
getConnectionFactory2Properties()));
ds.setConnectionURL(conf.getConnection2URL());
ds.setConnectionUserName(conf.getConnection2UserName());
ds.setConnectionPassword(conf.getConnection2Password());
}
return ds;
} else if (DBCPBASICDATASOURCENAME.equals(rawDs.getClass().getName())) {
reflectiveCall(rawDs, "setDriverClassLoader", ClassLoader.class, loader);
reflectiveCall(rawDs, "setDriverClassName", String.class, driver);
reflectiveCall(rawDs, "setConnectionProperties", String.class, props);
reflectiveCall(rawDs, "setUrl", String.class, factory2
? conf.getConnection2URL() : conf.getConnectionURL());
reflectiveCall(rawDs, "setUsername", String.class, factory2
? conf.getConnection2UserName() : conf.getConnectionUserName());
reflectiveCall(rawDs, "setPassword", String.class, factory2
? conf.getConnection2Password() : conf.getConnectionPassword());
return rawDs;
}
}
// see if their driver name is actually a data source
if (DataSource.class.isAssignableFrom(driverClass)) {
return (DataSource) Configurations.newInstance(driver,
conf, props, AccessController.doPrivileged(
J2DoPrivHelper.getClassLoaderAction(
DataSource.class)));
}
}
catch (OpenJPAException ke) {
throw ke;
} catch (Exception e) {
throw newConnectException(conf, factory2, e);
}
// not a driver or a data source; die
throw new UserException(_loc.get("bad-driver", driver)).setFatal(true);
}
private static void reflectiveCall(Object obj, String meth, Class<?> valClass, Object value) throws Exception {
Class<?> clazz = obj.getClass();
Method method = clazz.getMethod(meth, valClass);
method.invoke(obj, value);
}
/**
* Install listeners and base decorators.
*/
public static DecoratingDataSource decorateDataSource(DataSource ds,
JDBCConfiguration conf, boolean factory2) {
Options opts = Configurations.parseProperties((factory2)
? conf.getConnectionFactory2Properties()
: conf.getConnectionFactoryProperties());
Log jdbcLog = conf.getLog(JDBCConfiguration.LOG_JDBC);
Log sqlLog = conf.getLog(JDBCConfiguration.LOG_SQL);
DecoratingDataSource dds = new DecoratingDataSource(ds);
try {
// add user-defined decorators
List<ConnectionDecorator> decorators = new ArrayList<>();
decorators.addAll(Arrays.asList(conf.
getConnectionDecoratorInstances()));
// add jdbc events decorator
JDBCEventConnectionDecorator ecd =
new JDBCEventConnectionDecorator();
Configurations.configureInstance(ecd, conf, opts);
JDBCListener[] listeners = conf.getJDBCListenerInstances();
for (int i = 0; i < listeners.length; i++)
ecd.addListener(listeners[i]);
decorators.add(ecd);
// ask the DriverDataSource to provide any additional decorators
if (ds instanceof DriverDataSource) {
List<ConnectionDecorator> decs = ((DriverDataSource) ds).
createConnectionDecorators();
if (decs != null)
decorators.addAll(decs);
}
// logging decorator
LoggingConnectionDecorator lcd =
new LoggingConnectionDecorator();
Configurations.configureInstance(lcd, conf, opts);
lcd.getLogs().setJDBCLog(jdbcLog);
lcd.getLogs().setSQLLog(sqlLog);
decorators.add(lcd);
dds.addDecorators(decorators);
return dds;
} catch (OpenJPAException ke) {
throw ke;
} catch (Exception e) {
throw newConnectException(conf, factory2, e);
}
}
/**
* Install things deferred until the DBDictionary instance is available.
*
* @author Steve Kim
*/
public static DecoratingDataSource installDBDictionary(DBDictionary dict,
DecoratingDataSource ds, final JDBCConfiguration conf,
boolean factory2) {
DataSource inner = ds.getInnermostDelegate();
if (inner instanceof DriverDataSource)
((DriverDataSource) inner).initDBDictionary(dict);
Connection conn = null;
try {
// add the dictionary as a warning handler on the logging decorator
ConnectionDecorator cd;
for (Iterator<ConnectionDecorator> itr = ds.getDecorators().iterator(); itr.hasNext();) {
cd = itr.next();
if (cd instanceof LoggingConnectionDecorator)
((LoggingConnectionDecorator) cd).setWarningHandler(dict);
}
// misc configuration connection decorator (statement timeouts,
// transaction isolation, etc)
ConfiguringConnectionDecorator ccd =
new ConfiguringConnectionDecorator();
ccd.setTransactionIsolation(conf.getTransactionIsolationConstant());
//OPENJPA-2517: Allow a javax.persistence.query.timeout to apply to all
//EM operations (not just Query operations). Convert from milliseconds
//to seconds. See DBDictionary.setQueryTimeout for similar conversions.
//DBDictionary.setQueryTimeout will log warnings for invalid values,
//therefore there is no need to do so again here. Furthermore, there is no
//need to check for -1 here, ConfigurationConnectionDecorator checks for it.
int timeout = conf.getQueryTimeout();
if (dict.allowQueryTimeoutOnFindUpdate){
if (timeout > 0 && timeout < 1000) {
// round up to 1 sec
timeout = 1;
}
else if (timeout >= 1000){
timeout = timeout/1000;
}
}
ccd.setQueryTimeout(timeout);
Log log = conf.getLog(JDBCConfiguration.LOG_JDBC);
if (factory2 || !conf.isConnectionFactoryModeManaged()) {
if (!dict.supportsMultipleNontransactionalResultSets)
ccd.setAutoCommit(Boolean.FALSE);
else
ccd.setAutoCommit(Boolean.TRUE);
// add trace info for autoCommit setting
if (log.isTraceEnabled())
log.trace(_loc.get("set-auto-commit", new Object[] {
dict.supportsMultipleNontransactionalResultSets}));
}
Options opts = Configurations.parseProperties((factory2)
? conf.getConnectionFactory2Properties()
: conf.getConnectionFactoryProperties());
Configurations.configureInstance(ccd, conf, opts);
ds.addDecorator(ccd);
// allow the dbdictionary to decorate the connection further
ds.addDecorator(dict);
// ensure dbdictionary to process connectedConfiguration()
if (!factory2)
conn = ds.getConnection(conf.getConnectionUserName(), conf
.getConnectionPassword());
else
conn = ds.getConnection(conf.getConnection2UserName(), conf
.getConnection2Password());
return ds;
} catch (Exception e) {
throw newConnectException(conf, factory2, e);
} finally {
if (conn != null)
try {
conn.close();
} catch (SQLException se) {
// ignore any exception since the connection is not going
// to be used anyway
}
}
}
static OpenJPAException newConnectException(JDBCConfiguration conf,
boolean factory2, Exception cause) {
return new UserException(_eloc.get("poolds-null", factory2
? new Object[]{conf.getConnection2DriverName(),
conf.getConnection2URL()}
: new Object[]{conf.getConnectionDriverName(),
conf.getConnectionURL()}),
cause).setFatal(true);
}
/**
* Return a data source with the given user name and password
* pre-configured as the defaults when {@link DataSource#getConnection}
* is called.
*/
public static DataSource defaultsDataSource(DataSource ds,
String user, String pass) {
if (user == null && pass == null)
return ds;
// also check if they are both blank strings
if ("".equals(user) && "".equals(pass))
return ds;
return new DefaultsDataSource(ds, user, pass);
}
/**
* Close the given data source.
*/
public static void closeDataSource(DataSource ds) {
if (ds instanceof DelegatingDataSource)
ds = ((DelegatingDataSource) ds).getInnermostDelegate();
ImplHelper.close(ds);
}
/**
* A data source with pre-configured default user name and password.
*/
private static class DefaultsDataSource
extends DelegatingDataSource {
private final String _user;
private final String _pass;
public DefaultsDataSource(DataSource ds, String user, String pass) {
super(ds);
_user = user;
_pass = pass;
}
@Override
public Connection getConnection()
throws SQLException {
return super.getConnection(_user, _pass);
}
@Override
public Connection getConnection(String user, String pass)
throws SQLException {
return super.getConnection(user, pass);
}
}
}