View Javadoc

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