View Javadoc

    1   // ========================================================================
    2   // Copyright 2004-2005 Mort Bay Consulting Pty. Ltd.
    3   // ------------------------------------------------------------------------
    4   // Licensed under the Apache License, Version 2.0 (the "License");
    5   // you may not use this file except in compliance with the License.
    6   // You may obtain a copy of the License at
    7   // http://www.apache.org/licenses/LICENSE-2.0
    8   // Unless required by applicable law or agreed to in writing, software
    9   // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  // ========================================================================
    14
    15  package org.mortbay.xml;
    16
    17  import java.io.IOException;
    18  import java.io.InputStream;
    19  import java.io.StringReader;
    20  import java.lang.reflect.Constructor;
    21  import java.lang.reflect.Field;
    22  import java.lang.reflect.InvocationTargetException;
    23  import java.lang.reflect.Method;
    24  import java.lang.reflect.Modifier;
    25  import java.net.InetAddress;
    26  import java.net.MalformedURLException;
    27  import java.net.URL;
    28  import java.net.UnknownHostException;
    29  import java.util.HashMap;
    30  import java.util.Iterator;
    31  import java.util.Map;
    32  import java.util.Properties;
    33
    34  import org.mortbay.component.LifeCycle;
    35  import org.mortbay.log.Log;
    36  import org.mortbay.resource.Resource;
    37  import org.mortbay.util.LazyList;
    38  import org.mortbay.util.Loader;
    39  import org.mortbay.util.TypeUtil;
    40  import org.xml.sax.InputSource;
    41  import org.xml.sax.SAXException;
    42
    43  /* ------------------------------------------------------------ */
    44  /**
    45   * Configure Objects from XML. This class reads an XML file conforming to the configure.dtd DTD and
    46   * uses it to configure and object by calling set, put or other methods on the object.
    47   *
    48   * @author Greg Wilkins (gregw)
    49   */
    50  public class XmlConfiguration
    51  {
    52
    53      private static Class[] __primitives = { Boolean.TYPE, Character.TYPE, Byte.TYPE, Short.TYPE,
    54              Integer.TYPE, Long.TYPE, Float.TYPE, Double.TYPE, Void.TYPE};
    55
    56      private static Class[] __primitiveHolders = { Boolean.class, Character.class, Byte.class,
    57              Short.class, Integer.class, Long.class, Float.class, Double.class, Void.class};
    58      private static final Integer ZERO=new Integer(0);
    59
    60      /* ------------------------------------------------------------ */
    61      private static XmlParser __parser;
    62      private XmlParser.Node _config;
    63      private Map _idMap = new HashMap();
    64      private Map _propertyMap = new HashMap();
    65
    66      /* ------------------------------------------------------------ */
    67      private synchronized static void initParser() throws IOException
    68      {
    69          if (__parser != null) return;
    70
    71          __parser = new XmlParser();
    72          try
    73          {
    74              URL configURL = Loader.getResource(XmlConfiguration.class, "org/mortbay/xml/configure_6_0.dtd", true);
    75              __parser.redirectEntity("configure.dtd", configURL);
    76              __parser.redirectEntity("configure_1_3.dtd", configURL);
    77              __parser.redirectEntity("http://jetty.mortbay.org/configure.dtd", configURL);
    78              __parser.redirectEntity("-//Mort Bay Consulting//DTD Configure//EN", configURL);
    79              __parser.redirectEntity("http://jetty.mortbay.org/configure_1_3.dtd", configURL);
    80              __parser.redirectEntity("-//Mort Bay Consulting//DTD Configure 1.3//EN", configURL);
    81              __parser.redirectEntity("configure_1_2.dtd", configURL);
    82              __parser.redirectEntity("http://jetty.mortbay.org/configure_1_2.dtd", configURL);
    83              __parser.redirectEntity("-//Mort Bay Consulting//DTD Configure 1.2//EN", configURL);
    84              __parser.redirectEntity("configure_1_1.dtd", configURL);
    85              __parser.redirectEntity("http://jetty.mortbay.org/configure_1_1.dtd", configURL);
    86              __parser.redirectEntity("-//Mort Bay Consulting//DTD Configure 1.1//EN", configURL);
    87              __parser.redirectEntity("configure_1_0.dtd", configURL);
    88              __parser.redirectEntity("http://jetty.mortbay.org/configure_1_0.dtd", configURL);
    89              __parser.redirectEntity("-//Mort Bay Consulting//DTD Configure 1.0//EN", configURL);
    90          }
    91          catch (ClassNotFoundException e)
    92          {
    93              Log.warn(e.toString());
    94              Log.debug(e);
    95          }
    96      }
    97
    98      /* ------------------------------------------------------------ */
    99      /**
    100      * Constructor. Reads the XML configuration file.
    101      *
    102      * @param configuration
    103      */
    104     public XmlConfiguration(URL configuration) throws SAXException, IOException
    105     {
    106         initParser();
    107         synchronized (__parser)
    108         {
    109             _config = __parser.parse(configuration.toString());
    110         }
    111     }
    112
    113     /* ------------------------------------------------------------ */
    114     /**
    115      * Constructor.
    116      *
    117      * @param configuration String of XML configuration commands excluding the normal XML preamble.
    118      *            The String should start with a " <Configure ...." element.
    119      * @exception SAXException
    120      * @exception IOException
    121      */
    122     public XmlConfiguration(String configuration) throws SAXException, IOException
    123     {
    124         initParser();
    125         configuration = "<?xml version=\"1.0\"  encoding=\"ISO-8859-1\"?>\n<!DOCTYPE Configure PUBLIC \"-//Mort Bay Consulting//DTD Configure 1.2//EN\" \"http://jetty.mortbay.org/configure_1_2.dtd\">"
    126                 + configuration;
    127         InputSource source = new InputSource(new StringReader(configuration));
    128         synchronized (__parser)
    129         {
    130             _config = __parser.parse(source);
    131         }
    132     }
    133
    134     /* ------------------------------------------------------------ */
    135     /**
    136      * Constructor.
    137      *
    138      * @param configuration An input stream containing a complete e.g. configuration file
    139      * @exception SAXException
    140      * @exception IOException
    141      */
    142     public XmlConfiguration(InputStream configuration) throws SAXException, IOException
    143     {
    144         initParser();
    145         InputSource source = new InputSource(configuration);
    146         synchronized (__parser)
    147         {
    148             _config = __parser.parse(source);
    149         }
    150     }
    151
    152     /* ------------------------------------------------------------ */
    153     public Map getIdMap()
    154     {
    155         return _idMap;
    156     }
    157
    158     /* ------------------------------------------------------------ */
    159     public void setIdMap(Map map)
    160     {
    161         _idMap=map;
    162     }
    163
    164     /* ------------------------------------------------------------ */
    165     public void setProperties (Map map)
    166     {
    167         _propertyMap = map;
    168     }
    169
    170     /* ------------------------------------------------------------ */
    171     public Map getProperties ()
    172     {
    173         return _propertyMap;
    174     }
    175
    176     /* ------------------------------------------------------------ */
    177     /**
    178      * Configure an object. If the object is of the approprate class, the XML configuration script
    179      * is applied to the object.
    180      *
    181      * @param obj The object to be configured.
    182      * @exception Exception
    183      */
    184     public void configure(Object obj) throws Exception
    185     {
    186         //Check the class of the object
    187         Class oClass = nodeClass(_config);
    188         if (!oClass.isInstance(obj))
    189                 throw new IllegalArgumentException("Object is not of type " + oClass);
    190         configure(obj, _config, 0);
    191     }
    192
    193     /* ------------------------------------------------------------ */
    194     /**
    195      * Configure an object.  If the configuration has an ID, an object is looked up
    196      * by ID and it's type check.  Otherwise a new object is created.
    197      *
    198      * @return The newly created configured object.
    199      * @exception Exception
    200      */
    201     public Object configure() throws Exception
    202     {
    203         Class oClass = nodeClass(_config);
    204
    205         String id = _config.getAttribute("id");
    206         Object obj = id==null?null:_idMap.get(id);
    207
    208         if (obj==null && oClass !=null)
    209             obj = oClass.newInstance();
    210
    211         if (oClass!=null && !oClass.isInstance(obj))
    212             throw new ClassCastException(oClass.toString());
    213
    214         configure(obj, _config, 0);
    215         return obj;
    216     }
    217
    218     /* ------------------------------------------------------------ */
    219     private Class nodeClass(XmlParser.Node node) throws ClassNotFoundException
    220     {
    221         String className = node.getAttribute("class");
    222         if (className == null) return null;
    223
    224         return Loader.loadClass(XmlConfiguration.class, className,true);
    225     }
    226
    227     /* ------------------------------------------------------------ */
    228     /*
    229      * Recursive configuration step. This method applies the remaining Set, Put and Call elements to
    230      * the current object. @param obj @param cfg @param i @exception Exception
    231      */
    232     private void configure(Object obj, XmlParser.Node cfg, int i) throws Exception
    233     {
    234         String id = cfg.getAttribute("id");
    235         if (id!=null)
    236             _idMap.put(id,obj);
    237
    238         for (; i < cfg.size(); i++)
    239         {
    240             Object o = cfg.get(i);
    241             if (o instanceof String) continue;
    242             XmlParser.Node node = (XmlParser.Node) o;
    243
    244             try
    245             {
    246                 String tag = node.getTag();
    247                 if ("Set".equals(tag))
    248                     set(obj, node);
    249                 else if ("Put".equals(tag))
    250                     put(obj, node);
    251                 else if ("Call".equals(tag))
    252                     call(obj, node);
    253                 else if ("Get".equals(tag))
    254                     get(obj, node);
    255                 else if ("New".equals(tag))
    256                     newObj(obj, node);
    257                 else if ("Array".equals(tag))
    258                     newArray(obj, node);
    259                 else if ("Ref".equals(tag))
    260                     refObj(obj, node);
    261                 else if ("Property".equals(tag))
    262                     propertyObj(obj, node);
    263                 else
    264                     throw new IllegalStateException("Unknown tag: " + tag);
    265             }
    266             catch (Exception e)
    267             {
    268                 Log.warn("Config error at " + node, e.toString());
    269                 throw e;
    270             }
    271         }
    272     }
    273
    274     /* ------------------------------------------------------------ */
    275     /*
    276      * Call a set method. This method makes a best effort to find a matching set method. The type of
    277      * the value is used to find a suitable set method by 1. Trying for a trivial type match. 2.
    278      * Looking for a native type match. 3. Trying all correctly named methods for an auto
    279      * conversion. 4. Attempting to construct a suitable value from original value. @param obj
    280      * @param node
    281      */
    282     private void set(Object obj, XmlParser.Node node) throws Exception
    283     {
    284         String attr = node.getAttribute("name");
    285         String name = "set" + attr.substring(0, 1).toUpperCase() + attr.substring(1);
    286         Object value = value(obj, node);
    287         Object[] arg = { value};
    288
    289         Class oClass = nodeClass(node);
    290         if (oClass != null)
    291             obj = null;
    292         else
    293             oClass = obj.getClass();
    294
    295         Class[] vClass = { Object.class};
    296         if (value != null) vClass[0] = value.getClass();
    297
    298         if (Log.isDebugEnabled())
    299                 Log.debug("XML "+(obj!=null?obj.toString():oClass.getName()) + "." + name + "(" + value + ")");
    300
    301         // Try for trivial match
    302         try
    303         {
    304             Method set = oClass.getMethod(name, vClass);
    305             set.invoke(obj, arg);
    306             return;
    307         }
    308         catch (IllegalArgumentException e)
    309         {
    310             Log.ignore(e);
    311         }
    312         catch (IllegalAccessException e)
    313         {
    314             Log.ignore(e);
    315         }
    316         catch (NoSuchMethodException e)
    317         {
    318             Log.ignore(e);
    319         }
    320
    321         // Try for native match
    322         try
    323         {
    324             Field type = vClass[0].getField("TYPE");
    325             vClass[0] = (Class) type.get(null);
    326             Method set = oClass.getMethod(name, vClass);
    327             set.invoke(obj, arg);
    328             return;
    329         }
    330         catch (NoSuchFieldException e)
    331         {
    332             Log.ignore(e);
    333         }
    334         catch (IllegalArgumentException e)
    335         {
    336             Log.ignore(e);
    337         }
    338         catch (IllegalAccessException e)
    339         {
    340             Log.ignore(e);
    341         }
    342         catch (NoSuchMethodException e)
    343         {
    344             Log.ignore(e);
    345         }
    346
    347         // Try a field
    348         try
    349         {
    350             Field field = oClass.getField(attr);
    351             if (Modifier.isPublic(field.getModifiers()))
    352             {
    353                 field.set(obj, value);
    354                 return;
    355             }
    356         }
    357         catch (NoSuchFieldException e)
    358         {
    359             Log.ignore(e);
    360         }
    361
    362         // Search for a match by trying all the set methods
    363         Method[] sets = oClass.getMethods();
    364         Method set = null;
    365         for (int s = 0; sets != null && s < sets.length; s++)
    366         {
    367             if (name.equals(sets[s].getName()) && sets[s].getParameterTypes().length == 1)
    368             {
    369                 // lets try it
    370                 try
    371                 {
    372                     set = sets[s];
    373                     sets[s].invoke(obj, arg);
    374                     return;
    375                 }
    376                 catch (IllegalArgumentException e)
    377                 {
    378                     Log.ignore(e);
    379                 }
    380                 catch (IllegalAccessException e)
    381                 {
    382                     Log.ignore(e);
    383                 }
    384             }
    385         }
    386
    387         // Try converting the arg to the last set found.
    388         if (set != null)
    389         {
    390             try
    391             {
    392                 Class sClass = set.getParameterTypes()[0];
    393                 if (sClass.isPrimitive())
    394                 {
    395                     for (int t = 0; t < __primitives.length; t++)
    396                     {
    397                         if (sClass.equals(__primitives[t]))
    398                         {
    399                             sClass = __primitiveHolders[t];
    400                             break;
    401                         }
    402                     }
    403                 }
    404                 Constructor cons = sClass.getConstructor(vClass);
    405                 arg[0] = cons.newInstance(arg);
    406                 set.invoke(obj, arg);
    407                 return;
    408             }
    409             catch (NoSuchMethodException e)
    410             {
    411                 Log.ignore(e);
    412             }
    413             catch (IllegalAccessException e)
    414             {
    415                 Log.ignore(e);
    416             }
    417             catch (InstantiationException e)
    418             {
    419                 Log.ignore(e);
    420             }
    421         }
    422
    423         // No Joy
    424         throw new NoSuchMethodException(oClass + "." + name + "(" + vClass[0] + ")");
    425     }
    426
    427     /* ------------------------------------------------------------ */
    428     /*
    429      * Call a put method.
    430      *
    431      * @param obj @param node
    432      */
    433     private void put(Object obj, XmlParser.Node node) throws Exception
    434     {
    435         if (!(obj instanceof Map))
    436                 throw new IllegalArgumentException("Object for put is not a Map: " + obj);
    437         Map map = (Map) obj;
    438
    439         String name = node.getAttribute("name");
    440         Object value = value(obj, node);
    441         map.put(name, value);
    442         if (Log.isDebugEnabled()) Log.debug("XML "+obj + ".put(" + name + "," + value + ")");
    443     }
    444
    445     /* ------------------------------------------------------------ */
    446     /*
    447      * Call a get method. Any object returned from the call is passed to the configure method to
    448      * consume the remaining elements. @param obj @param node @return @exception Exception
    449      */
    450     private Object get(Object obj, XmlParser.Node node) throws Exception
    451     {
    452         Class oClass = nodeClass(node);
    453         if (oClass != null)
    454             obj = null;
    455         else
    456             oClass = obj.getClass();
    457
    458         String name = node.getAttribute("name");
    459         String id = node.getAttribute("id");
    460         if (Log.isDebugEnabled()) Log.debug("XML get " + name);
    461
    462         try
    463         {
    464             // try calling a getXxx method.
    465             Method method = oClass.getMethod("get" + name.substring(0, 1).toUpperCase()
    466                     + name.substring(1), (java.lang.Class[]) null);
    467             obj = method.invoke(obj, (java.lang.Object[]) null);
    468             configure(obj, node, 0);
    469         }
    470         catch (NoSuchMethodException nsme)
    471         {
    472             try
    473             {
    474                 Field field = oClass.getField(name);
    475                 obj = field.get(obj);
    476                 configure(obj, node, 0);
    477             }
    478             catch (NoSuchFieldException nsfe)
    479             {
    480                 throw nsme;
    481             }
    482         }
    483         if (id != null) _idMap.put(id, obj);
    484         return obj;
    485     }
    486
    487     /* ------------------------------------------------------------ */
    488     /*
    489      * Call a method. A method is selected by trying all methods with matching names and number of
    490      * arguments. Any object returned from the call is passed to the configure method to consume the
    491      * remaining elements. Note that if this is a static call we consider only methods declared
    492      * directly in the given class. i.e. we ignore any static methods in superclasses. @param obj
    493      * @param node @return @exception Exception
    494      */
    495     private Object call(Object obj, XmlParser.Node node) throws Exception
    496     {
    497         String id = node.getAttribute("id");
    498         Class oClass = nodeClass(node);
    499         if (oClass != null)
    500             obj = null;
    501         else if (obj != null) oClass = obj.getClass();
    502         if (oClass == null) throw new IllegalArgumentException(node.toString());
    503
    504         int size = 0;
    505         int argi = node.size();
    506         for (int i = 0; i < node.size(); i++)
    507         {
    508             Object o = node.get(i);
    509             if (o instanceof String) continue;
    510             if (!((XmlParser.Node) o).getTag().equals("Arg"))
    511             {
    512                 argi = i;
    513                 break;
    514             }
    515             size++;
    516         }
    517
    518         Object[] arg = new Object[size];
    519         for (int i = 0, j = 0; j < size; i++)
    520         {
    521             Object o = node.get(i);
    522             if (o instanceof String) continue;
    523             arg[j++] = value(obj, (XmlParser.Node) o);
    524         }
    525
    526         String method = node.getAttribute("name");
    527         if (Log.isDebugEnabled()) Log.debug("XML call " + method);
    528
    529         // Lets just try all methods for now
    530         Method[] methods = oClass.getMethods();
    531         for (int c = 0; methods != null && c < methods.length; c++)
    532         {
    533             if (!methods[c].getName().equals(method)) continue;
    534             if (methods[c].getParameterTypes().length != size) continue;
    535             if (Modifier.isStatic(methods[c].getModifiers()) != (obj == null)) continue;
    536             if ((obj == null) && methods[c].getDeclaringClass() != oClass) continue;
    537
    538             Object n = null;
    539             boolean called = false;
    540             try
    541             {
    542                 n = methods[c].invoke(obj, arg);
    543                 called = true;
    544             }
    545             catch (IllegalAccessException e)
    546             {
    547                 Log.ignore(e);
    548             }
    549             catch (IllegalArgumentException e)
    550             {
    551                 Log.ignore(e);
    552             }
    553             if (called)
    554             {
    555                 if (id != null) _idMap.put(id, n);
    556                 configure(n, node, argi);
    557                 return n;
    558             }
    559         }
    560
    561         throw new IllegalStateException("No Method: " + node + " on " + oClass);
    562     }
    563
    564     /* ------------------------------------------------------------ */
    565     /*
    566      * Create a new value object.
    567      *
    568      * @param obj @param node @return @exception Exception
    569      */
    570     private Object newObj(Object obj, XmlParser.Node node) throws Exception
    571     {
    572         Class oClass = nodeClass(node);
    573         String id = node.getAttribute("id");
    574         int size = 0;
    575         int argi = node.size();
    576         for (int i = 0; i < node.size(); i++)
    577         {
    578             Object o = node.get(i);
    579             if (o instanceof String) continue;
    580             if (!((XmlParser.Node) o).getTag().equals("Arg"))
    581             {
    582                 argi = i;
    583                 break;
    584             }
    585             size++;
    586         }
    587
    588         Object[] arg = new Object[size];
    589         for (int i = 0, j = 0; j < size; i++)
    590         {
    591             Object o = node.get(i);
    592             if (o instanceof String) continue;
    593             arg[j++] = value(obj, (XmlParser.Node) o);
    594         }
    595
    596         if (Log.isDebugEnabled()) Log.debug("XML new " + oClass);
    597
    598         // Lets just try all constructors for now
    599         Constructor[] constructors = oClass.getConstructors();
    600         for (int c = 0; constructors != null && c < constructors.length; c++)
    601         {
    602             if (constructors[c].getParameterTypes().length != size) continue;
    603
    604             Object n = null;
    605             boolean called = false;
    606             try
    607             {
    608                 n = constructors[c].newInstance(arg);
    609                 called = true;
    610             }
    611             catch (IllegalAccessException e)
    612             {
    613                 Log.ignore(e);
    614             }
    615             catch (InstantiationException e)
    616             {
    617                 Log.ignore(e);
    618             }
    619             catch (IllegalArgumentException e)
    620             {
    621                 Log.ignore(e);
    622             }
    623             if (called)
    624             {
    625                 if (id != null) _idMap.put(id, n);
    626                 configure(n, node, argi);
    627                 return n;
    628             }
    629         }
    630
    631         throw new IllegalStateException("No Constructor: " + node + " on " + obj);
    632     }
    633
    634     /* ------------------------------------------------------------ */
    635     /*
    636      * Reference an id value object.
    637      *
    638      * @param obj @param node @return @exception NoSuchMethodException @exception
    639      * ClassNotFoundException @exception InvocationTargetException
    640      */
    641     private Object refObj(Object obj, XmlParser.Node node) throws Exception
    642     {
    643         String id = node.getAttribute("id");
    644         obj = _idMap.get(id);
    645         if (obj == null) throw new IllegalStateException("No object for id=" + id);
    646         configure(obj, node, 0);
    647         return obj;
    648     }
    649
    650
    651     /* ------------------------------------------------------------ */
    652     /*
    653      * Create a new array object.
    654      *
    655      */
    656     private Object newArray(Object obj, XmlParser.Node node) throws Exception
    657     {
    658
    659         // Get the type
    660         Class aClass = java.lang.Object.class;
    661         String type = node.getAttribute("type");
    662         final String id = node.getAttribute("id");
    663         if (type != null)
    664         {
    665             aClass = TypeUtil.fromName(type);
    666             if (aClass == null)
    667             {
    668                 if ("String".equals(type))
    669                     aClass = java.lang.String.class;
    670                 else if ("URL".equals(type))
    671                     aClass = java.net.URL.class;
    672                 else if ("InetAddress".equals(type))
    673                     aClass = java.net.InetAddress.class;
    674                 else
    675                     aClass = Loader.loadClass(XmlConfiguration.class, type,true);
    676             }
    677         }
    678
    679         Object al=null;
    680
    681         Iterator iter = node.iterator("Item");
    682         while(iter.hasNext())
    683         {
    684             XmlParser.Node item= (XmlParser.Node)iter.next();
    685             String nid = item.getAttribute("id");
    686             Object v = value(obj, item);
    687             al=LazyList.add(al,(v==null&&aClass.isPrimitive())?ZERO:v);
    688             if (nid != null)
    689                 _idMap.put(nid, v);
    690         }
    691
    692         Object array =  LazyList.toArray(al,aClass);
    693         if (id != null)
    694             _idMap.put(id, array);
    695         return array;
    696     }
    697
    698     /* ------------------------------------------------------------ */
    699     /*
    700      * Create a new map object.
    701      *
    702      */
    703     private Object newMap(Object obj, XmlParser.Node node) throws Exception
    704     {
    705         String id = node.getAttribute("id");
    706
    707         Map map = new HashMap();
    708         if (id != null) _idMap.put(id, map);
    709
    710         for (int i = 0; i < node.size(); i++)
    711         {
    712             Object o = node.get(i);
    713             if (o instanceof String) continue;
    714             XmlParser.Node entry = (XmlParser.Node) o;
    715             if (!entry.getTag().equals("Entry")) throw new IllegalStateException("Not an Entry");
    716
    717
    718             XmlParser.Node key=null;
    719             XmlParser.Node value=null;
    720
    721             for (int j = 0; j < entry.size(); j++)
    722             {
    723                 o = entry.get(j);
    724                 if (o instanceof String) continue;
    725                 XmlParser.Node item = (XmlParser.Node) o;
    726                 if (!item.getTag().equals("Item")) throw new IllegalStateException("Not an Item");
    727                 if (key==null)
    728                     key=item;
    729                 else
    730                     value=item;
    731             }
    732
    733             if (key==null || value==null)
    734                 throw new IllegalStateException("Missing Item in Entry");
    735             String kid = key.getAttribute("id");
    736             String vid = value.getAttribute("id");
    737
    738             Object k = value(obj, key);
    739             Object v = value(obj, value);
    740             map.put(k,v);
    741
    742             if (kid != null) _idMap.put(kid, k);
    743             if (vid != null) _idMap.put(vid, v);
    744         }
    745
    746         return map;
    747     }
    748
    749     /* ------------------------------------------------------------ */
    750     /*
    751      * Create a new value object.
    752      *
    753      * @param obj @param node @return @exception Exception
    754      */
    755     private Object propertyObj(Object obj, XmlParser.Node node) throws Exception
    756     {
    757         String id = node.getAttribute("id");
    758         String name = node.getAttribute("name");
    759         Object defval = node.getAttribute("default");
    760         Object prop=null;
    761         if (_propertyMap!=null && _propertyMap.containsKey(name))
    762         {
    763             prop=_propertyMap.get(name);
    764         }
    765         else if (defval != null)
    766             prop=defval;
    767
    768         if (id != null)
    769             _idMap.put(id, prop);
    770         if (prop!=null)
    771             configure(prop, node, 0);
    772         return prop;
    773     }
    774
    775     /* ------------------------------------------------------------ */
    776     /*
    777      * Get the value of an element. If no value type is specified, then white space is trimmed out
    778      * of the value. If it contains multiple value elements they are added as strings before being
    779      * converted to any specified type. @param node
    780      */
    781     private Object value(Object obj, XmlParser.Node node) throws Exception
    782     {
    783         Object value = null;
    784
    785         // Get the type
    786         String type = node.getAttribute("type");
    787
    788         // Try a ref lookup
    789         String ref = node.getAttribute("ref");
    790         if (ref != null)
    791         {
    792             value = _idMap.get(ref);
    793         }
    794         else
    795         {
    796             // handle trivial case
    797             if (node.size() == 0)
    798             {
    799                 if ("String".equals(type)) return "";
    800                 return null;
    801             }
    802
    803             // Trim values
    804             int first = 0;
    805             int last = node.size() - 1;
    806
    807             // Handle default trim type
    808             if (type == null || !"String".equals(type))
    809             {
    810                 // Skip leading white
    811                 Object item = null;
    812                 while (first <= last)
    813                 {
    814                     item = node.get(first);
    815                     if (!(item instanceof String)) break;
    816                     item = ((String) item).trim();
    817                     if (((String) item).length() > 0) break;
    818                     first++;
    819                 }
    820
    821                 // Skip trailing white
    822                 while (first < last)
    823                 {
    824                     item = node.get(last);
    825                     if (!(item instanceof String)) break;
    826                     item = ((String) item).trim();
    827                     if (((String) item).length() > 0) break;
    828                     last--;
    829                 }
    830
    831                 // All white, so return null
    832                 if (first > last) return null;
    833             }
    834
    835             if (first == last)
    836                 //  Single Item value
    837                 value = itemValue(obj, node.get(first));
    838             else
    839             {
    840                 // Get the multiple items as a single string
    841                 StringBuffer buf = new StringBuffer();
    842                 synchronized (buf)
    843                 {
    844                     for (int i = first; i <= last; i++)
    845                     {
    846                         Object item = node.get(i);
    847                         buf.append(itemValue(obj, item));
    848                     }
    849                     value = buf.toString();
    850                 }
    851             }
    852         }
    853
    854         // Untyped or unknown
    855         if (value == null)
    856         {
    857             if ("String".equals(type)) return "";
    858             return null;
    859         }
    860
    861         // Try to type the object
    862         if (type == null)
    863         {
    864             if (value != null && value instanceof String) return ((String) value).trim();
    865             return value;
    866         }
    867
    868         if ("String".equals(type) || "java.lang.String".equals(type)) return value.toString();
    869
    870         Class pClass = TypeUtil.fromName(type);
    871         if (pClass != null) return TypeUtil.valueOf(pClass, value.toString());
    872
    873         if ("URL".equals(type) || "java.net.URL".equals(type))
    874         {
    875             if (value instanceof URL) return value;
    876             try
    877             {
    878                 return new URL(value.toString());
    879             }
    880             catch (MalformedURLException e)
    881             {
    882                 throw new InvocationTargetException(e);
    883             }
    884         }
    885
    886         if ("InetAddress".equals(type) || "java.net.InetAddress".equals(type))
    887         {
    888             if (value instanceof InetAddress) return value;
    889             try
    890             {
    891                 return InetAddress.getByName(value.toString());
    892             }
    893             catch (UnknownHostException e)
    894             {
    895                 throw new InvocationTargetException(e);
    896             }
    897         }
    898
    899         throw new IllegalStateException("Unknown type " + type);
    900     }
    901
    902     /* ------------------------------------------------------------ */
    903     /*
    904      * Get the value of a single element. @param obj @param item @return @exception Exception
    905      */
    906     private Object itemValue(Object obj, Object item) throws Exception
    907     {
    908         // String value
    909         if (item instanceof String) return item;
    910
    911         XmlParser.Node node = (XmlParser.Node) item;
    912         String tag = node.getTag();
    913         if ("Call".equals(tag)) return call(obj, node);
    914         if ("Get".equals(tag)) return get(obj, node);
    915         if ("New".equals(tag)) return newObj(obj, node);
    916         if ("Ref".equals(tag)) return refObj(obj, node);
    917         if ("Array".equals(tag)) return newArray(obj, node);
    918         if ("Map".equals(tag)) return newMap(obj, node);
    919         if ("Property".equals(tag)) return propertyObj(obj,node);
    920
    921         if ("SystemProperty".equals(tag))
    922         {
    923             String name = node.getAttribute("name");
    924             String defaultValue = node.getAttribute("default");
    925             return System.getProperty(name, defaultValue);
    926         }
    927
    928         Log.warn("Unknown value tag: " + node, new Throwable());
    929         return null;
    930     }
    931
    932     /* ------------------------------------------------------------ */
    933     /* ------------------------------------------------------------ */
    934     /* ------------------------------------------------------------ */
    935     /* ------------------------------------------------------------ */
    936     /**
    937      * Run the XML configurations as a main application.
    938      * The command line is used to obtain properties files (must be named '*.properties') and XmlConfiguration
    939      * files.
    940      * <p>
    941      * Any property file on the command line is added to a combined Property instance that is passed to
    942      * each configuration file via {@link XmlConfiguration#setProperties(Map)}.
    943      * <p>
    944      * Each configuration file on the command line is used to create a new XmlConfiguration instance and the
    945      * {@link XmlConfiguration#configure()} method is used to create the configured object.  If the resulting
    946      * object is an instance of {@link LifeCycle}, then it is started.
    947      * <p>
    948      * Any IDs created in a configuration are passed to the next configuration file on the command line using
    949      * {@link #getIdMap()} and {@link #setIdMap(Map)}. This allows objects with IDs created in one config file to
    950      * be referenced in subsequent config files on the command line.
    951      *
    952      * @param args array of property and xml configuration filenames or {@link Resource}s.
    953      */
    954     public static void main(String[] args)
    955     {
    956         try
    957         {
    958             Properties properties=new Properties();
    959             XmlConfiguration last=null;
    960             Object[] obj = new Object[args.length];
    961             for (int i = 0; i < args.length; i++)
    962             {
    963                 if (args[i].toLowerCase().endsWith(".properties"))
    964                 {
    965                     properties.load(Resource.newResource(args[i]).getInputStream());
    966                 }
    967                 else
    968                 {
    969                     XmlConfiguration configuration = new XmlConfiguration(Resource.newResource(args[i]).getURL());
    970                     if (last!=null)
    971                         configuration.getIdMap().putAll(last.getIdMap());
    972                     if (properties.size()>0)
    973                         configuration.setProperties(properties);
    974                     obj[i] = configuration.configure();
    975                     last=configuration;
    976                 }
    977             }
    978
    979             for (int i = 0; i < args.length; i++)
    980             {
    981                 if (obj[i] instanceof LifeCycle)
    982                 {
    983                     LifeCycle lc = (LifeCycle)obj[i];
    984                     if (!lc.isRunning())
    985                         lc.start();
    986                 }
    987             }
    988         }
    989         catch (Exception e)
    990         {
    991             Log.warn(Log.EXCEPTION, e);
    992         }
    993
    994     }
    995
    996 }
    997