/**
* 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.tomee.jdbc;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.openejb.cipher.PasswordCipherFactory;
import org.apache.openejb.loader.SystemInstance;
import org.apache.openejb.monitoring.LocalMBeanServer;
import org.apache.openejb.monitoring.ObjectNameBuilder;
import org.apache.openejb.resource.jdbc.dbcp.DataSourceSerialization;
import org.apache.openejb.resource.jdbc.pool.PoolDataSourceCreator;
import org.apache.openejb.spi.ContainerSystem;
import org.apache.openejb.util.Duration;
import org.apache.openejb.util.SuperProperties;
import org.apache.openejb.util.reflection.Reflections;
import org.apache.tomcat.jdbc.pool.ConnectionPool;
import org.apache.tomcat.jdbc.pool.PoolConfiguration;
import org.apache.tomcat.jdbc.pool.PoolProperties;
import org.apache.tomcat.jdbc.pool.PooledConnection;
import javax.management.ObjectName;
import javax.naming.NamingException;
import javax.sql.CommonDataSource;
import javax.sql.DataSource;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.SQLException;
import java.util.Properties;
public class TomEEDataSourceCreator extends PoolDataSourceCreator {
@Override
public DataSource pool(final String name, final DataSource ds, final Properties properties) {
final PoolConfiguration config = build(TomEEPoolProperties.class, createProperties(name, properties));
config.setDataSource(ds);
final ConnectionPool pool;
try {
pool = new ConnectionPool(config);
} catch (final SQLException e) {
throw new IllegalStateException(e);
}
final TomEEDataSource dataSource = new TomEEDataSource(config, pool, name);
recipes.put(dataSource, recipes.remove(config)); // transfer unset props for correct logging
return dataSource;
}
@Override
public CommonDataSource pool(final String name, final String driver, final Properties properties) {
final PoolConfiguration config = build(TomEEPoolProperties.class, createProperties(name, properties));
final TomEEDataSource ds = new TomEEDataSource(config, name);
recipes.put(ds, recipes.remove(config));
return ds;
}
@Override
protected void doDestroy(final CommonDataSource dataSource) throws Throwable {
final org.apache.tomcat.jdbc.pool.DataSource ds = (org.apache.tomcat.jdbc.pool.DataSource) dataSource;
if (ds instanceof TomEEDataSource) {
((TomEEDataSource) ds).internalJMXUnregister();
}
ds.close(true);
}
@Override
protected boolean trackRecipeFor(final Object value) {
return super.trackRecipeFor(value) || TomEEPoolProperties.class.isInstance(value);
}
private SuperProperties createProperties(final String name, final Properties properties) {
final SuperProperties converted = new SuperProperties() {
@Override
public Object setProperty(final String name, final String value) {
if (value == null) {
return super.getProperty(name);
}
return super.setProperty(name, value);
}
}.caseInsensitive(true);
converted.setProperty("name", name);
// very few properties have default = connection ones, so ensure to translate them with priority to specific ones
converted.setProperty("url", properties.getProperty("url", (String) properties.remove("JdbcUrl")));
converted.setProperty("driverClassName", properties.getProperty("driverClassName", (String) properties.remove("JdbcDriver")));
converted.setProperty("username", (String) properties.remove("username"));
converted.setProperty("password", (String) properties.remove("password"));
converted.putAll(properties);
final String passwordCipher = (String) converted.remove("PasswordCipher");
if (passwordCipher != null && !"PlainText".equals(passwordCipher)) {
converted.setProperty("password", PasswordCipherFactory.getPasswordCipher(passwordCipher).decrypt(converted.getProperty("Password").toCharArray()));
}
return converted;
}
public static class TomEEDataSource extends org.apache.tomcat.jdbc.pool.DataSource implements Serializable {
private static final Log LOGGER = LogFactory.getLog(TomEEDataSource.class);
private static final Class<?>[] CONNECTION_POOL_CLASS = new Class<?>[]{ PoolConfiguration.class };
private final String name;
private ObjectName internalOn;
public TomEEDataSource(final PoolConfiguration properties, final ConnectionPool pool, final String name) {
super(readOnly(properties));
this.pool = pool;
initJmx(name);
this.name = name;
}
public TomEEDataSource(final PoolConfiguration poolConfiguration, final String name) {
super(readOnly(poolConfiguration));
try { // just to force the pool to be created and be able to register the mbean
createPool();
initJmx(name);
} catch (final Throwable e) {
LOGGER.error("Can't create DataSource", e);
}
this.name = name;
}
@Override
protected void registerJmx() {
// no-op
}
@Override
protected void unregisterJmx() {
// no-op
}
@Override
public ConnectionPool createPool() throws SQLException {
if (pool != null) {
return pool;
} else {
pool = new TomEEConnectionPool(poolProperties, Thread.currentThread().getContextClassLoader()); // to force to init the driver with TCCL
return pool;
}
}
private static PoolConfiguration readOnly(final PoolConfiguration pool) {
try {
return (PoolConfiguration) Proxy.newProxyInstance(TomEEDataSourceCreator.class.getClassLoader(), CONNECTION_POOL_CLASS, new ReadOnlyConnectionpool(pool));
} catch (final Throwable e) {
return (PoolConfiguration) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), CONNECTION_POOL_CLASS, new ReadOnlyConnectionpool(pool));
}
}
private void initJmx(final String name) {
try {
internalOn = ObjectNameBuilder.uniqueName("datasources", name.replace("/", "_"), this);
try {
if (pool.getJmxPool() != null) {
LocalMBeanServer.get().registerMBean(pool.getJmxPool(), internalOn);
}
} catch (final Exception e) {
LOGGER.error("Unable to register JDBC pool with JMX", e);
}
} catch (final Exception ignored) {
// no-op
}
}
public void internalJMXUnregister() {
if (internalOn != null) {
try {
LocalMBeanServer.get().unregisterMBean(internalOn);
} catch (final Exception e) {
LOGGER.error("Unable to unregister JDBC pool with JMX", e);
}
}
}
Object writeReplace() throws ObjectStreamException {
return new DataSourceSerialization(name);
}
}
private static class ReadOnlyConnectionpool implements InvocationHandler {
private final PoolConfiguration delegate;
public ReadOnlyConnectionpool(final PoolConfiguration pool) {
this.delegate = pool;
}
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
final String name = method.getName();
if (!(name.startsWith("set") && args != null && args.length == 1 && Void.TYPE.equals(method.getReturnType()))) {
return method.invoke(delegate, args);
}
if (name.equals("setDataSource")) {
delegate.setDataSource(args[0]);
}
return null;
}
}
private static class TomEEConnectionPool extends ConnectionPool {
private final ClassLoader creationLoader;
public TomEEConnectionPool(final PoolConfiguration poolProperties, final ClassLoader creationLoader) throws SQLException {
super(poolProperties);
this.creationLoader = creationLoader;
}
@Override
protected PooledConnection create(final boolean incrementCounter) {
final PooledConnection con = super.create(incrementCounter);
if (getPoolProperties().getDataSource() == null) { // using driver
// init driver with TCCL
ClassLoader cl = Thread.currentThread().getContextClassLoader();
if (cl == null) {
cl = TomEEConnectionPool.class.getClassLoader();
}
try {
Reflections.set(con, "driver", Class.forName(getPoolProperties().getDriverClassName(), true, cl).newInstance());
} catch (final ClassNotFoundException cnfe) {
try { // custom resource classloader
Reflections.set(con, "driver", Class.forName(getPoolProperties().getDriverClassName(), true, creationLoader).newInstance());
} catch (final Exception e) {
// will fail later, no worry
}
} catch (final Exception cn) {
// will fail later, no worry
}
}
return con;
}
}
// enhanced API/setters
public static class TomEEPoolProperties extends PoolProperties {
public void setMinEvictableIdleTime(final String minEvictableIdleTime) {
final Duration duration = new Duration(minEvictableIdleTime);
super.setMinEvictableIdleTimeMillis((int) duration.getUnit().toMillis(duration.getTime()));
}
public void setTimeBetweenEvictionRuns(final String timeBetweenEvictionRuns) {
final Duration duration = new Duration(timeBetweenEvictionRuns);
super.setMinEvictableIdleTimeMillis((int) duration.getUnit().toMillis(duration.getTime()));
}
public void setXaDataSource(final String jndi) {
// we should do setDataSourceJNDI(jndi); but ATM tomcat doesnt do the lookup so using this as correct impl
try {
setDataSource(SystemInstance.get().getComponent(ContainerSystem.class).getJNDIContext().lookup("openejb:Resource/" + jndi));
} catch (final NamingException e) {
throw new IllegalStateException(e);
}
}
@Override
public void setDataSourceJNDI(final String jndi) {
super.setDataSourceJNDI("openejb:Resource/" + jndi);
}
}
}