View Javadoc

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