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