net.java.ao.db.NuoDBDisposableDataSourceHandler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of activeobjects-core Show documentation
Show all versions of activeobjects-core Show documentation
This is the core library for Active Objects. It is generic and can be embedded in any environment.
As such it is generic and won't contain all connection pooling, etc.
/*
* Licensed 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 net.java.ao.db;
import net.java.ao.Disposable;
import net.java.ao.DisposableDataSource;
import org.slf4j.Logger;
import javax.sql.DataSource;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Map;
import java.util.Objects;
import static com.google.common.collect.Maps.newHashMap;
import static java.lang.reflect.Proxy.newProxyInstance;
import static java.sql.ResultSet.TYPE_FORWARD_ONLY;
import static java.sql.Types.BIGINT;
import static org.slf4j.LoggerFactory.getLogger;
/**
* The NuoDB JDBC Driver supports ResultSet.TYPE_FORWARD_ONLY currently. The NuoDB data source handler intercepts
* invocations of methods, which create SQL statements and forcibly overrides result set type to
* {@link java.sql.ResultSet.TYPE_FORWARD_ONLY}. These are methods intercepted by the handler:
*
* - Statement createStatement(int resultSetType, int resultSetConcurrency);
* - PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency);
* - CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency);
*
*
* @author Sergey Bushik
*/
public class NuoDBDisposableDataSourceHandler {
private static final ClassLoader CLASS_LOADER = NuoDBDisposableDataSourceHandler.class.getClassLoader();
/**
* Creates reflection proxy for the specified NuoDB data source
*
* @param dataSource to intercept invocations
* @return disposable data source
*/
public static DisposableDataSource newInstance(DataSource dataSource) {
return newInstance(dataSource, null);
}
public static DisposableDataSource newInstance(DataSource dataSource, Disposable disposable) {
return (DisposableDataSource) newProxy(
new Class[]{DisposableDataSource.class},
new DelegatingDisposableDataSourceHandler(dataSource, disposable));
}
private static Object newProxy(Class>[] interfaces, InvocationHandler invocationHandler) {
Object proxy = newProxyInstance(CLASS_LOADER, interfaces, invocationHandler);
return proxy;
}
/**
* Intercepts dispose() and getConnection() methods
*/
static class DelegatingDisposableDataSourceHandler extends DelegatingInvocationHandler {
private static final String GET_CONNECTION = "getConnection";
private static final String DISPOSE = "dispose";
private Disposable disposable;
public DelegatingDisposableDataSourceHandler(DataSource dataSource, Disposable disposable) {
super(dataSource);
this.disposable = disposable;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String name = method.getName();
Class>[] parameterTypes = method.getParameterTypes();
Object result = null;
// call dispose
if (name.equals(DISPOSE) && parameterTypes.length == 0 && disposable != null) {
disposable.dispose();
// proxy getConnection() invocation
} else if (name.equals(GET_CONNECTION)) {
Connection connection = (Connection) delegate(method, args);
result = newProxy(new Class[]{Connection.class},
new DelegatingConnectionHandler(connection));
} else {
result = delegate(method, args);
}
return result;
}
}
/**
* The handler, where result set type actually forcibly changed to those supported by NuoDB JDBC Driver.
*/
static class DelegatingConnectionHandler extends DelegatingInvocationHandler {
public static final String CREATE_STATEMENT = "createStatement";
public static final String PREPARE_STATEMENT = "prepareStatement";
public static final String PREPARE_CALL = "prepareCall";
public DelegatingConnectionHandler(Connection connection) {
super(connection);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String name = method.getName();
Class>[] parameterTypes = method.getParameterTypes();
Class extends Statement> statement = null;
Integer resultSetTypeArgIndex = null;
// change result set type to java.sql.Types.TYPE_FORWARD_ONLY
if (name.equals(CREATE_STATEMENT)) {
statement = Statement.class;
// Statement createStatement(int resultSetType, int resultSetConcurrency);
// Statement createStatement(int resultSetType, int resultSetConcurrency,
// int resultSetHoldability) throws SQLException;
if (parameterTypes.length >= 2) {
resultSetTypeArgIndex = 0;
}
} else if (name.equals(PREPARE_STATEMENT)) {
// PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency);
// PreparedStatement prepareStatement(String sql, int resultSetType,
// int resultSetConcurrency, int resultSetHoldability);
statement = PreparedStatement.class;
if (parameterTypes.length >= 3) {
resultSetTypeArgIndex = 1;
}
} else if (name.equals(PREPARE_CALL)) {
statement = CallableStatement.class;
// CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency);
// CallableStatement prepareCall(String sql, int resultSetType,
// int resultSetConcurrency, int resultSetHoldability) throws SQLException;
if (parameterTypes.length >= 3) {
resultSetTypeArgIndex = 1;
}
}
if (resultSetTypeArgIndex != null &&
((Integer) args[resultSetTypeArgIndex]) != TYPE_FORWARD_ONLY) {
args[resultSetTypeArgIndex] = TYPE_FORWARD_ONLY;
if (logger.isTraceEnabled()) {
logger.trace("Result set type changed to forward only");
}
}
Object result = super.invoke(proxy, method, args);
return statement != null ? newProxy(new Class[]{statement},
new DelegatingStatementHandler((Connection) proxy, (Statement) result)) : result;
}
}
static class DelegatingStatementHandler extends DelegatingInvocationHandler {
private static final String GET_CONNECTION = "getConnection";
private static final String GET_RESULT_SET = "getResultSet";
private static final String EXECUTE_QUERY = "executeQuery";
private Connection connection;
public DelegatingStatementHandler(Connection connection, Statement statement) {
super(statement);
this.connection = connection;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String name = method.getName();
Object result;
// proxy connection
if (name.equals(GET_CONNECTION)) {
result = connection;
// proxy result set
} else if (name.equals(EXECUTE_QUERY) || name.equals(GET_RESULT_SET)) {
result = newProxy(new Class[]{ResultSet.class},
new DelegatingResultSetHandler((Statement) proxy,
(ResultSet) super.invoke(proxy, method, args)));
} else {
result = super.invoke(proxy, method, args);
}
return result;
}
}
static class DelegatingResultSetHandler extends DelegatingInvocationHandler {
private static final String GET_STATEMENT = "getStatement";
private static final String GET_OBJECT = "getObject";
private ResultSetMetaData metaData;
private Statement statement;
private Map columns;
protected DelegatingResultSetHandler(Statement statement, ResultSet resultSet) throws SQLException {
super(resultSet);
this.statement = statement;
this.metaData = target.getMetaData();
int count = metaData.getColumnCount();
Map columns = newHashMap();
for (int index = 0; index < count; index++) {
int column = index + 1;
columns.put(metaData.getColumnName(column), column);
}
this.columns = columns;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String name = method.getName();
Object result;
// proxy statement
if (name.equals(GET_STATEMENT)) {
result = statement;
// change resultSet.getObject() for BIGINT to return resultSet.getLong()
} else if (name.equals(GET_OBJECT)) {
Integer column = null;
if (method.getParameterTypes()[0].equals(String.class)) {
column = columns.get(args[0]);
} else {
column = (Integer) args[0];
}
// fixes get object for java.sql.Types.BIGINT to return long
if (column != null && metaData.getColumnType(column) == BIGINT) {
result = target.getLong(column);
} else {
result = super.invoke(proxy, method, args);
}
} else {
result = super.invoke(proxy, method, args);
}
return result;
}
}
/**
* Base delegating invocation handler
*/
static class DelegatingInvocationHandler implements InvocationHandler {
protected final Logger logger = getLogger(getClass());
protected final T target;
protected DelegatingInvocationHandler(T target) {
this.target = Objects.requireNonNull(target, "target can't be null");
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return delegate(method, args);
}
protected Object delegate(Method method, Object[] args) throws Throwable {
final Method m = target.getClass().getMethod(method.getName(), method.getParameterTypes());
m.setAccessible(true);
try {
return m.invoke(target, args);
} catch (IllegalAccessException exception) {
// avoid UndeclaredThrowableExceptions
throw new RuntimeException(exception);
} catch (IllegalArgumentException exception) {
// avoid UndeclaredThrowableExceptions
throw new RuntimeException(exception);
} catch (InvocationTargetException exception) {
// avoid UndeclaredThrowableExceptions
throw exception.getCause();
}
}
}
}