/* (c) 2014 Open Source Geospatial Foundation - all rights reserved
* (c) 2001 - 2013 OpenPlans
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.data.jdbc;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.Statement;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.geotools.data.jdbc.datasource.UnWrapper;
import org.geotools.util.logging.Logging;
//import org.springframework.jdbc.support.nativejdbc.XAPoolNativeJdbcExtractor;
/**
* Generic UnWrapper using reflection to access original JDBC connection.
* <p>
* This implementation is a stand-in for SpringUnWrapper which is not currently able to unwrap JBoss
* WrappedConnectionJDK6 connections. While a list of well-known Connection implementations is
* catered for (with both the class name and unwrap method recorded) the GenericUnWrapper is willing
* to search through the available fields looking for an implementation of Connection to return.
*
* @author Jody Garnett - OpenGeo
*/
public class GenericUnWrapper implements UnWrapper {
private final static Logger LOGGER = Logging
.getLogger("org.geoserver.data.jdbc.GenericUnWrapper");
private static final Method IGNORE;
static {
try {
IGNORE = GenericUnWrapper.class.getMethod("ignore",new Class[0]);
} catch (Exception inconsistent) {
throw new IllegalStateException("Expected static GenericUnWrapper ignore() method",inconsistent);
}
}
/**
* Primary record of known access methods used to unwrap conenctions in exotic deployment
* environments such as JBoss.
* <p>
* This field is package visible to allow local testing.
*/
static final Map<Class<?>, Method> CONNECTION_METHODS;
static {
CONNECTION_METHODS = new ConcurrentHashMap<Class<?>, Method>();
// if the environment does not contain the classes ... skip
methodSearch("JBoss EAP 6.0", CONNECTION_METHODS,
"org.jboss.jca.adapters.jdbc.jdk6.WrappedConnectionJDK6", "getUnderlyingConnection");
methodSearch("JBoss JCA", CONNECTION_METHODS,
"org.jboss.jca.adapters.jdbc.WrappedConnection", "getUnderlyingConnection");
methodSearch("JBoss Resource Adapter", CONNECTION_METHODS,
"org.jboss.resource.adapter.jdbc.WrappedConnection", "getUnderlyingConnection");
}
/**
* Primary record of known access methods used to unwrap statements.
* <p>
* This field is package visible to allow local testing.
*/
static final Map<Class<?>, Method> STATEMENT_METHODS;
static {
STATEMENT_METHODS = new ConcurrentHashMap<Class<?>, Method>();
methodSearch("JBoss Resource Adapter", STATEMENT_METHODS,
"org.jboss.resource.adapter.jdbc.WrappedCallableStatement",
"getUnderlyingStatement");
}
/**
* Look up method used for unwrapping (if supported in the application container).
*
* @param env
* @param methods
* @param className
* @param methodName
*/
private static void methodSearch(String env, Map<Class<?>, Method> methods, String className,
String methodName) {
try {
Class<?> wrappedConnection = Class.forName(className);
Method unwrap = wrappedConnection.getMethod(methodName, (Class[]) null);
LOGGER.info(env + " " + className + " supported");
methods.put(wrappedConnection, unwrap);
} catch (ClassNotFoundException ignore) {
LOGGER.finer(env + " " + className + " not found");
} catch (Throwable e) {
LOGGER.fine(env + " " + className + " not available:" + e);
}
}
/**
* Used as reflection target of {@link #IGNORE} placeholder.
*/
public static void ignore(){
// this space is intentionally left blank
}
public boolean canUnwrap(Connection conn) {
Connection unwrapped = unwrapInternal(Connection.class, conn, CONNECTION_METHODS);
return unwrapped != null;
}
public Connection unwrap(Connection conn) {
Connection unwrapped = unwrapInternal(Connection.class, conn, CONNECTION_METHODS);
if (unwrapped != null) {
return unwrapped;
} else {
throw new IllegalArgumentException("This connection is not unwrappable, "
+ "check canUnwrap before calling unwrap");
}
}
public boolean canUnwrap(Statement statement) {
Statement unwrapped = unwrapInternal(Statement.class, statement, STATEMENT_METHODS);
return unwrapped != null;
}
public Statement unwrap(Statement statement) {
Statement unwrapped = unwrapInternal(Statement.class, statement, STATEMENT_METHODS);
if (unwrapped != null) {
return unwrapped;
} else {
throw new IllegalArgumentException("This statement is not unwrappable, "
+ "check canUnwrap before calling unwrap");
}
}
/**
* Using provided map of methods to unwrap. For each implementation class an unwrapper method is
* is provided, or null is sentinel (indicating no method is available). For classes that do not
* provide an unwrapping method reflection is tried once (resulting in either a cached method to
* next time, or null for use as a sentinel
*
* @param target
* @param conn
* @param methods
*
*/
private <T> T unwrapInternal(Class<T> target, T conn, Map<Class<?>, Method> methods) {
Class<?> implementation = conn.getClass();
// Check if we have a known method to use
if (methods.containsKey(implementation)) {
Method accessMethod = methods.get(implementation);
if (accessMethod == IGNORE) {
return null; // reflection has already been tried and come up empty
}
T unwrapped = unwrapInternal(target, conn, implementation, accessMethod);
return unwrapped;
}
else {
// Scan for superclass/interface method
for (Entry<Class<?>, Method> entry : methods.entrySet()) {
Class<?> wrapper = entry.getKey();
Method accessMethod = entry.getValue();
if (wrapper.isInstance(conn)) {
T unwrapped = unwrapInternal(target, conn, wrapper, accessMethod);
if (unwrapped != null) {
methods.put(implementation, accessMethod);
return unwrapped;
}
}
}
// Use reflection to scan for an accessMethod
for (Method method : implementation.getMethods()) {
if (target.isAssignableFrom(method.getReturnType())
&& method.getParameterTypes().length == 0 && method.isAccessible()) {
// possible accessor method
T unwrapped = unwrapInternal(target, conn, implementation, method);
if (unwrapped != null) {
methods.put(implementation, method);
return unwrapped;
}
}
}
// Give up - mark this one as not possible so we can exit early next time
methods.put(implementation, IGNORE);
}
return null; // not found
}
/**
* Safe unwrap method using invoke on the provided accessMethod.
*
* All errors are logged at finest detail, and null is returned.
*
* @param target
* @param conn
* @param wrapper
* @param accessMethod
* @return unwrapped instance of target class, or null if not available
*/
private <T> T unwrapInternal(Class<T> target, T conn, Class<?> wrapper, Method accessMethod) {
if (accessMethod == null ) {
LOGGER.finest("Using " + wrapper.getName() + " does not have accessMethod to unwrap " + target.getSimpleName() );
return null; // skip inaccessible method
}
try {
Object result = accessMethod.invoke(conn, (Object[]) null);
if (result == null) {
LOGGER.finest("Using " + wrapper.getName() + "." + accessMethod.getName()
+ "() to unwrap " + target.getSimpleName() + " produced a null");
return null;
}
if (result == conn) {
LOGGER.finest("Using " + wrapper.getName() + "." + accessMethod.getName()
+ "() to unwrap did not result in native " + target.getSimpleName() + ": "
+ result.getClass().getSimpleName());
return null;
}
if (!target.isInstance(result)) {
LOGGER.finest("Using " + wrapper.getName() + "." + accessMethod.getName()
+ "() to unwrap did not result in native " + target.getSimpleName() + ": "
+ result.getClass().getSimpleName());
return null;
}
return target.cast(result);
} catch (IllegalArgumentException e) {
LOGGER.log(Level.FINEST, "Using " + wrapper.getName() + "." + accessMethod.getName()
+ "() to unwrap " + target.getSimpleName() + " failed: " + e);
return null; // unexpected with no arguments
} catch (IllegalAccessException e) {
LOGGER.log(Level.FINEST, "Using " + wrapper.getName() + "." + accessMethod.getName()
+ "() to unwrap " + target.getSimpleName() + " failed: " + e);
return null; // could be a visibility issue
} catch (InvocationTargetException e) {
LOGGER.log(Level.FINEST, "Using " + wrapper.getName() + "." + accessMethod.getName()
+ "() to unwrap " + target.getSimpleName() + " failed: " + e);
return null; // abort abort
}
}
}