GenericNamingResourcesFactory.java
/*
* 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.tomcat.jdbc.naming;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Set;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.NamingException;
import javax.naming.RefAddr;
import javax.naming.Reference;
import javax.naming.spi.ObjectFactory;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.jdbc.pool.ClassLoaderUtil;
import org.apache.tomcat.jdbc.pool.util.MessageFactory;
/**
* Simple way of configuring generic resources by using reflection. Example usage:
*
* <pre>
* <code>
* <Resource factory="org.apache.tomcat.jdbc.naming.GenericNamingResourcesFactory"
* name="jdbc/test"
* type="org.apache.derby.jdbc.ClientXADataSource"
* databaseName="sample"
* createDatabase="create"
* serverName="localhost"
* port="1527"/>
* </code>
* </pre>
*/
public class GenericNamingResourcesFactory implements ObjectFactory {
/**
* Creates a new GenericNamingResourcesFactory instance.
*/
public GenericNamingResourcesFactory() {
// Default constructor
}
private static final Log log = LogFactory.getLog(GenericNamingResourcesFactory.class);
/**
* Reference metadata that Tomcat attaches to a {@code ResourceRef}; these are not bean properties of the target
* object. Mirrors the list excluded by {@code org.apache.naming.factory.BeanFactory}. Inlined because
* tomcat-jdbc.jar does not depend on catalina.jar.
*/
private static final Set<String> RESERVED_ADDR_TYPES = Collections.unmodifiableSet(
new HashSet<>(Arrays.asList("factory", "singleton", "scope", "auth", "description", "forceString")));
@Override
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment)
throws Exception {
if ((obj == null) || !(obj instanceof Reference)) {
return null;
}
Reference ref = (Reference) obj;
Enumeration<RefAddr> refs = ref.getAll();
String type = ref.getClassName();
if (type == null || type.isEmpty()) {
throw new NamingException(
MessageFactory.get("genericNamingResourcesFactory.noResourceType", name));
}
Object o;
try {
Class<?> clazz = ClassLoaderUtil.loadClass(type, GenericNamingResourcesFactory.class.getClassLoader(),
Thread.currentThread().getContextClassLoader());
o = clazz.getConstructor().newInstance();
} catch (ClassNotFoundException e) {
NamingException ne =
new NamingException(MessageFactory.get("genericNamingResourcesFactory.classNotFound", type, name));
ne.setRootCause(e);
throw ne;
} catch (InvocationTargetException e) {
// The class has a no-argument constructor, but it threw. Propagate
// only a fatal VirtualMachineError, in the spirit of
// ExceptionUtils.handleThrowable(), which tomcat-jdbc.jar cannot
// use directly.
Throwable cause = e.getCause();
if (cause instanceof VirtualMachineError) {
throw (VirtualMachineError) cause;
}
NamingException ne =
new NamingException(MessageFactory.get("genericNamingResourcesFactory.constructorThrew", type, name));
ne.setRootCause(e);
throw ne;
} catch (ReflectiveOperationException e) {
// No public no-arg constructor (NoSuchMethodException), or the
// class cannot be instantiated (abstract, interface, illegal
// access).
NamingException ne =
new NamingException(MessageFactory.get("genericNamingResourcesFactory.noUsableConstructor",
type, name));
ne.setRootCause(e);
throw ne;
}
while (refs.hasMoreElements()) {
RefAddr addr = refs.nextElement();
String param = addr.getType();
if (param == null || RESERVED_ADDR_TYPES.contains(param)) {
// Reference metadata, not a configuration property
continue;
}
String value = null;
if (addr.getContent() != null) {
value = addr.getContent().toString();
}
if (!setProperty(o, param, value)) {
log.warn(MessageFactory.get("genericNamingResourcesFactory.propertyNotApplied", param, type));
}
}
return o;
}
@SuppressWarnings("null") // setPropertyMethodVoid can't be null when used
private static boolean setProperty(Object o, String name, String value) {
if (log.isTraceEnabled()) {
log.trace("IntrospectionUtils: setProperty(" + o.getClass() + " " + name + "=" + value + ")");
}
String setter = "set" + capitalize(name);
try {
Method[] methods = o.getClass().getMethods();
Method setPropertyMethodVoid = null;
Method setPropertyMethodBool = null;
// First, the ideal case - a setFoo( String ) method
for (int i = 0; i < methods.length; i++) {
Class<?>[] paramT = methods[i].getParameterTypes();
if (setter.equals(methods[i].getName()) && paramT.length == 1 &&
"java.lang.String".equals(paramT[0].getName())) {
methods[i].invoke(o, new Object[] { value });
return true;
}
}
// Try a setFoo ( int ) or ( boolean )
for (int i = 0; i < methods.length; i++) {
boolean ok = true;
if (setter.equals(methods[i].getName()) && methods[i].getParameterTypes().length == 1) {
// Match - find the type and invoke it
Class<?> paramType = methods[i].getParameterTypes()[0];
Object[] params = new Object[1];
// Try a setFoo ( int )
if ("java.lang.Integer".equals(paramType.getName()) || "int".equals(paramType.getName())) {
try {
params[0] = Integer.valueOf(value);
} catch (NumberFormatException ex) {
ok = false;
}
// Try a setFoo ( long )
} else if ("java.lang.Long".equals(paramType.getName()) || "long".equals(paramType.getName())) {
try {
params[0] = Long.valueOf(value);
} catch (NumberFormatException ex) {
ok = false;
}
// Try a setFoo ( boolean )
} else if ("java.lang.Boolean".equals(paramType.getName()) ||
"boolean".equals(paramType.getName())) {
// Boolean.valueOf() reports everything it does not
// recognise, missing content included, as false.
// Missing content is not a configured value: let the
// caller report the property as not applied.
if (value == null || value.isEmpty()) {
ok = false;
} else {
params[0] = Boolean.valueOf(value);
}
// Try a setFoo ( InetAddress )
} else if ("java.net.InetAddress".equals(paramType.getName())) {
// getByName() documents null and empty content as the
// loopback address instead of failing, and a missing
// host is not a request for loopback: do not invoke
// the setter with a fabricated address.
if (value == null || value.isEmpty()) {
ok = false;
} else {
try {
params[0] = InetAddress.getByName(value);
} catch (UnknownHostException exc) {
if (log.isDebugEnabled()) {
log.debug("IntrospectionUtils: Unable to resolve host name:" + value);
}
ok = false;
}
}
// Unknown type
} else {
// Unsupported parameter type. Do not invoke the
// method with a null value. Let the caller fall back
// to setProperty(name, value) or handle the failure.
ok = false;
if (log.isDebugEnabled()) {
log.debug("IntrospectionUtils: Unknown type " + paramType.getName());
}
}
if (ok) {
methods[i].invoke(o, params);
return true;
}
}
// Save "setProperty" for later, but only one that the two
// string arguments below can actually be passed to: a
// setProperty of any other signature is not a (name, value)
// property bag, and invoking it reflectively with those
// arguments would only fail with an IllegalArgumentException
// that masks the "property was not applied" report.
if ("setProperty".equals(methods[i].getName())) {
Class<?>[] paramT = methods[i].getParameterTypes();
if (paramT.length == 2 && paramT[0].isAssignableFrom(String.class)
&& paramT[1].isAssignableFrom(String.class)) {
if (methods[i].getReturnType() == Boolean.TYPE) {
setPropertyMethodBool = methods[i];
} else {
setPropertyMethodVoid = methods[i];
}
}
}
}
// Ok, no setXXX found, try a setProperty("name", "value")
if (setPropertyMethodBool != null || setPropertyMethodVoid != null) {
Object[] params = new Object[2];
params[0] = name;
params[1] = value;
if (setPropertyMethodBool != null) {
try {
return ((Boolean) setPropertyMethodBool.invoke(o, params)).booleanValue();
} catch (IllegalArgumentException biae) {
// Defensive: the recorded method's parameter types
// were checked above, so invoking it with the name
// and value should not fail on the arguments; if it
// somehow does, fall back to the other method.
if (setPropertyMethodVoid != null) {
setPropertyMethodVoid.invoke(o, params);
return true;
} else {
throw biae;
}
}
} else {
setPropertyMethodVoid.invoke(o, params);
return true;
}
}
} catch (IllegalArgumentException ex2) {
log.warn(MessageFactory.get("genericNamingResourcesFactory.unableToSetProperty",
name, value, o.getClass().getName()), ex2);
} catch (SecurityException ex1) {
log.warn(MessageFactory.get("genericNamingResourcesFactory.securityExceptionSettingProperty",
name, value, o.getClass().getName()), ex1);
} catch (IllegalAccessException iae) {
log.warn(MessageFactory.get("genericNamingResourcesFactory.illegalAccessExceptionSettingProperty",
name, value, o.getClass().getName()), iae);
} catch (InvocationTargetException ie) {
// In the spirit of ExceptionUtils.handleThrowable(), which is not
// available to tomcat-jdbc.jar: propagate the fatal
// VirtualMachineError from the setter and report everything else.
Throwable cause = ie.getCause();
if (cause instanceof ThreadDeath) {
throw (ThreadDeath) cause;
}
if (cause instanceof VirtualMachineError) {
throw (VirtualMachineError) cause;
}
log.warn(MessageFactory.get("genericNamingResourcesFactory.setterThrew",
name, value, o.getClass().getName()), ie);
}
return false;
}
/**
* Capitalizes the first character of the given string.
*
* @param name The string to capitalize
*
* @return The capitalized string, or the original if null or empty
*/
public static String capitalize(String name) {
if (name == null || name.length() == 0) {
return name;
}
char[] chars = name.toCharArray();
chars[0] = Character.toUpperCase(chars[0]);
return new String(chars);
}
}