// ======================================================================== // $Id: XmlConfiguration.java,v 1.28 2005/08/13 08:12:14 gregwilkins Exp $ // Copyright 1999-2004 Mort Bay Consulting Pty. Ltd. // ------------------------------------------------------------------------ // 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.lightbody.bmp.proxy.jetty.xml; import net.lightbody.bmp.proxy.jetty.log.LogFactory; import net.lightbody.bmp.proxy.jetty.util.*; import org.apache.commons.logging.Log; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import java.io.IOException; import java.io.InputStream; import java.io.StringReader; import java.lang.reflect.*; import java.net.InetAddress; import java.net.MalformedURLException; import java.net.URL; import java.net.UnknownHostException; import java.util.HashMap; import java.util.Map; /* ------------------------------------------------------------ */ /** * Configure Objects from XML. This class reads an XML file conforming to the configure.dtd DTD and * uses it to configure and object by calling set, put or other methods on the object. * * @version $Id: XmlConfiguration.java,v 1.28 2005/08/13 08:12:14 gregwilkins Exp $ * @author Greg Wilkins (gregw) */ public class XmlConfiguration { private static Log log = LogFactory.getLog(XmlConfiguration.class); private static Class[] __primitives = { Boolean.TYPE, Character.TYPE, Byte.TYPE, Short.TYPE, Integer.TYPE, Long.TYPE, Float.TYPE, Double.TYPE, Void.TYPE}; private static Class[] __primitiveHolders = { Boolean.class, Character.class, Byte.class, Short.class, Integer.class, Long.class, Float.class, Double.class, Void.class}; /* ------------------------------------------------------------ */ private static XmlParser __parser; private XmlParser.Node _config; private Map _idMap = new HashMap(); /* ------------------------------------------------------------ */ private synchronized static void initParser() throws IOException { if (__parser != null) return; __parser = new XmlParser(); URL config13URL = XmlConfiguration.class.getClassLoader().getResource( "net/lightbody/bmp/proxy/jetty/xml/configure_1_3.dtd"); __parser.redirectEntity("configure.dtd", config13URL); __parser.redirectEntity("configure_1_3.dtd", config13URL); __parser.redirectEntity("http://jetty.jetty.org/configure_1_3.dtd", config13URL); __parser.redirectEntity("http://jetty.jetty.org/configure.dtd", config13URL); __parser.redirectEntity("-//Mort Bay Consulting//DTD Configure 1.3//EN", config13URL); __parser.redirectEntity("-//Mort Bay Consulting//DTD Configure//EN", config13URL); URL config12URL = XmlConfiguration.class.getClassLoader().getResource( "net/lightbody/bmp/proxy/jetty/xml/configure_1_2.dtd"); __parser.redirectEntity("configure_1_2.dtd", config12URL); __parser.redirectEntity("http://jetty.jetty.org/configure_1_2.dtd", config12URL); __parser.redirectEntity("-//Mort Bay Consulting//DTD Configure 1.2//EN", config12URL); URL config11URL = XmlConfiguration.class.getClassLoader().getResource( "net/lightbody/bmp/proxy/jetty/xml/configure_1_1.dtd"); __parser.redirectEntity("configure_1_1.dtd", config11URL); __parser.redirectEntity("http://jetty.jetty.org/configure_1_1.dtd", config11URL); __parser.redirectEntity("-//Mort Bay Consulting//DTD Configure 1.1//EN", config11URL); URL config10URL = XmlConfiguration.class.getClassLoader().getResource( "net/lightbody/bmp/proxy/jetty/xml/configure_1_0.dtd"); __parser.redirectEntity("configure_1_0.dtd", config10URL); __parser.redirectEntity("http://jetty.jetty.org/configure_1_0.dtd", config10URL); __parser.redirectEntity("-//Mort Bay Consulting//DTD Configure 1.0//EN", config10URL); } /* ------------------------------------------------------------ */ /** * Constructor. Reads the XML configuration file. * * @param configuration */ public XmlConfiguration(URL configuration) throws SAXException, IOException { initParser(); synchronized (__parser) { _config = __parser.parse(configuration.toString()); } } /* ------------------------------------------------------------ */ /** * Constructor. * * @param configuration String of XML configuration commands excluding the normal XML preamble. * The String should start with a " <Configure ...." element. * @exception SAXException * @exception IOException */ public XmlConfiguration(String configuration) throws SAXException, IOException { initParser(); configuration = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n<!DOCTYPE Configure PUBLIC \"-//Mort Bay Consulting//DTD Configure 1.2//EN\" \"http://jetty.jetty.org/configure_1_2.dtd\">" + configuration; InputSource source = new InputSource(new StringReader(configuration)); synchronized (__parser) { _config = __parser.parse(source); } } /* ------------------------------------------------------------ */ /** * Constructor. * * @param configuration An input stream containing a complete e.g. configuration file * @exception SAXException * @exception IOException */ public XmlConfiguration(InputStream configuration) throws SAXException, IOException { initParser(); InputSource source = new InputSource(configuration); synchronized (__parser) { _config = __parser.parse(source); } } /* ------------------------------------------------------------ */ /** * Configure an object. If the object is of the approprate class, the XML configuration script * is applied to the object. * * @param obj The object to be configured. * @exception ClassNotFoundException * @exception NoSuchMethodException * @exception InvocationTargetException * @throws IllegalAccessException * @throws InstantiationException */ public void configure(Object obj) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { //Check the class of the object Class oClass = nodeClass(_config); if (oClass != null) { if (obj != null && !oClass.isInstance(obj)) throw new IllegalArgumentException("Object is not of type " + oClass); if (obj == null) obj = oClass.newInstance(); } configure(obj, _config, 0); } /* ------------------------------------------------------------ */ /** * Create a new object and configure it. A new object is created and configured. * * @return The newly created configured object. * @exception ClassNotFoundException * @exception NoSuchMethodException * @exception InvocationTargetException * @exception InstantiationException * @exception IllegalAccessException */ public Object newInstance() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { Class oClass = nodeClass(_config); Object obj = null; if (oClass != null) obj = oClass.newInstance(); configure(obj, _config, 0); return obj; } /* ------------------------------------------------------------ */ private Class nodeClass(XmlParser.Node node) throws ClassNotFoundException { String className = node.getAttribute("class"); if (className == null) return null; return Loader.loadClass(XmlConfiguration.class, className); } /* ------------------------------------------------------------ */ /* * Recursive configuration step. This method applies the remaining Set, Put and Call elements to * the current object. @param obj @param cfg @param i @exception ClassNotFoundException * @exception NoSuchMethodException @exception InvocationTargetException */ private void configure(Object obj, XmlParser.Node cfg, int i) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { XmlParser.Node node = null; try { for (; i < cfg.size(); i++) { Object o = cfg.get(i); if (o instanceof String) continue; node = (XmlParser.Node) o; String tag = node.getTag(); if ("Set".equals(tag)) set(obj, node); else if ("Put".equals(tag)) put(obj, node); else if ("Call".equals(tag)) call(obj, node); else if ("Get".equals(tag)) get(obj, node); else if ("New".equals(tag)) newObj(obj, node); else if ("Ref".equals(tag)) refObj(obj, node); else throw new IllegalStateException("Unknown tag: " + tag); } } catch(InvocationTargetException e) { log.warn("Exception at "+node.toString(),e.getTargetException()); throw e; } catch (Error e) { log.debug(node); throw e; } catch (Exception e) { log.debug(node); if (e instanceof NoSuchMethodException) throw (NoSuchMethodException) e; if (e instanceof InvocationTargetException) throw (InvocationTargetException) e; if (e instanceof IllegalAccessException) throw (IllegalAccessException) e; if (e instanceof RuntimeException) throw (RuntimeException) e; } } /* ------------------------------------------------------------ */ /* * Call a set method. This method makes a best effort to find a matching set method. The type of * the value is used to find a suitable set method by 1. Trying for a trivial type match. 2. * Looking for a native type match. 3. Trying all correctly named methods for an auto * conversion. 4. Attempting to construct a suitable value from original value. @param obj * @param node */ private void set(Object obj, XmlParser.Node node) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { String attr = node.getAttribute("name"); String name = "set" + attr.substring(0, 1).toUpperCase() + attr.substring(1); Object value = value(obj, node); Object[] arg = { value}; Class oClass = nodeClass(node); if (oClass != null) obj = null; else oClass = obj.getClass(); Class[] vClass = { Object.class}; if (value != null) vClass[0] = value.getClass(); if (log.isDebugEnabled()) log.debug(obj + "." + name + "(" + vClass[0] + " " + value + ")"); // Try for trivial match try { Method set = oClass.getMethod(name, vClass); set.invoke(obj, arg); return; } catch (IllegalArgumentException e) { LogSupport.ignore(log, e); } catch (IllegalAccessException e) { LogSupport.ignore(log, e); } catch (NoSuchMethodException e) { LogSupport.ignore(log, e); } // Try for native match try { Field type = vClass[0].getField("TYPE"); vClass[0] = (Class) type.get(null); Method set = oClass.getMethod(name, vClass); set.invoke(obj, arg); return; } catch (NoSuchFieldException e) { LogSupport.ignore(log, e); } catch (IllegalArgumentException e) { LogSupport.ignore(log, e); } catch (IllegalAccessException e) { LogSupport.ignore(log, e); } catch (NoSuchMethodException e) { LogSupport.ignore(log, e); } // Try a field try { Field field = oClass.getField(attr); if (Modifier.isPublic(field.getModifiers())) { field.set(obj, value); return; } } catch (NoSuchFieldException e) { LogSupport.ignore(log, e); } // Search for a match by trying all the set methods Method[] sets = oClass.getMethods(); Method set = null; for (int s = 0; sets != null && s < sets.length; s++) { if (name.equals(sets[s].getName()) && sets[s].getParameterTypes().length == 1) { // lets try it try { set = sets[s]; sets[s].invoke(obj, arg); return; } catch (IllegalArgumentException e) { LogSupport.ignore(log, e); } catch (IllegalAccessException e) { LogSupport.ignore(log, e); } } } // Try converting the arg to the last set found. if (set != null) { try { Class sClass = set.getParameterTypes()[0]; if (sClass.isPrimitive()) { for (int t = 0; t < __primitives.length; t++) { if (sClass.equals(__primitives[t])) { sClass = __primitiveHolders[t]; break; } } } Constructor cons = sClass.getConstructor(vClass); arg[0] = cons.newInstance(arg); set.invoke(obj, arg); return; } catch (NoSuchMethodException e) { LogSupport.ignore(log, e); } catch (IllegalAccessException e) { LogSupport.ignore(log, e); } catch (InstantiationException e) { LogSupport.ignore(log, e); } } // No Joy throw new NoSuchMethodException(oClass + "." + name + "(" + vClass[0] + ")"); } /* ------------------------------------------------------------ */ /* * Call a put method. * * @param obj @param node */ private void put(Object obj, XmlParser.Node node) throws NoSuchMethodException, ClassNotFoundException, InvocationTargetException, IllegalAccessException { if (!(obj instanceof Map)) throw new IllegalArgumentException("Object for put is not a Map: " + obj); Map map = (Map) obj; String name = node.getAttribute("name"); Object value = value(obj, node); map.put(name, value); if (log.isDebugEnabled()) log.debug(obj + ".put(" + name + "+" + value + ")"); } /* ------------------------------------------------------------ */ /* * Call a get method. Any object returned from the call is passed to the configure method to * consume the remaining elements. @param obj @param node @return @exception * NoSuchMethodException @exception ClassNotFoundException @exception InvocationTargetException */ private Object get(Object obj, XmlParser.Node node) throws NoSuchMethodException, ClassNotFoundException, InvocationTargetException, IllegalAccessException { Class oClass = nodeClass(node); if (oClass != null) obj = null; else oClass = obj.getClass(); String name = node.getAttribute("name"); String id = node.getAttribute("id"); if (log.isDebugEnabled()) log.debug("get " + name); try { // try calling a getXxx method. Method method = oClass.getMethod("get" + name.substring(0, 1).toUpperCase() + name.substring(1), (java.lang.Class[]) null); obj = method.invoke(obj, (java.lang.Object[]) null); configure(obj, node, 0); } catch (NoSuchMethodException nsme) { try { Field field = oClass.getField(name); obj = field.get(obj); configure(obj, node, 0); } catch (NoSuchFieldException nsfe) { throw nsme; } } if (id != null) _idMap.put(id, obj); return obj; } /* ------------------------------------------------------------ */ /* * Call a method. A method is selected by trying all methods with matching names and number of * arguments. Any object returned from the call is passed to the configure method to consume the * remaining elements. Note that if this is a static call we consider only methods declared * directly in the given class. i.e. we ignore any static methods in superclasses. @param obj * @param node @return @exception NoSuchMethodException @exception ClassNotFoundException * @exception InvocationTargetException */ private Object call(Object obj, XmlParser.Node node) throws NoSuchMethodException, ClassNotFoundException, InvocationTargetException, IllegalAccessException { String id = node.getAttribute("id"); Class oClass = nodeClass(node); if (oClass != null) obj = null; else if (obj != null) oClass = obj.getClass(); if (oClass == null) throw new IllegalArgumentException(node.toString()); int size = 0; int argi = node.size(); for (int i = 0; i < node.size(); i++) { Object o = node.get(i); if (o instanceof String) continue; if (!((XmlParser.Node) o).getTag().equals("Arg")) { argi = i; break; } size++; } Object[] arg = new Object[size]; for (int i = 0, j = 0; j < size; i++) { Object o = node.get(i); if (o instanceof String) continue; arg[j++] = value(obj, (XmlParser.Node) o); } String method = node.getAttribute("name"); if (log.isDebugEnabled()) log.debug("call " + method); // Lets just try all methods for now Method[] methods = oClass.getMethods(); for (int c = 0; methods != null && c < methods.length; c++) { if (!methods[c].getName().equals(method)) continue; if (methods[c].getParameterTypes().length != size) continue; if (Modifier.isStatic(methods[c].getModifiers()) != (obj == null)) continue; if ((obj == null) && methods[c].getDeclaringClass() != oClass) continue; Object n = null; boolean called = false; try { n = methods[c].invoke(obj, arg); called = true; } catch (IllegalAccessException e) { LogSupport.ignore(log, e); } catch (IllegalArgumentException e) { LogSupport.ignore(log, e); } if (called) { if (id != null) _idMap.put(id, n); configure(n, node, argi); return n; } } throw new IllegalStateException("No Method: " + node + " on " + oClass); } /* ------------------------------------------------------------ */ /* * Create a new value object. * * @param obj @param node @return @exception NoSuchMethodException @exception * ClassNotFoundException @exception InvocationTargetException */ private Object newObj(Object obj, XmlParser.Node node) throws NoSuchMethodException, ClassNotFoundException, InvocationTargetException, IllegalAccessException { Class oClass = nodeClass(node); String id = node.getAttribute("id"); int size = 0; int argi = node.size(); for (int i = 0; i < node.size(); i++) { Object o = node.get(i); if (o instanceof String) continue; if (!((XmlParser.Node) o).getTag().equals("Arg")) { argi = i; break; } size++; } Object[] arg = new Object[size]; for (int i = 0, j = 0; j < size; i++) { Object o = node.get(i); if (o instanceof String) continue; arg[j++] = value(obj, (XmlParser.Node) o); } if (log.isDebugEnabled()) log.debug("new " + oClass); // Lets just try all constructors for now Constructor[] constructors = oClass.getConstructors(); for (int c = 0; constructors != null && c < constructors.length; c++) { if (constructors[c].getParameterTypes().length != size) continue; Object n = null; boolean called = false; try { n = constructors[c].newInstance(arg); called = true; } catch (IllegalAccessException e) { LogSupport.ignore(log, e); } catch (InstantiationException e) { LogSupport.ignore(log, e); } catch (IllegalArgumentException e) { LogSupport.ignore(log, e); } if (called) { if (id != null) _idMap.put(id, n); configure(n, node, argi); return n; } } throw new IllegalStateException("No Constructor: " + node + " on " + obj); } /* ------------------------------------------------------------ */ /* * Reference an id value object. * * @param obj @param node @return @exception NoSuchMethodException @exception * ClassNotFoundException @exception InvocationTargetException */ private Object refObj(Object obj, XmlParser.Node node) throws NoSuchMethodException, ClassNotFoundException, InvocationTargetException, IllegalAccessException { String id = node.getAttribute("id"); obj = _idMap.get(id); if (obj == null) throw new IllegalStateException("No object for id=" + id); configure(obj, node, 0); return obj; } /* ------------------------------------------------------------ */ /* * Create a new array object. * * @param obj @param node @return @exception NoSuchMethodException @exception * ClassNotFoundException @exception InvocationTargetException */ private Object newArray(Object obj, XmlParser.Node node) throws NoSuchMethodException, ClassNotFoundException, InvocationTargetException, IllegalAccessException { // Get the type Class aClass = java.lang.Object.class; String type = node.getAttribute("type"); String id = node.getAttribute("id"); if (type != null) { aClass = TypeUtil.fromName(type); if (aClass == null) { if ("String".equals(type)) aClass = java.lang.String.class; else if ("URL".equals(type)) aClass = java.net.URL.class; else if ("InetAddress".equals(type)) aClass = java.net.InetAddress.class; else if ("InetAddrPort".equals(type)) aClass = net.lightbody.bmp.proxy.jetty.util.InetAddrPort.class; else aClass = Loader.loadClass(XmlConfiguration.class, type); } } Object array = Array.newInstance(aClass, node.size()); if (id != null) _idMap.put(id, obj); for (int i = 0; i < node.size(); i++) { Object o = node.get(i); if (o instanceof String) continue; XmlParser.Node item = (XmlParser.Node) o; if (!item.getTag().equals("Item")) throw new IllegalStateException("Not an Item"); id = item.getAttribute("id"); Object v = value(obj, item); if (v != null) Array.set(array, i, v); if (id != null) _idMap.put(id, v); } return array; } /* ------------------------------------------------------------ */ /* * Get the value of an element. If no value type is specified, then white space is trimmed out * of the value. If it contains multiple value elements they are added as strings before being * converted to any specified type. @param node */ private Object value(Object obj, XmlParser.Node node) throws NoSuchMethodException, ClassNotFoundException, InvocationTargetException, IllegalAccessException { Object value = null; // Get the type String type = node.getAttribute("type"); // Try a ref lookup String ref = node.getAttribute("ref"); if (ref != null) { value = _idMap.get(ref); } else { // handle trivial case if (node.size() == 0) { if ("String".equals(type)) return ""; return null; } // Trim values int first = 0; int last = node.size() - 1; // Handle default trim type if (type == null || !"String".equals(type)) { // Skip leading white Object item = null; while (first <= last) { item = node.get(first); if (!(item instanceof String)) break; item = ((String) item).trim(); if (((String) item).length() > 0) break; first++; } // Skip trailing white while (first < last) { item = node.get(last); if (!(item instanceof String)) break; item = ((String) item).trim(); if (((String) item).length() > 0) break; last--; } // All white, so return null if (first > last) return null; } if (first == last) // Single Item value value = itemValue(obj, node.get(first)); else { // Get the multiple items as a single string StringBuffer buf = new StringBuffer(); synchronized (buf) { for (int i = first; i <= last; i++) { Object item = node.get(i); buf.append(itemValue(obj, item)); } value = buf.toString(); } } } // Untyped or unknown if (value == null) { if ("String".equals(type)) return ""; return null; } // Try to type the object if (type == null) { if (value != null && value instanceof String) return ((String) value).trim(); return value; } if ("String".equals(type) || "java.lang.String".equals(type)) return value.toString(); Class pClass = TypeUtil.fromName(type); if (pClass != null) return TypeUtil.valueOf(pClass, value.toString()); if ("URL".equals(type) || "java.net.URL".equals(type)) { if (value instanceof URL) return value; try { return new URL(value.toString()); } catch (MalformedURLException e) { throw new InvocationTargetException(e); } } if ("InetAddress".equals(type) || "java.net.InetAddress".equals(type)) { if (value instanceof InetAddress) return value; try { return InetAddress.getByName(value.toString()); } catch (UnknownHostException e) { throw new InvocationTargetException(e); } } if ("InetAddrPort".equals(type) || "net.lightbody.bmp.proxy.jetty.util.InetAddrPort".equals(type)) { if (value instanceof InetAddrPort) return value; try { return new InetAddrPort(value.toString()); } catch (UnknownHostException e) { throw new InvocationTargetException(e); } } throw new IllegalStateException("Unknown type " + type); } /* ------------------------------------------------------------ */ /* * Get the value of a single element. @param obj @param item @return @exception * ClassNotFoundException */ private Object itemValue(Object obj, Object item) throws NoSuchMethodException, ClassNotFoundException, InvocationTargetException, IllegalAccessException { // String value if (item instanceof String) return item; XmlParser.Node node = (XmlParser.Node) item; String tag = node.getTag(); if ("Call".equals(tag)) return call(obj, node); if ("Get".equals(tag)) return get(obj, node); if ("New".equals(tag)) return newObj(obj, node); if ("Ref".equals(tag)) return refObj(obj, node); if ("Array".equals(tag)) return newArray(obj, node); if ("SystemProperty".equals(tag)) { String name = node.getAttribute("name"); String defaultValue = node.getAttribute("default"); return System.getProperty(name, defaultValue); } log.warn("Unknown value tag: " + node, new Throwable()); return null; } /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ public static void main(String[] arg) { try { for (int i = 0; i < arg.length; i++) new XmlConfiguration(Resource.newResource(arg[i]).getURL()).newInstance(); } catch (Exception e) { log.warn(LogSupport.EXCEPTION, e); } } }