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