--- /dev/null
+/*
+ */
+package org.apache.tomcat.integration;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Refactoring of IntrospectionUtils and modeler dynamic bean.
+ *
+ * Unlike IntrospectionUtils, the method informations can be cached.
+ * Also I hope this class will be simpler to use.
+ * There is no static cache.
+ *
+ * @author Costin Manolache
+ */
+public class DynamicObject {
+ // Based on MbeansDescriptorsIntrospectionSource
+
+ static Logger log = Logger.getLogger(DynamicObject.class.getName());
+
+ static Class<?> NO_PARAMS[] = new Class[0];
+
+
+ private static String strArray[] = new String[0];
+
+ private static Class<?>[] supportedTypes = new Class[] { Boolean.class,
+ Boolean.TYPE, Byte.class, Byte.TYPE, Character.class,
+ Character.TYPE, Short.class, Short.TYPE, Integer.class,
+ Integer.TYPE, Long.class, Long.TYPE, Float.class, Float.TYPE,
+ Double.class, Double.TYPE, String.class, strArray.getClass(),
+ BigDecimal.class, BigInteger.class, AtomicInteger.class,
+ AtomicLong.class, java.io.File.class, };
+
+
+ private Class realClass;
+
+ private Map<String, Method> getAttMap;
+
+ public DynamicObject(Class beanClass) {
+ this.realClass = beanClass;
+ initCache();
+ }
+
+ public DynamicObject(Class beanClass, boolean noCache) {
+ this.realClass = beanClass;
+ }
+
+ private void initCache() {
+ Method methods[] = null;
+
+ getAttMap = new HashMap<String, Method>();
+
+ methods = realClass.getMethods();
+ for (int j = 0; j < methods.length; ++j) {
+ if (ignorable(methods[j])) {
+ continue;
+ }
+ String name = methods[j].getName();
+
+ Class<?> params[] = methods[j].getParameterTypes();
+
+ if (name.startsWith("get") && params.length == 0) {
+ Class<?> ret = methods[j].getReturnType();
+ if (!supportedType(ret)) {
+ if (log.isLoggable(Level.FINE))
+ log.fine("Unsupported type " + methods[j]);
+ continue;
+ }
+ name = unCapitalize(name.substring(3));
+
+ getAttMap.put(name, methods[j]);
+ } else if (name.startsWith("is") && params.length == 0) {
+ Class<?> ret = methods[j].getReturnType();
+ if (Boolean.TYPE != ret) {
+ if (log.isLoggable(Level.FINE))
+ log.fine("Unsupported type " + methods[j] + " " + ret);
+ continue;
+ }
+ name = unCapitalize(name.substring(2));
+
+ getAttMap.put(name, methods[j]);
+ }
+ }
+ }
+
+ private boolean ignorable(Method method) {
+ if (Modifier.isStatic(method.getModifiers()))
+ return true;
+ if (!Modifier.isPublic(method.getModifiers())) {
+ return true;
+ }
+ if (method.getDeclaringClass() == Object.class)
+ return true;
+ return false;
+ }
+
+ public List<String> attributeNames() {
+ List<String> attributes = new ArrayList<String>();
+ Method methods[] = realClass.getMethods();
+ for (int j = 0; j < methods.length; ++j) {
+ String name = methods[j].getName();
+ if (ignorable(methods[j])) {
+ continue;
+ }
+ Class<?> params[] = methods[j].getParameterTypes();
+ if (name.startsWith("get") && params.length == 0) {
+ Class<?> ret = methods[j].getReturnType();
+ if (!supportedType(ret)) {
+ continue;
+ }
+ name = unCapitalize(name.substring(3));
+ attributes.add(name);
+ } else if (name.startsWith("is") && params.length == 0) {
+ Class<?> ret = methods[j].getReturnType();
+ if (Boolean.TYPE != ret) {
+ continue;
+ }
+ name = unCapitalize(name.substring(2));
+ attributes.add(name);
+ } else if (name.startsWith("set") && params.length == 1) {
+ if (!supportedType(params[0])) {
+ continue;
+ }
+ name = unCapitalize(name.substring(3));
+ attributes.add(name);
+ }
+ }
+
+ return attributes;
+ }
+
+
+ public Object invoke(Object proxy, String method) throws Exception {
+ Method executeM = null;
+ Class<?> c = proxy.getClass();
+ executeM = c.getMethod(method, NO_PARAMS);
+ if (executeM == null) {
+ throw new RuntimeException("No execute in " + proxy.getClass());
+ }
+ return executeM.invoke(proxy, (Object[]) null);
+ }
+
+ // TODO
+ public Object invoke(String method, Object[] params) {
+ return null;
+ }
+
+ public boolean hasHook(String method) {
+ return false;
+ }
+
+ public Object getAttribute(Object o, String att) {
+ Method m = getAttMap.get(att);
+ if (m == null)
+ return null;
+ try {
+ return m.invoke(o);
+ } catch (Throwable e) {
+ log.log(Level.INFO, "Error getting attribute " + realClass + " "
+ + att, e);
+ return null;
+ }
+ }
+
+ public boolean setAttribute(Object proxy, String name, Object value) {
+ String methodName = "set" + capitalize(name);
+ Method[] methods = proxy.getClass().getMethods();
+ for (Method m : methods) {
+ Class<?>[] paramT = m.getParameterTypes();
+ if (methodName.equals(m.getName())
+ && paramT.length == 1
+ && (value == null || paramT[0].isAssignableFrom(value
+ .getClass()))) {
+ try {
+ m.invoke(proxy, value);
+ return true;
+ } catch (IllegalArgumentException e) {
+ log.severe("Error setting: " + name + " "
+ + proxy.getClass().getName() + " " + e);
+ } catch (IllegalAccessException e) {
+ log.severe("Error setting: " + name + " "
+ + proxy.getClass().getName() + " " + e);
+ } catch (InvocationTargetException e) {
+ log.severe("Error setting: " + name + " "
+ + proxy.getClass().getName() + " " + e);
+ }
+ }
+ }
+ return false;
+ }
+
+ public boolean setProperty(Object proxy, String name, String value) {
+ String setter = "set" + capitalize(name);
+
+ try {
+ Method methods[] = proxy.getClass().getMethods();
+
+ Method setPropertyMethod = null;
+
+ // First, the ideal case - a setFoo( String ) method
+ for (int i = 0; i < methods.length; i++) {
+ if (ignorable(methods[i])) {
+ continue;
+ }
+ Class<?> paramT[] = methods[i].getParameterTypes();
+ if (setter.equals(methods[i].getName()) && paramT.length == 1) {
+ if ("java.lang.String".equals(paramT[0].getName())) {
+ methods[i].invoke(proxy, new Object[] { value });
+ return true;
+ } else {
+ // match - find the type and invoke it
+ Class<?> paramType = methods[i].getParameterTypes()[0];
+ Object params[] = new Object[1];
+ params[0] = convert(value, paramType);
+ if (params[0] != null) {
+ methods[i].invoke(proxy, params);
+ return true;
+ }
+ }
+ }
+ // save "setProperty" for later
+ if ("setProperty".equals(methods[i].getName()) &&
+ paramT.length == 2 &&
+ paramT[0] == String.class &&
+ paramT[1] == String.class) {
+ setPropertyMethod = methods[i];
+ }
+ }
+
+ try {
+ Field field = proxy.getClass().getField(name);
+ if (field != null) {
+ Object conv = convert(value, field.getType());
+ if (conv != null) {
+ field.set(proxy, conv);
+ return true;
+ }
+ }
+ } catch (NoSuchFieldException e) {
+ // ignore
+ }
+
+ // Ok, no setXXX found, try a setProperty("name", "value")
+ if (setPropertyMethod != null) {
+ Object params[] = new Object[2];
+ params[0] = name;
+ params[1] = value;
+ setPropertyMethod.invoke(proxy, params);
+ return true;
+ }
+
+ } catch (Throwable ex2) {
+ log.log(Level.WARNING, "IAE " + proxy + " " + name + " " + value,
+ ex2);
+ }
+ return false;
+ }
+
+ // ----------- Helpers ------------------
+
+ static Object convert(String object, Class<?> paramType) {
+ Object result = null;
+ if ("java.lang.String".equals(paramType.getName())) {
+ result = object;
+ } else if ("java.lang.Long".equals(paramType.getName())
+ || "long".equals(paramType.getName())) {
+ try {
+ result = Long.parseLong(object);
+ } catch (NumberFormatException ex) {
+ }
+ // Try a setFoo ( boolean )
+ } else if ("java.lang.Integer".equals(paramType.getName())
+ || "int".equals(paramType.getName())) {
+ try {
+ result = new Integer(object);
+ } catch (NumberFormatException ex) {
+ }
+ // Try a setFoo ( boolean )
+ } else if ("java.lang.Boolean".equals(paramType.getName())
+ || "boolean".equals(paramType.getName())) {
+ result = new Boolean(object);
+ } else {
+ log.info("Unknown type " + paramType.getName());
+ }
+ if (result == null) {
+ throw new IllegalArgumentException("Can't convert argument: "
+ + object + " to " + paramType );
+ }
+ return result;
+ }
+
+ /**
+ * Converts the first character of the given String into lower-case.
+ *
+ * @param name
+ * The string to convert
+ * @return String
+ */
+ static String unCapitalize(String name) {
+ if (name == null || name.length() == 0) {
+ return name;
+ }
+ char chars[] = name.toCharArray();
+ chars[0] = Character.toLowerCase(chars[0]);
+ return new String(chars);
+ }
+
+ /**
+ * Check if this class is one of the supported types. If the class is
+ * supported, returns true. Otherwise, returns false.
+ *
+ * @param ret
+ * The class to check
+ * @return boolean True if class is supported
+ */
+ static boolean supportedType(Class<?> ret) {
+ for (int i = 0; i < supportedTypes.length; i++) {
+ if (ret == supportedTypes[i]) {
+ return true;
+ }
+ }
+ if (isBeanCompatible(ret)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Check if this class conforms to JavaBeans specifications. If the class is
+ * conformant, returns true.
+ *
+ * @param javaType
+ * The class to check
+ * @return boolean True if the class is compatible.
+ */
+ static boolean isBeanCompatible(Class<?> javaType) {
+ // Must be a non-primitive and non array
+ if (javaType.isArray() || javaType.isPrimitive()) {
+ return false;
+ }
+
+ // Anything in the java or javax package that
+ // does not have a defined mapping is excluded.
+ if (javaType.getName().startsWith("java.")
+ || javaType.getName().startsWith("javax.")) {
+ return false;
+ }
+
+ try {
+ javaType.getConstructor(new Class[] {});
+ } catch (java.lang.NoSuchMethodException e) {
+ return false;
+ }
+
+ // Make sure superclass is compatible
+ Class<?> superClass = javaType.getSuperclass();
+ if (superClass != null && superClass != java.lang.Object.class
+ && superClass != java.lang.Exception.class
+ && superClass != java.lang.Throwable.class) {
+ if (!isBeanCompatible(superClass)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Reverse of Introspector.decapitalize
+ */
+ 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);
+ }
+
+
+}
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
+import java.util.logging.Level;
import java.util.logging.Logger;
+import org.apache.tomcat.integration.DynamicObject;
import org.apache.tomcat.integration.ObjectManager;
-import org.apache.tomcat.util.IntrospectionUtils;
/**
- * This is a very small 'dependency injection'/registry poor-man substitute,
+ * This is a very small 'dependency injection'/registry poor-man substitute,
* based on old tomcat IntrospectionUtils ( which is based on ant ).
* Alternative would be to just pick one of spring/guice/etc and use it.
* This class is a bit smaller and should be enough for simple use.
- *
- * How it works:
+ *
+ * How it works:
* - when bound, simple properties are injected in the objects using
* the old IntrospectionUtils, same as in original Tomcat server.xml
- *
+ *
* - object creation using class name - properties injected as well.
* Similar with how server.xml or ant works.
- *
- * - it is based on a big Properties file, with command line arguments
+ *
+ * - it is based on a big Properties file, with command line arguments
* merged in.
- *
+ *
* Tomcat doesn't require any of the features - they are just used to
- * allow configuration in 'default' mode, when no other framework is
- * used.
- *
+ * allow configuration in 'default' mode, when no other framework is
+ * used.
+ *
* See the Spring example for an alternative. I believe most POJO frameworks
- * can be supported.
- *
+ * can be supported.
+ *
* @author Costin Manolache
*/
public class SimpleObjectManager extends ObjectManager {
- static Logger log = Logger.getLogger(SimpleObjectManager.class.getName());
+ public static final String ARGS = "Main.args";
+ static Logger log = Logger.getLogger(SimpleObjectManager.class.getName());
+
protected Properties props = new Properties();
protected Map<String, Object> objects = new HashMap();
- ObjectManager om;
-
+
public SimpleObjectManager() {
// Register PropertiesSpi
}
public SimpleObjectManager(String[] args) {
this();
- bind("Main.args", args);
+ bind(ARGS, args);
}
-
+
public void loadResource(String res) {
InputStream in = this.getClass().getClassLoader()
.getResourceAsStream(res);
- load(in);
+ if (in != null) {
+ load(in);
+ }
}
-
+
public void register(ObjectManager om) {
- this.om = om;
super.register(om);
}
-
+
public ObjectManager getObjectManager() {
- return om;
+ return this;
}
public void load(InputStream is) {
try {
props.load(is);
+ processIncludes();
} catch (IOException e) {
throw new RuntimeException("Error loading default config");
}
}
-
+
+ // resolve included "config". Very basic, just one, etc.
+ private void processIncludes() throws IOException {
+ String value = props.getProperty("config");
+ if (value == null) {
+ value = props.getProperty("include");
+ if (value != null) {
+ props.remove("include");
+ }
+ } else {
+ // avoid loop
+ props.remove("config");
+ }
+ if (value == null) {
+ return;
+ }
+ if (new File(value).exists()) {
+ load(new FileInputStream(value));
+ } else {
+ loadResource(value);
+ }
+ }
+
public Properties getProperties() {
return props;
}
-
+
@Override
public void unbind(String name) {
+ // Children
+ // TODO: call @destroy
+ super.unbind(name);
}
@Override
public void bind(String name, Object o) {
//log.info("Bound: " + name + " " + o);
- if ("Main.args".equals(name)) {
+ if (ARGS.equals(name)) {
try {
processArgs((String[]) o, props);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
-
- // TODO: can I make 'inject' public - Guice seems to
+
+ // TODO: can I make 'inject' public - Guice seems to
// support this.
inject(name, o);
+
+ // Children
+ super.bind(name, o);
}
@Override
public Object get(String key) {
// Use same syntax as Spring props.
+ Object res = null;
String prop = props.getProperty(key + ".(class)");
if (prop != null) {
- Object res = loadClass(prop);
- inject(key, res);
- return res;
+ res = loadClass(prop);
}
- return null;
+ if (res == null) {
+ res = super.get(key);
+ }
+
+ if (res == null) {
+ // Maybe it's just a class name
+ res = loadClass(key);
+ }
+
+ if (res != null) {
+ inject(key, res);
+ }
+ return res;
+ }
+
+ public String getProperty(String key) {
+ String prop = props.getProperty(key);
+ if (prop != null) {
+ return prop;
+ }
+ return super.getProperty(key);
}
private void inject(String name, Object o) {
String pref = name + ".";
int prefLen = pref.length();
- for (String k: props.stringPropertyNames()) {
+ DynamicObject dyno = new DynamicObject(o.getClass());
+ dyno.setAttribute(o, "ObjectManager", this);
+
+ for (Object kObj: props.keySet()) {
+ if (!(kObj instanceof String)) {continue;}
+ String k = (String) kObj;
if (k.startsWith(pref)) {
if (k.endsWith(")")) {
- continue; // special
+ continue; // special
}
String value = props.getProperty(k);
- value = IntrospectionUtils.replaceProperties(value,
+ value = AntProperties.replaceProperties(value,
props, null);
String p = k.substring(prefLen);
int idx = p.indexOf(".");
// ignore suffix - indexed properties
p = p.substring(0, idx);
}
- IntrospectionUtils.setProperty(o, p, value);
- log.info("Setting: " + name + " " + k + " " + value);
+ dyno.setProperty(o, p, value);
+ if (log.isLoggable(Level.FINE)) {
+ log.info("Setting: " + name + " " + k + " " + value);
+ }
}
}
+
+
// We could do cooler things - inject objects, etc.
}
-
+
+
private Object loadClass(String className) {
try {
Class c = Class.forName(className);
+ if (c.isInterface()) {
+ return null;
+ }
Object ext = c.newInstance();
return ext;
} catch (Throwable e) {
- e.printStackTrace();
return null;
- }
+ }
}
-
+
/**
* Populate properties based on CLI:
* -key value
* --key=value
- *
+ *
* --config=FILE - load a properties file
- *
+ *
* @param args
* @param p
* @param meta
* @return everything after the first non arg not starting with '-'
- * @throws IOException
+ * @throws IOException
*/
- public String[] processArgs(String[] args, Properties props)
+ public String[] processArgs(String[] args, Properties props)
throws IOException {
for (int i = 0; i < args.length; i++) {
} else {
String [] res = new String[args.length - i];
System.arraycopy(args, i, res, 0, res.length);
+ processIncludes();
return res;
}
-
- String name = arg;
+
+ String name = arg;
int eq = arg.indexOf("=");
String value = null;
if (eq > 0) {
value = args[i];
}
- if ("config".equals(arg)) {
- if (new File(value).exists()) {
- load(new FileInputStream(value));
- } else {
- loadResource(value);
- }
- } else {
- props.put(name, value);
- }
+ props.put(name, value);
+
}
+
+ processIncludes();
return new String[] {};
- }
+ }
}