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