Author: costin Date: Thu Nov 26 06:50:10 2009 New Revision: 884419 URL: http://svn.apache.org/viewvc?rev=884419&view=rev Log: Few change to the ObjectManager ( intended for integration with existing frameworks ). The 'sample/if no other framework around' SimpleObject manager no longer depends on IntrospectionUtils, refactored it ( and parts of modeler ) to DynamicObject.
Added: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/integration/DynamicObject.java (with props) tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/integration/simple/AntProperties.java (with props) Removed: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/integration/simple/LocalFilesystem.java Modified: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/integration/ObjectManager.java tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/integration/simple/Main.java tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/integration/simple/SimpleObjectManager.java Added: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/integration/DynamicObject.java URL: http://svn.apache.org/viewvc/tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/integration/DynamicObject.java?rev=884419&view=auto ============================================================================== --- tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/integration/DynamicObject.java (added) +++ tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/integration/DynamicObject.java Thu Nov 26 06:50:10 2009 @@ -0,0 +1,393 @@ +/* + */ +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); + } + + +} Propchange: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/integration/DynamicObject.java ------------------------------------------------------------------------------ svn:eol-style = native Modified: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/integration/ObjectManager.java URL: http://svn.apache.org/viewvc/tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/integration/ObjectManager.java?rev=884419&r1=884418&r2=884419&view=diff ============================================================================== --- tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/integration/ObjectManager.java (original) +++ tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/integration/ObjectManager.java Thu Nov 26 06:50:10 2009 @@ -79,6 +79,19 @@ } /** + * Create or get a new object with the given name. + */ + public String getProperty(String key) { + for (ObjectManager p : children) { + String o = p.getProperty(key); + if (o != null) { + return o; + } + } + return null; + } + + /** * Helper for typed get. */ public Object get(Class c) { @@ -93,6 +106,6 @@ new ArrayList<ObjectManager>(); public void register(ObjectManager om) { - om.children.add(this); + children.add(om); } } Added: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/integration/simple/AntProperties.java URL: http://svn.apache.org/viewvc/tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/integration/simple/AntProperties.java?rev=884419&view=auto ============================================================================== --- tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/integration/simple/AntProperties.java (added) +++ tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/integration/simple/AntProperties.java Thu Nov 26 06:50:10 2009 @@ -0,0 +1,75 @@ +/* + */ +package org.apache.tomcat.integration.simple; + +import java.util.Hashtable; + +/** + * Extracted from IntrospectionHelper - a simple utility class to + * do ant style ${property} replacements on a string, using a map + * holding properties. Also allows a hook for dynamic, on-demand + * properties. + * + * @author Costin Manolache + */ +public class AntProperties { + public static interface PropertySource { + public String getProperty(String key); + } + + /** + * Replace ${NAME} with the property value + */ + public static String replaceProperties(String value, + Hashtable<Object,Object> staticProp, PropertySource dynamicProp[]) { + if (value.indexOf("$") < 0) { + return value; + } + StringBuffer sb = new StringBuffer(); + int prev = 0; + // assert value!=nil + int pos; + while ((pos = value.indexOf("$", prev)) >= 0) { + if (pos > 0) { + sb.append(value.substring(prev, pos)); + } + if (pos == (value.length() - 1)) { + sb.append('$'); + prev = pos + 1; + } else if (value.charAt(pos + 1) != '{') { + sb.append('$'); + prev = pos + 1; // XXX + } else { + int endName = value.indexOf('}', pos); + if (endName < 0) { + sb.append(value.substring(pos)); + prev = value.length(); + continue; + } + String n = value.substring(pos + 2, endName); + String v = null; + if (staticProp != null) { + v = (String) staticProp.get(n); + } + if (v == null && dynamicProp != null) { + for (int i = 0; i < dynamicProp.length; i++) { + v = dynamicProp[i].getProperty(n); + if (v != null) { + break; + } + } + } + if (v == null) + v = "${" + n + "}"; + + sb.append(v); + prev = endName + 1; + } + } + if (prev < value.length()) + sb.append(value.substring(prev)); + return sb.toString(); + } + + +} Propchange: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/integration/simple/AntProperties.java ------------------------------------------------------------------------------ svn:eol-style = native Modified: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/integration/simple/Main.java URL: http://svn.apache.org/viewvc/tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/integration/simple/Main.java?rev=884419&r1=884418&r2=884419&view=diff ============================================================================== --- tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/integration/simple/Main.java (original) +++ tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/integration/simple/Main.java Thu Nov 26 06:50:10 2009 @@ -17,7 +17,6 @@ package org.apache.tomcat.integration.simple; -import org.apache.tomcat.integration.ObjectManager; /** * Replacement for tomcat-lite specific Main, using the simple @@ -28,17 +27,34 @@ * @author Costin Manolache */ public class Main { + static boolean running = true; + static Object lock = new Object(); + + public static void stop() { + running = false; + synchronized (lock) { + lock.notify(); + } + } + + public static void waitStop() { + while (running) { + try { + synchronized (lock) { + lock.wait(); + } + } catch (InterruptedException e) { + } + } + } public static void main(String args[]) - throws Exception { - SimpleObjectManager om = new SimpleObjectManager(); - - // Will process CLI. - // 'config' will load a config file. - om.bind("Main.args", args); + throws Exception { + // '--config' will load a config file. + SimpleObjectManager om = new SimpleObjectManager(args); - Runnable main = (Runnable) om.get("Main"); - if (main == null) { + String run = (String) om.getProperty("RUN"); + if (run == null) { // TODO: look for a pre-defined name in local dir, resource, // manifest System.err.println("Using default tomcat-lite configuration"); @@ -50,16 +66,16 @@ String cfgFile = "org/apache/tomcat/lite/config.properties"; om.loadResource(cfgFile); - main = (Runnable) om.get("Main"); + run = (String) om.getProperty("RUN"); } - // add JMX support - ObjectManager jmx = (ObjectManager) om.get("JMX"); - if (jmx != null) { - jmx.register(om); + String[] runNames = run.split(","); + for (String name: runNames) { + Object main = om.get(name); + if (main instanceof Runnable) { + ((Runnable) main).run(); + } } - - main.run(); - + } } Modified: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/integration/simple/SimpleObjectManager.java URL: http://svn.apache.org/viewvc/tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/integration/simple/SimpleObjectManager.java?rev=884419&r1=884418&r2=884419&view=diff ============================================================================== --- tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/integration/simple/SimpleObjectManager.java (original) +++ tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/integration/simple/SimpleObjectManager.java Thu Nov 26 06:50:10 2009 @@ -24,111 +24,162 @@ 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) { @@ -136,13 +187,18 @@ 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("."); @@ -150,38 +206,45 @@ // 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++) { @@ -193,10 +256,11 @@ } 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) { @@ -210,16 +274,11 @@ 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[] {}; - } + } } --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org