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.Array;
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.security.AccessController;
30  import java.security.PrivilegedAction;
31  import java.util.ArrayList;
32  import java.util.Collection;
33  import java.util.Enumeration;
34  import java.util.HashMap;
35  import java.util.HashSet;
36  import java.util.List;
37  import java.util.Map;
38  import java.util.Properties;
39  import java.util.Queue;
40  import java.util.Set;
41  import java.util.concurrent.atomic.AtomicReference;
42  
43  import org.eclipse.jetty.util.ArrayQueue;
44  import org.eclipse.jetty.util.LazyList;
45  import org.eclipse.jetty.util.Loader;
46  import org.eclipse.jetty.util.TypeUtil;
47  import org.eclipse.jetty.util.component.LifeCycle;
48  import org.eclipse.jetty.util.log.Log;
49  import org.eclipse.jetty.util.log.Logger;
50  import org.eclipse.jetty.util.resource.Resource;
51  import org.eclipse.jetty.xml.XmlParser.Node;
52  import org.xml.sax.InputSource;
53  import org.xml.sax.SAXException;
54  
55  /* ------------------------------------------------------------ */
56  /**
57   * Configure Objects from XML. This class reads an XML file conforming to the configure.dtd DTD and uses it to configure and object by calling set, put or other
58   * methods on the object.
59   *
60   * <p>
61   * The actual XML file format may be changed (eg to spring XML) by implementing the {@link ConfigurationProcessorFactory} interfaces to be found by the
62   * <code>ServiceLoader</code> by using the DTD and first tag element in the file. Note that DTD will be null if validation is off.
63   *
64   */
65  public class XmlConfiguration
66  {
67      private static final Logger LOG = Log.getLogger(XmlConfiguration.class);
68  
69      private static final Class<?>[] __primitives =
70      { Boolean.TYPE, Character.TYPE, Byte.TYPE, Short.TYPE, Integer.TYPE, Long.TYPE, Float.TYPE, Double.TYPE, Void.TYPE };
71  
72      private static final Class<?>[] __primitiveHolders =
73      { Boolean.class, Character.class, Byte.class, Short.class, Integer.class, Long.class, Float.class, Double.class, Void.class };
74  
75      private static final Class<?>[] __supportedCollections =
76      { ArrayList.class,ArrayQueue.class,HashSet.class,Queue.class,List.class,Set.class,Collection.class,};
77  
78      private static final Iterable<?> __factoryLoader;
79  
80      private static final XmlParser __parser = initParser();
81  
82      static
83      {
84          Iterable<?> loader=null;
85          try
86          {
87              // Use reflection to look up 1.6 service loader
88              // loader=ServiceLoader.load(ConfigurationProcessorFactory.class);
89              Class<?> slc = ClassLoader.getSystemClassLoader().loadClass("java.util.ServiceLoader");
90              Method load = slc.getMethod("load",Class.class);
91              loader=(Iterable<?>)load.invoke(null,ConfigurationProcessorFactory.class);
92          }
93          catch(Exception e)
94          {
95              LOG.ignore(e);
96          }
97          finally
98          {
99              __factoryLoader=loader;
100         }
101     }
102 
103     /* ------------------------------------------------------------ */
104     private URL _url;
105     private String _dtd;
106     private ConfigurationProcessor _processor;
107     private final Map<String, Object> _idMap = new HashMap<String, Object>();
108     private final Map<String, String> _propertyMap = new HashMap<String, String>();
109 
110     /* ------------------------------------------------------------ */
111     private synchronized static XmlParser initParser()
112     {
113         XmlParser parser = new XmlParser();
114         try
115         {
116             URL config60 = Loader.getResource(XmlConfiguration.class,"org/eclipse/jetty/xml/configure_6_0.dtd",true);
117             URL config76 = Loader.getResource(XmlConfiguration.class,"org/eclipse/jetty/xml/configure_7_6.dtd",true);
118             parser.redirectEntity("configure.dtd",config76);
119             parser.redirectEntity("configure_1_0.dtd",config60);
120             parser.redirectEntity("configure_1_1.dtd",config60);
121             parser.redirectEntity("configure_1_2.dtd",config60);
122             parser.redirectEntity("configure_1_3.dtd",config60);
123             parser.redirectEntity("configure_6_0.dtd",config60);
124             parser.redirectEntity("configure_7_6.dtd",config76);
125 
126             parser.redirectEntity("http://jetty.mortbay.org/configure.dtd",config76);
127             parser.redirectEntity("http://jetty.eclipse.org/configure.dtd",config76);
128             parser.redirectEntity("http://www.eclipse.org/jetty/configure.dtd",config76);
129 
130             parser.redirectEntity("-//Mort Bay Consulting//DTD Configure//EN",config76);
131             parser.redirectEntity("-//Jetty//Configure//EN",config76);
132         }
133         catch (ClassNotFoundException e)
134         {
135             LOG.warn(e.toString());
136             LOG.debug(e);
137         }
138         return parser;
139     }
140 
141     /* ------------------------------------------------------------ */
142     /**
143      * Reads and parses the XML configuration file.
144      *
145      * @param configuration the URL of the XML configuration
146      * @throws IOException if the configuration could not be read
147      * @throws SAXException if the configuration could not be parsed
148      */
149     public XmlConfiguration(URL configuration) throws SAXException, IOException
150     {
151         synchronized (__parser)
152         {
153             _url=configuration;
154             setConfig(__parser.parse(configuration.toString()));
155             _dtd=__parser.getDTD();
156         }
157     }
158 
159     /* ------------------------------------------------------------ */
160     /**
161      * Reads and parses the XML configuration string.
162      *
163      * @param configuration String of XML configuration commands excluding the normal XML preamble.
164      * The String should start with a "&lt;Configure ....&gt;" element.
165      * @throws IOException if the configuration could not be read
166      * @throws SAXException if the configuration could not be parsed
167      */
168     public XmlConfiguration(String configuration) throws SAXException, IOException
169     {
170         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\">"
171                 + configuration;
172         InputSource source = new InputSource(new StringReader(configuration));
173         synchronized (__parser)
174         {
175             setConfig( __parser.parse(source));
176             _dtd=__parser.getDTD();
177         }
178     }
179 
180     /* ------------------------------------------------------------ */
181     /**
182      * Reads and parses the XML configuration stream.
183      *
184      * @param configuration An input stream containing a complete configuration file
185      * @throws IOException if the configuration could not be read
186      * @throws SAXException if the configuration could not be parsed
187      */
188     public XmlConfiguration(InputStream configuration) throws SAXException, IOException
189     {
190         InputSource source = new InputSource(configuration);
191         synchronized (__parser)
192         {
193             setConfig(__parser.parse(source));
194             _dtd=__parser.getDTD();
195         }
196     }
197 
198     /* ------------------------------------------------------------ */
199     private void setConfig(XmlParser.Node config)
200     {
201         if ("Configure".equals(config.getTag()))
202         {
203             _processor=new JettyXmlConfiguration();
204         }
205         else if (__factoryLoader!=null)
206         {
207             for ( Object factory : __factoryLoader)
208             {
209                 // use reflection to get 1.6 methods
210                 Method gcp;
211                 try
212                 {
213                     gcp = factory.getClass().getMethod("getConfigurationProcessor",String.class,String.class);
214                     _processor = (ConfigurationProcessor) gcp.invoke(factory,_dtd,config.getTag());
215                 }
216                 catch (Exception e)
217                 {
218                     LOG.warn(e);
219                 }
220                 if (_processor!=null)
221                     break;
222             }
223 
224             if (_processor==null)
225                 throw new IllegalStateException("Unknown configuration type: "+config.getTag()+" in "+this);
226         }
227         else
228         {
229             throw new IllegalArgumentException("Unknown XML tag:"+config.getTag());
230         }
231         _processor.init(_url,config,_idMap, _propertyMap);
232     }
233 
234 
235     /* ------------------------------------------------------------ */
236     public Map<String, Object> getIdMap()
237     {
238         return _idMap;
239     }
240 
241     /* ------------------------------------------------------------ */
242     /**
243      * @param map the ID map
244      * @deprecated use {@link #getIdMap()}.put(...)
245      */
246     @Deprecated
247     public void setIdMap(Map<String, Object> map)
248     {
249         _idMap.clear();
250         _idMap.putAll(map);
251     }
252 
253     /* ------------------------------------------------------------ */
254     /**
255      * @param map the properties map
256      * @deprecated use {@link #getProperties()}.putAll(...)
257      */
258     @Deprecated
259     public void setProperties(Map<String, String> map)
260     {
261         _propertyMap.clear();
262         _propertyMap.putAll(map);
263     }
264 
265     /* ------------------------------------------------------------ */
266     public Map<String, String> getProperties()
267     {
268         return _propertyMap;
269     }
270 
271     /* ------------------------------------------------------------ */
272     /**
273      * Applies the XML configuration script to the given object.
274      *
275      * @param obj The object to be configured, which must be of a type or super type
276      * of the class attribute of the &lt;Configure&gt; element.
277      * @throws Exception if the configuration fails
278      * @return the configured object
279      */
280     public Object configure(Object obj) throws Exception
281     {
282         return _processor.configure(obj);
283     }
284 
285     /* ------------------------------------------------------------ */
286     /**
287      * Applies the XML configuration script.
288      * If the root element of the configuration has an ID, an object is looked up by ID and its type checked
289      * against the root element's type.
290      * Otherwise a new object of the type specified by the root element is created.
291      *
292      * @return The newly created configured object.
293      * @throws Exception if the configuration fails
294      */
295     public Object configure() throws Exception
296     {
297         return _processor.configure();
298     }
299 
300     /* ------------------------------------------------------------ */
301     /* ------------------------------------------------------------ */
302     /* ------------------------------------------------------------ */
303     private static class JettyXmlConfiguration implements ConfigurationProcessor
304     {
305         XmlParser.Node _config;
306         Map<String, Object> _idMap;
307         Map<String, String> _propertyMap;
308 
309         public void init(URL url, XmlParser.Node config, Map<String, Object> idMap, Map<String, String> properties)
310         {
311             _config=config;
312             _idMap=idMap;
313             _propertyMap=properties;
314         }
315 
316         /* ------------------------------------------------------------ */
317         public Object configure(Object obj) throws Exception
318         {
319             // Check the class of the object
320             Class<?> oClass = nodeClass(_config);
321             if (oClass != null && !oClass.isInstance(obj))
322             {
323                 String loaders = (oClass.getClassLoader()==obj.getClass().getClassLoader())?"":"Object Class and type Class are from different loaders.";
324                 throw new IllegalArgumentException("Object of class '"+obj.getClass().getCanonicalName()+"' is not of type '" + oClass.getCanonicalName()+"'. "+loaders);
325             }
326             configure(obj,_config,0);
327             return obj;
328         }
329 
330         /* ------------------------------------------------------------ */
331         public Object configure() throws Exception
332         {
333             Class<?> oClass = nodeClass(_config);
334 
335             String id = _config.getAttribute("id");
336             Object obj = id == null?null:_idMap.get(id);
337 
338             if (obj == null && oClass != null)
339                 obj = oClass.newInstance();
340 
341             if (oClass != null && !oClass.isInstance(obj))
342                 throw new ClassCastException(oClass.toString());
343 
344             configure(obj,_config,0);
345             return obj;
346         }
347 
348         /* ------------------------------------------------------------ */
349         private static Class<?> nodeClass(XmlParser.Node node) throws ClassNotFoundException
350         {
351             String className = node.getAttribute("class");
352             if (className == null)
353                 return null;
354 
355             return Loader.loadClass(XmlConfiguration.class,className,true);
356         }
357 
358         /* ------------------------------------------------------------ */
359         /**
360          * Recursive configuration routine.
361          * This method applies the nested Set, Put, Call, etc. elements to the given object.
362          *
363          * @param obj the object to configure
364          * @param cfg the XML nodes of the configuration
365          * @param i the index of the XML nodes
366          * @throws Exception if the configuration fails
367          */
368         public void configure(Object obj, XmlParser.Node cfg, int i) throws Exception
369         {
370             String id = cfg.getAttribute("id");
371             if (id != null)
372                 _idMap.put(id,obj);
373 
374             for (; i < cfg.size(); i++)
375             {
376                 Object o = cfg.get(i);
377                 if (o instanceof String)
378                     continue;
379                 XmlParser.Node node = (XmlParser.Node)o;
380 
381                 try
382                 {
383                     String tag = node.getTag();
384                     if ("Set".equals(tag))
385                         set(obj,node);
386                     else if ("Put".equals(tag))
387                         put(obj,node);
388                     else if ("Call".equals(tag))
389                         call(obj,node);
390                     else if ("Get".equals(tag))
391                         get(obj,node);
392                     else if ("New".equals(tag))
393                         newObj(obj,node);
394                     else if ("Array".equals(tag))
395                         newArray(obj,node);
396                     else if ("Ref".equals(tag))
397                         refObj(obj,node);
398                     else if ("Property".equals(tag))
399                         propertyObj(node);
400                     else
401                         throw new IllegalStateException("Unknown tag: " + tag);
402                 }
403                 catch (Exception e)
404                 {
405                     LOG.warn("Config error at " + node,e.toString());
406                     throw e;
407                 }
408             }
409         }
410 
411         /* ------------------------------------------------------------ */
412         /*
413          * Call a set method. This method makes a best effort to find a matching set method. The type of the value is used to find a suitable set method by 1.
414          * Trying for a trivial type match. 2. Looking for a native type match. 3. Trying all correctly named methods for an auto conversion. 4. Attempting to
415          * construct a suitable value from original value. @param obj
416          *
417          * @param node
418          */
419         private void set(Object obj, XmlParser.Node node) throws Exception
420         {
421             String attr = node.getAttribute("name");
422             String name = "set" + attr.substring(0,1).toUpperCase() + attr.substring(1);
423             Object value = value(obj,node);
424             Object[] arg =
425             { value };
426 
427             Class<?> oClass = nodeClass(node);
428             if (oClass != null)
429                 obj = null;
430             else
431                 oClass = obj.getClass();
432 
433             Class<?>[] vClass =
434             { Object.class };
435             if (value != null)
436                 vClass[0] = value.getClass();
437 
438             if (LOG.isDebugEnabled())
439                 LOG.debug("XML " + (obj != null?obj.toString():oClass.getName()) + "." + name + "(" + value + ")");
440 
441             // Try for trivial match
442             try
443             {
444                 Method set = oClass.getMethod(name,vClass);
445                 set.invoke(obj,arg);
446                 return;
447             }
448             catch (IllegalArgumentException e)
449             {
450                 LOG.ignore(e);
451             }
452             catch (IllegalAccessException e)
453             {
454                 LOG.ignore(e);
455             }
456             catch (NoSuchMethodException e)
457             {
458                 LOG.ignore(e);
459             }
460 
461             // Try for native match
462             try
463             {
464                 Field type = vClass[0].getField("TYPE");
465                 vClass[0] = (Class<?>)type.get(null);
466                 Method set = oClass.getMethod(name,vClass);
467                 set.invoke(obj,arg);
468                 return;
469             }
470             catch (NoSuchFieldException e)
471             {
472                 LOG.ignore(e);
473             }
474             catch (IllegalArgumentException e)
475             {
476                 LOG.ignore(e);
477             }
478             catch (IllegalAccessException e)
479             {
480                 LOG.ignore(e);
481             }
482             catch (NoSuchMethodException e)
483             {
484                 LOG.ignore(e);
485             }
486 
487             // Try a field
488             try
489             {
490                 Field field = oClass.getField(attr);
491                 if (Modifier.isPublic(field.getModifiers()))
492                 {
493                     field.set(obj,value);
494                     return;
495                 }
496             }
497             catch (NoSuchFieldException e)
498             {
499                 LOG.ignore(e);
500             }
501 
502             // Search for a match by trying all the set methods
503             Method[] sets = oClass.getMethods();
504             Method set = null;
505             for (int s = 0; sets != null && s < sets.length; s++)
506             {
507                 Class<?>[] paramTypes = sets[s].getParameterTypes();
508                 if (name.equals(sets[s].getName()) && paramTypes.length == 1)
509                 {
510 
511                     // lets try it
512                     try
513                     {
514                         set = sets[s];
515                         sets[s].invoke(obj,arg);
516                         return;
517                     }
518                     catch (IllegalArgumentException e)
519                     {
520                         LOG.ignore(e);
521                     }
522                     catch (IllegalAccessException e)
523                     {
524                         LOG.ignore(e);
525                     }
526 
527                     try
528                     {
529                         for (Class<?> c : __supportedCollections)
530                             if (paramTypes[0].isAssignableFrom(c))
531                             {
532                                 sets[s].invoke(obj,convertArrayToCollection(value,c));
533                                 return;
534                             }
535                     }
536                     catch (IllegalAccessException e)
537                     {
538                         LOG.ignore(e);
539                     }
540                 }
541             }
542 
543             // Try converting the arg to the last set found.
544             if (set != null)
545             {
546                 try
547                 {
548                     Class<?> sClass = set.getParameterTypes()[0];
549                     if (sClass.isPrimitive())
550                     {
551                         for (int t = 0; t < __primitives.length; t++)
552                         {
553                             if (sClass.equals(__primitives[t]))
554                             {
555                                 sClass = __primitiveHolders[t];
556                                 break;
557                             }
558                         }
559                     }
560                     Constructor<?> cons = sClass.getConstructor(vClass);
561                     arg[0] = cons.newInstance(arg);
562                     set.invoke(obj,arg);
563                     return;
564                 }
565                 catch (NoSuchMethodException e)
566                 {
567                     LOG.ignore(e);
568                 }
569                 catch (IllegalAccessException e)
570                 {
571                     LOG.ignore(e);
572                 }
573                 catch (InstantiationException e)
574                 {
575                     LOG.ignore(e);
576                 }
577             }
578 
579             // No Joy
580             throw new NoSuchMethodException(oClass + "." + name + "(" + vClass[0] + ")");
581         }
582 
583         /**
584          * @param array the array to convert
585          * @param collectionType the desired collection type
586          * @return a collection of the desired type if the array can be converted
587          */
588         private static Collection<?> convertArrayToCollection(Object array, Class<?> collectionType)
589         {
590             Collection<?> collection = null;
591             if (array.getClass().isArray())
592             {
593                 if (collectionType.isAssignableFrom(ArrayList.class))
594                     collection = convertArrayToArrayList(array);
595                 else if (collectionType.isAssignableFrom(HashSet.class))
596                     collection = new HashSet<Object>(convertArrayToArrayList(array));
597                 else if (collectionType.isAssignableFrom(ArrayQueue.class))
598                 {
599                     ArrayQueue<Object> q= new ArrayQueue<Object>();
600                     q.addAll(convertArrayToArrayList(array));
601                     collection=q;
602                 }
603             }
604             if (collection==null)
605                 throw new IllegalArgumentException("Can't convert \"" + array.getClass() + "\" to " + collectionType);
606             return collection;
607         }
608 
609         /* ------------------------------------------------------------ */
610         private static ArrayList<Object> convertArrayToArrayList(Object array)
611         {
612             int length = Array.getLength(array);
613             ArrayList<Object> list = new ArrayList<Object>(length);
614             for (int i = 0; i < length; i++)
615                 list.add(Array.get(array,i));
616             return list;
617         }
618 
619         /* ------------------------------------------------------------ */
620         /*
621          * Call a put method.
622          *
623          * @param obj @param node
624          */
625         private void put(Object obj, XmlParser.Node node) throws Exception
626         {
627             if (!(obj instanceof Map))
628                 throw new IllegalArgumentException("Object for put is not a Map: " + obj);
629             @SuppressWarnings("unchecked")
630             Map<Object, Object> map = (Map<Object, Object>)obj;
631 
632             String name = node.getAttribute("name");
633             Object value = value(obj,node);
634             map.put(name,value);
635             if (LOG.isDebugEnabled())
636                 LOG.debug("XML " + obj + ".put(" + name + "," + value + ")");
637         }
638 
639         /* ------------------------------------------------------------ */
640         /*
641          * Call a get method. Any object returned from the call is passed to the configure method to consume the remaining elements. @param obj @param node
642          *
643          * @return @exception Exception
644          */
645         private Object get(Object obj, XmlParser.Node node) throws Exception
646         {
647             Class<?> oClass = nodeClass(node);
648             if (oClass != null)
649                 obj = null;
650             else
651                 oClass = obj.getClass();
652 
653             String name = node.getAttribute("name");
654             String id = node.getAttribute("id");
655             if (LOG.isDebugEnabled())
656                 LOG.debug("XML get " + name);
657 
658             try
659             {
660                 // try calling a getXxx method.
661                 Method method = oClass.getMethod("get" + name.substring(0,1).toUpperCase() + name.substring(1),(java.lang.Class[])null);
662                 obj = method.invoke(obj,(java.lang.Object[])null);
663                 configure(obj,node,0);
664             }
665             catch (NoSuchMethodException nsme)
666             {
667                 try
668                 {
669                     Field field = oClass.getField(name);
670                     obj = field.get(obj);
671                     configure(obj,node,0);
672                 }
673                 catch (NoSuchFieldException nsfe)
674                 {
675                     throw nsme;
676                 }
677             }
678             if (id != null)
679                 _idMap.put(id,obj);
680             return obj;
681         }
682 
683         /* ------------------------------------------------------------ */
684         /*
685          * Call a method. A method is selected by trying all methods with matching names and number of arguments. Any object returned from the call is passed to
686          * the configure method to consume the remaining elements. Note that if this is a static call we consider only methods declared directly in the given
687          * class. i.e. we ignore any static methods in superclasses. @param obj
688          *
689          * @param node @return @exception Exception
690          */
691         private Object call(Object obj, XmlParser.Node node) throws Exception
692         {
693             String id = node.getAttribute("id");
694             Class<?> oClass = nodeClass(node);
695             if (oClass != null)
696                 obj = null;
697             else if (obj != null)
698                 oClass = obj.getClass();
699             if (oClass == null)
700                 throw new IllegalArgumentException(node.toString());
701 
702             int size = 0;
703             int argi = node.size();
704             for (int i = 0; i < node.size(); i++)
705             {
706                 Object o = node.get(i);
707                 if (o instanceof String)
708                     continue;
709                 if (!((XmlParser.Node)o).getTag().equals("Arg"))
710                 {
711                     argi = i;
712                     break;
713                 }
714                 size++;
715             }
716 
717             Object[] arg = new Object[size];
718             for (int i = 0, j = 0; j < size; i++)
719             {
720                 Object o = node.get(i);
721                 if (o instanceof String)
722                     continue;
723                 arg[j++] = value(obj,(XmlParser.Node)o);
724             }
725 
726             String method = node.getAttribute("name");
727             if (LOG.isDebugEnabled())
728                 LOG.debug("XML call " + method);
729 
730             try
731             {
732                 Object n= TypeUtil.call(oClass,method,obj,arg);
733                 if (id != null)
734                     _idMap.put(id,n);
735                 configure(n,node,argi);
736                 return n;
737             }
738             catch (NoSuchMethodException e)
739             {
740                 IllegalStateException ise = new IllegalStateException("No Method: " + node + " on " + oClass);
741                 ise.initCause(e);
742                 throw ise;
743             }
744 
745         }
746 
747         /* ------------------------------------------------------------ */
748         /*
749          * Create a new value object.
750          *
751          * @param obj @param node @return @exception Exception
752          */
753         private Object newObj(Object obj, XmlParser.Node node) throws Exception
754         {
755             Class<?> oClass = nodeClass(node);
756             String id = node.getAttribute("id");
757             int size = 0;
758             int argi = node.size();
759             for (int i = 0; i < node.size(); i++)
760             {
761                 Object o = node.get(i);
762                 if (o instanceof String)
763                     continue;
764                 if (!((XmlParser.Node)o).getTag().equals("Arg"))
765                 {
766                     argi = i;
767                     break;
768                 }
769                 size++;
770             }
771 
772             Object[] arg = new Object[size];
773             for (int i = 0, j = 0; j < size; i++)
774             {
775                 Object o = node.get(i);
776                 if (o instanceof String)
777                     continue;
778                 arg[j++] = value(obj,(XmlParser.Node)o);
779             }
780 
781             if (LOG.isDebugEnabled())
782                 LOG.debug("XML new " + oClass);
783 
784             // Lets just try all constructors for now
785             Constructor<?>[] constructors = oClass.getConstructors();
786             for (int c = 0; constructors != null && c < constructors.length; c++)
787             {
788                 if (constructors[c].getParameterTypes().length != size)
789                     continue;
790 
791                 Object n = null;
792                 boolean called = false;
793                 try
794                 {
795                     n = constructors[c].newInstance(arg);
796                     called = true;
797                 }
798                 catch (IllegalAccessException e)
799                 {
800                     LOG.ignore(e);
801                 }
802                 catch (InstantiationException e)
803                 {
804                     LOG.ignore(e);
805                 }
806                 catch (IllegalArgumentException e)
807                 {
808                     LOG.ignore(e);
809                 }
810                 if (called)
811                 {
812                     if (id != null)
813                         _idMap.put(id,n);
814                     configure(n,node,argi);
815                     return n;
816                 }
817             }
818 
819             throw new IllegalStateException("No Constructor: " + node + " on " + obj);
820         }
821 
822         /* ------------------------------------------------------------ */
823         /*
824          * Reference an id value object.
825          *
826          * @param obj @param node @return @exception NoSuchMethodException @exception ClassNotFoundException @exception InvocationTargetException
827          */
828         private Object refObj(Object obj, XmlParser.Node node) throws Exception
829         {
830             String id = node.getAttribute("id");
831             obj = _idMap.get(id);
832             if (obj == null)
833                 throw new IllegalStateException("No object for id=" + id);
834             configure(obj,node,0);
835             return obj;
836         }
837 
838         /* ------------------------------------------------------------ */
839         /*
840          * Create a new array object.
841          */
842         private Object newArray(Object obj, XmlParser.Node node) throws Exception
843         {
844 
845             // Get the type
846             Class<?> aClass = java.lang.Object.class;
847             String type = node.getAttribute("type");
848             final String id = node.getAttribute("id");
849             if (type != null)
850             {
851                 aClass = TypeUtil.fromName(type);
852                 if (aClass == null)
853                 {
854                     if ("String".equals(type))
855                         aClass = java.lang.String.class;
856                     else if ("URL".equals(type))
857                         aClass = java.net.URL.class;
858                     else if ("InetAddress".equals(type))
859                         aClass = java.net.InetAddress.class;
860                     else
861                         aClass = Loader.loadClass(XmlConfiguration.class,type,true);
862                 }
863             }
864 
865             Object al = null;
866 
867             for (Object nodeObject : node)
868             {
869                 XmlParser.Node item = (Node)nodeObject;
870                 String nid = item.getAttribute("id");
871                 Object v = value(obj,item);
872                 al = LazyList.add(al,(v == null && aClass.isPrimitive())?0:v);
873                 if (nid != null)
874                     _idMap.put(nid,v);
875             }
876 
877             Object array = LazyList.toArray(al,aClass);
878             if (id != null)
879                 _idMap.put(id,array);
880             return array;
881         }
882 
883         /* ------------------------------------------------------------ */
884         /*
885          * Create a new map object.
886          */
887         private Object newMap(Object obj, XmlParser.Node node) throws Exception
888         {
889             String id = node.getAttribute("id");
890 
891             Map<Object, Object> map = new HashMap<Object, Object>();
892             if (id != null)
893                 _idMap.put(id,map);
894 
895             for (Object o : node)
896             {
897                 if (o instanceof String)
898                     continue;
899                 XmlParser.Node entry = (XmlParser.Node)o;
900                 if (!entry.getTag().equals("Entry"))
901                     throw new IllegalStateException("Not an Entry");
902 
903                 XmlParser.Node key = null;
904                 XmlParser.Node value = null;
905 
906                 for (Object object : entry)
907                 {
908                     if (object instanceof String)
909                         continue;
910                     XmlParser.Node item = (XmlParser.Node)object;
911                     if (!item.getTag().equals("Item"))
912                         throw new IllegalStateException("Not an Item");
913                     if (key == null)
914                         key = item;
915                     else
916                         value = item;
917                 }
918 
919                 if (key == null || value == null)
920                     throw new IllegalStateException("Missing Item in Entry");
921                 String kid = key.getAttribute("id");
922                 String vid = value.getAttribute("id");
923 
924                 Object k = value(obj,key);
925                 Object v = value(obj,value);
926                 map.put(k,v);
927 
928                 if (kid != null)
929                     _idMap.put(kid,k);
930                 if (vid != null)
931                     _idMap.put(vid,v);
932             }
933 
934             return map;
935         }
936 
937         /* ------------------------------------------------------------ */
938         /*
939          * Get a Property.
940          *
941          * @param node
942          * @return
943          * @exception Exception
944          */
945         private Object propertyObj(XmlParser.Node node) throws Exception
946         {
947             String id = node.getAttribute("id");
948             String name = node.getAttribute("name");
949             String defaultValue = node.getAttribute("default");
950             Object prop;
951             if (_propertyMap != null && _propertyMap.containsKey(name))
952                 prop = _propertyMap.get(name);
953             else
954                 prop = defaultValue;
955             if (id != null)
956                 _idMap.put(id,prop);
957             if (prop != null)
958                 configure(prop,node,0);
959             return prop;
960         }
961 
962 
963         /* ------------------------------------------------------------ */
964         /*
965          * Get the value of an element. If no value type is specified, then white space is trimmed out of the value. If it contains multiple value elements they
966          * are added as strings before being converted to any specified type. @param node
967          */
968         private Object value(Object obj, XmlParser.Node node) throws Exception
969         {
970             Object value;
971 
972             // Get the type
973             String type = node.getAttribute("type");
974 
975             // Try a ref lookup
976             String ref = node.getAttribute("ref");
977             if (ref != null)
978             {
979                 value = _idMap.get(ref);
980             }
981             else
982             {
983                 // handle trivial case
984                 if (node.size() == 0)
985                 {
986                     if ("String".equals(type))
987                         return "";
988                     return null;
989                 }
990 
991                 // Trim values
992                 int first = 0;
993                 int last = node.size() - 1;
994 
995                 // Handle default trim type
996                 if (type == null || !"String".equals(type))
997                 {
998                     // Skip leading white
999                     Object item;
1000                     while (first <= last)
1001                     {
1002                         item = node.get(first);
1003                         if (!(item instanceof String))
1004                             break;
1005                         item = ((String)item).trim();
1006                         if (((String)item).length() > 0)
1007                             break;
1008                         first++;
1009                     }
1010 
1011                     // Skip trailing white
1012                     while (first < last)
1013                     {
1014                         item = node.get(last);
1015                         if (!(item instanceof String))
1016                             break;
1017                         item = ((String)item).trim();
1018                         if (((String)item).length() > 0)
1019                             break;
1020                         last--;
1021                     }
1022 
1023                     // All white, so return null
1024                     if (first > last)
1025                         return null;
1026                 }
1027 
1028                 if (first == last)
1029                     // Single Item value
1030                     value = itemValue(obj,node.get(first));
1031                 else
1032                 {
1033                     // Get the multiple items as a single string
1034                     StringBuilder buf = new StringBuilder();
1035                     for (int i = first; i <= last; i++)
1036                     {
1037                         Object item = node.get(i);
1038                         buf.append(itemValue(obj,item));
1039                     }
1040                     value = buf.toString();
1041                 }
1042             }
1043 
1044             // Untyped or unknown
1045             if (value == null)
1046             {
1047                 if ("String".equals(type))
1048                     return "";
1049                 return null;
1050             }
1051 
1052             // Try to type the object
1053             if (type == null)
1054             {
1055                 if (value instanceof String)
1056                     return ((String)value).trim();
1057                 return value;
1058             }
1059 
1060             if (isTypeMatchingClass(type,String.class))
1061                 return value.toString();
1062 
1063             Class<?> pClass = TypeUtil.fromName(type);
1064             if (pClass != null)
1065                 return TypeUtil.valueOf(pClass,value.toString());
1066 
1067             if (isTypeMatchingClass(type,URL.class))
1068             {
1069                 if (value instanceof URL)
1070                     return value;
1071                 try
1072                 {
1073                     return new URL(value.toString());
1074                 }
1075                 catch (MalformedURLException e)
1076                 {
1077                     throw new InvocationTargetException(e);
1078                 }
1079             }
1080 
1081             if (isTypeMatchingClass(type,InetAddress.class))
1082             {
1083                 if (value instanceof InetAddress)
1084                     return value;
1085                 try
1086                 {
1087                     return InetAddress.getByName(value.toString());
1088                 }
1089                 catch (UnknownHostException e)
1090                 {
1091                     throw new InvocationTargetException(e);
1092                 }
1093             }
1094 
1095             for (Class<?> collectionClass : __supportedCollections)
1096             {
1097                 if (isTypeMatchingClass(type,collectionClass))
1098                     return convertArrayToCollection(value,collectionClass);
1099             }
1100 
1101             throw new IllegalStateException("Unknown type " + type);
1102         }
1103 
1104         /* ------------------------------------------------------------ */
1105         private static boolean isTypeMatchingClass(String type, Class<?> classToMatch)
1106         {
1107             return classToMatch.getSimpleName().equalsIgnoreCase(type) || classToMatch.getName().equals(type);
1108         }
1109 
1110         /* ------------------------------------------------------------ */
1111         /*
1112          * Get the value of a single element. @param obj @param item @return @exception Exception
1113          */
1114         private Object itemValue(Object obj, Object item) throws Exception
1115         {
1116             // String value
1117             if (item instanceof String)
1118                 return item;
1119 
1120             XmlParser.Node node = (XmlParser.Node)item;
1121             String tag = node.getTag();
1122             if ("Call".equals(tag))
1123                 return call(obj,node);
1124             if ("Get".equals(tag))
1125                 return get(obj,node);
1126             if ("New".equals(tag))
1127                 return newObj(obj,node);
1128             if ("Ref".equals(tag))
1129                 return refObj(obj,node);
1130             if ("Array".equals(tag))
1131                 return newArray(obj,node);
1132             if ("Map".equals(tag))
1133                 return newMap(obj,node);
1134             if ("Property".equals(tag))
1135                 return propertyObj(node);
1136 
1137             if ("SystemProperty".equals(tag))
1138             {
1139                 String name = node.getAttribute("name");
1140                 String defaultValue = node.getAttribute("default");
1141                 return System.getProperty(name,defaultValue);
1142             }
1143 
1144             if ("Env".equals(tag))
1145             {
1146                 String name = node.getAttribute("name");
1147                 String defaultValue = node.getAttribute("default");
1148                 String value=System.getenv(name);
1149                 return value==null?defaultValue:value;
1150             }
1151 
1152             LOG.warn("Unknown value tag: " + node,new Throwable());
1153             return null;
1154         }
1155     }
1156 
1157     /* ------------------------------------------------------------ */
1158     /* ------------------------------------------------------------ */
1159     /* ------------------------------------------------------------ */
1160     /* ------------------------------------------------------------ */
1161     /**
1162      * Run the XML configurations as a main application. The command line is used to obtain properties files (must be named '*.properties') and XmlConfiguration
1163      * files.
1164      * <p>
1165      * Any property file on the command line is added to a combined Property instance that is passed to each configuration file via
1166      * {@link XmlConfiguration#setProperties(Map)}.
1167      * <p>
1168      * Each configuration file on the command line is used to create a new XmlConfiguration instance and the {@link XmlConfiguration#configure()} method is used
1169      * to create the configured object. If the resulting object is an instance of {@link LifeCycle}, then it is started.
1170      * <p>
1171      * Any IDs created in a configuration are passed to the next configuration file on the command line using {@link #getIdMap()} and {@link #setIdMap(Map)} .
1172      * This allows objects with IDs created in one config file to be referenced in subsequent config files on the command line.
1173      *
1174      * @param args
1175      *            array of property and xml configuration filenames or {@link Resource}s.
1176      * @throws Exception if the XML configurations cannot be run
1177      */
1178     public static void main(final String[] args) throws Exception
1179     {
1180 
1181         final AtomicReference<Throwable> exception = new AtomicReference<Throwable>();
1182 
1183         AccessController.doPrivileged(new PrivilegedAction<Object>()
1184         {
1185             public Object run()
1186             {
1187                 try
1188                 {
1189 
1190                     Properties properties = null;
1191 
1192                     // Look for properties from start.jar
1193                     try
1194                     {
1195                         Class<?> config = XmlConfiguration.class.getClassLoader().loadClass("org.eclipse.jetty.start.Config");
1196                         properties = (Properties)config.getMethod("getProperties").invoke(null);
1197                         LOG.debug("org.eclipse.jetty.start.Config properties = {}",properties);
1198                     }
1199                     catch (NoClassDefFoundError e)
1200                     {
1201                         LOG.ignore(e);
1202                     }
1203                     catch (ClassNotFoundException e)
1204                     {
1205                         LOG.ignore(e);
1206                     }
1207                     catch (Exception e)
1208                     {
1209                         LOG.warn(e);
1210                     }
1211 
1212                     // If no start.config properties, use clean slate
1213                     if (properties == null)
1214                     {
1215                         properties = new Properties();
1216                         // Add System Properties
1217                         Enumeration<?> ensysprop = System.getProperties().propertyNames();
1218                         while (ensysprop.hasMoreElements())
1219                         {
1220                             String name = (String)ensysprop.nextElement();
1221                             properties.put(name,System.getProperty(name));
1222                         }
1223                     }
1224 
1225                     // For all arguments, load properties or parse XMLs
1226                     XmlConfiguration last = null;
1227                     Object[] obj = new Object[args.length];
1228                     for (int i = 0; i < args.length; i++)
1229                     {
1230                         if (args[i].toLowerCase().endsWith(".properties"))
1231                         {
1232                             properties.load(Resource.newResource(args[i]).getInputStream());
1233                         }
1234                         else
1235                         {
1236                             XmlConfiguration configuration = new XmlConfiguration(Resource.newResource(args[i]).getURL());
1237                             if (last != null)
1238                                 configuration.getIdMap().putAll(last.getIdMap());
1239                             if (properties.size() > 0)
1240                             {
1241                                 Map<String, String> props = new HashMap<String, String>();
1242                                 for (Object key : properties.keySet())
1243                                 {
1244                                     props.put(key.toString(),String.valueOf(properties.get(key)));
1245                                 }
1246                                 configuration.getProperties().putAll(props);
1247                             }
1248                             obj[i] = configuration.configure();
1249                             last = configuration;
1250                         }
1251                     }
1252 
1253                     // For all objects created by XmlConfigurations, start them if they are lifecycles.
1254                     for (int i = 0; i < args.length; i++)
1255                     {
1256                         if (obj[i] instanceof LifeCycle)
1257                         {
1258                             LifeCycle lc = (LifeCycle)obj[i];
1259                             if (!lc.isRunning())
1260                                 lc.start();
1261                         }
1262                     }
1263                 }
1264                 catch (Exception e)
1265                 {
1266                     LOG.debug(Log.EXCEPTION,e);
1267                     exception.set(e);
1268                 }
1269                 return null;
1270             }
1271         });
1272 
1273         Throwable th = exception.get();
1274         if (th != null)
1275         {
1276             if (th instanceof RuntimeException)
1277                 throw (RuntimeException)th;
1278             else if (th instanceof Exception)
1279                 throw (Exception)th;
1280             else if (th instanceof Error)
1281                 throw (Error)th;
1282             throw new Error(th);
1283         }
1284     }
1285 }