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,this);
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     /* ------------------------------------------------------------ */
266     /** Initialize a new Object defaults.
267      * <p>This method must be called by any {@link ConfigurationProcessor} when it 
268      * creates a new instance of an object before configuring it, so that a derived 
269      * XmlConfiguration class may inject default values.
270      * @param object the object to initialize defaults on
271      */
272     public void initializeDefaults(Object object)
273     {
274     }
275 
276     private static class JettyXmlConfiguration implements ConfigurationProcessor
277     {
278         private String _url;
279         XmlParser.Node _root;
280         XmlConfiguration _configuration;
281 
282         public void init(URL url, XmlParser.Node root, XmlConfiguration configuration)
283         {
284             _url=url==null?null:url.toString();
285             _root=root;
286             _configuration=configuration;
287         }
288 
289         public Object configure(Object obj) throws Exception
290         {
291             // Check the class of the object
292             Class<?> oClass = nodeClass(_root);
293             if (oClass != null && !oClass.isInstance(obj))
294             {
295                 String loaders = (oClass.getClassLoader()==obj.getClass().getClassLoader())?"":"Object Class and type Class are from different loaders.";
296                 throw new IllegalArgumentException("Object of class '"+obj.getClass().getCanonicalName()+"' is not of type '" + oClass.getCanonicalName()+"'. "+loaders+" in "+_url);
297             }
298             configure(obj,_root,0);
299             return obj;
300         }
301 
302         public Object configure() throws Exception
303         {
304             Class<?> oClass = nodeClass(_root);
305 
306             String id = _root.getAttribute("id");
307             Object obj = id == null?null:_configuration.getIdMap().get(id);
308 
309             int index = 0;
310             if (obj == null && oClass != null)
311             {
312                 index = _root.size();
313                 Map<String, Object> namedArgMap = new HashMap<>();
314 
315                 List<Object> arguments = new LinkedList<>();
316                 for (int i = 0; i < _root.size(); i++)
317                 {
318                     Object o = _root.get(i);
319                     if (o instanceof String)
320                     {
321                         continue;
322                     }
323                     XmlParser.Node node = (XmlParser.Node)o;
324 
325                     if (!(node.getTag().equals("Arg")))
326                     {
327                         index = i;
328                         break;
329                     }
330                     else
331                     {
332                         String namedAttribute = node.getAttribute("name");
333                         Object value=value(obj,(XmlParser.Node)o);
334                         if (namedAttribute != null)
335                             namedArgMap.put(namedAttribute,value);
336                         arguments.add(value);
337                     }
338                 }
339 
340                 try
341                 {
342                     if (namedArgMap.size() > 0)
343                         obj = TypeUtil.construct(oClass, arguments.toArray(), namedArgMap);
344                     else
345                         obj = TypeUtil.construct(oClass, arguments.toArray());
346                 }
347                 catch (NoSuchMethodException x)
348                 {
349                     throw new IllegalStateException("No suitable constructor on " + oClass, x);
350                 }
351             }
352             _configuration.initializeDefaults(obj);
353 
354             configure(obj, _root, index);
355             return obj;
356         }
357 
358         private static Class<?> nodeClass(XmlParser.Node node) throws ClassNotFoundException
359         {
360             String className = node.getAttribute("class");
361             if (className == null)
362                 return null;
363 
364             return Loader.loadClass(XmlConfiguration.class,className,true);
365         }
366 
367         /**
368          * Recursive configuration routine.
369          * This method applies the nested Set, Put, Call, etc. elements to the given object.
370          *
371          * @param obj the object to configure
372          * @param cfg the XML nodes of the configuration
373          * @param i the index of the XML nodes
374          * @throws Exception if the configuration fails
375          */
376         public void configure(Object obj, XmlParser.Node cfg, int i) throws Exception
377         {
378             String id = cfg.getAttribute("id");
379             if (id != null)
380                 _configuration.getIdMap().put(id,obj);
381 
382             // Object already constructed so skip any arguments
383             for (; i < cfg.size(); i++)
384             {
385                 Object o = cfg.get(i);
386                 if (o instanceof String)
387                     continue;
388                 XmlParser.Node node = (XmlParser.Node)o;
389                 if ("Arg".equals(node.getTag()))
390                 {
391                     LOG.warn("Ignored arg: "+node);
392                     continue;
393                 }
394                 break;
395             }
396             
397             // Process real arguments
398             for (; i < cfg.size(); i++)
399             {
400                 Object o = cfg.get(i);
401                 if (o instanceof String)
402                     continue;
403                 XmlParser.Node node = (XmlParser.Node)o;
404 
405                 try
406                 {
407                     String tag = node.getTag();
408                     switch (tag)
409                     {
410                         case "Set":
411                             set(obj, node);
412                             break;
413                         case "Put":
414                             put(obj, node);
415                             break;
416                         case "Call":
417                             call(obj, node);
418                             break;
419                         case "Get":
420                             get(obj, node);
421                             break;
422                         case "New":
423                             newObj(obj, node);
424                             break;
425                         case "Array":
426                             newArray(obj, node);
427                             break;
428                         case "Ref":
429                             refObj(obj, node);
430                             break;
431                         case "Property":
432                             propertyObj(node);
433                             break;
434                         default:
435                             throw new IllegalStateException("Unknown tag: " + tag + " in " + _url);
436                     }
437                 }
438                 catch (Exception e)
439                 {
440                     LOG.warn("Config error at " + node,e.toString()+" in "+_url);
441                     throw e;
442                 }
443             }
444         }
445 
446         /*
447          * 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.
448          * 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
449          * construct a suitable value from original value. @param obj
450          *
451          * @param node
452          */
453         private void set(Object obj, XmlParser.Node node) throws Exception
454         {
455             String attr = node.getAttribute("name");
456             String name = "set" + attr.substring(0,1).toUpperCase(Locale.ENGLISH) + attr.substring(1);
457             Object value = value(obj,node);
458             Object[] arg =
459             { value };
460 
461             Class<?> oClass = nodeClass(node);
462             if (oClass != null)
463                 obj = null;
464             else
465                 oClass = obj.getClass();
466 
467             Class<?>[] vClass =
468             { Object.class };
469             if (value != null)
470                 vClass[0] = value.getClass();
471 
472             if (LOG.isDebugEnabled())
473                 LOG.debug("XML " + (obj != null?obj.toString():oClass.getName()) + "." + name + "(" + value + ")");
474 
475             // Try for trivial match
476             try
477             {
478                 Method set = oClass.getMethod(name,vClass);
479                 set.invoke(obj,arg);
480                 return;
481             }
482             catch (IllegalArgumentException | IllegalAccessException | NoSuchMethodException e)
483             {
484                 LOG.ignore(e);
485             }
486 
487             // Try for native match
488             try
489             {
490                 Field type = vClass[0].getField("TYPE");
491                 vClass[0] = (Class<?>)type.get(null);
492                 Method set = oClass.getMethod(name,vClass);
493                 set.invoke(obj,arg);
494                 return;
495             }
496             catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException | NoSuchMethodException e)
497             {
498                 LOG.ignore(e);
499             }
500 
501             // Try a field
502             try
503             {
504                 Field field = oClass.getField(attr);
505                 if (Modifier.isPublic(field.getModifiers()))
506                 {
507                     field.set(obj,value);
508                     return;
509                 }
510             }
511             catch (NoSuchFieldException e)
512             {
513                 LOG.ignore(e);
514             }
515 
516             // Search for a match by trying all the set methods
517             Method[] sets = oClass.getMethods();
518             Method set = null;
519             for (int s = 0; sets != null && s < sets.length; s++)
520             {
521                 Class<?>[] paramTypes = sets[s].getParameterTypes();
522                 if (name.equals(sets[s].getName()) && paramTypes.length == 1)
523                 {
524                     // lets try it
525                     try
526                     {
527                         set = sets[s];
528                         sets[s].invoke(obj,arg);
529                         return;
530                     }
531                     catch (IllegalArgumentException | IllegalAccessException e)
532                     {
533                         LOG.ignore(e);
534                     }
535 
536                     try
537                     {
538                         for (Class<?> c : __supportedCollections)
539                             if (paramTypes[0].isAssignableFrom(c))
540                             {
541                                 sets[s].invoke(obj,convertArrayToCollection(value,c));
542                                 return;
543                             }
544                     }
545                     catch (IllegalAccessException e)
546                     {
547                         LOG.ignore(e);
548                     }
549                 }
550             }
551 
552             // Try converting the arg to the last set found.
553             if (set != null)
554             {
555                 try
556                 {
557                     Class<?> sClass = set.getParameterTypes()[0];
558                     if (sClass.isPrimitive())
559                     {
560                         for (int t = 0; t < __primitives.length; t++)
561                         {
562                             if (sClass.equals(__primitives[t]))
563                             {
564                                 sClass = __boxedPrimitives[t];
565                                 break;
566                             }
567                         }
568                     }
569                     Constructor<?> cons = sClass.getConstructor(vClass);
570                     arg[0] = cons.newInstance(arg);
571                     _configuration.initializeDefaults(arg[0]);
572                     set.invoke(obj,arg);
573                     return;
574                 }
575                 catch (NoSuchMethodException | IllegalAccessException | InstantiationException e)
576                 {
577                     LOG.ignore(e);
578                 }
579             }
580 
581             // No Joy
582             throw new NoSuchMethodException(oClass + "." + name + "(" + vClass[0] + ")");
583         }
584 
585         /**
586          * @param array the array to convert
587          * @param collectionType the desired collection type
588          * @return a collection of the desired type if the array can be converted
589          */
590         private static Collection<?> convertArrayToCollection(Object array, Class<?> collectionType)
591         {
592             Collection<?> collection = null;
593             if (array.getClass().isArray())
594             {
595                 if (collectionType.isAssignableFrom(ArrayList.class))
596                     collection = convertArrayToArrayList(array);
597                 else if (collectionType.isAssignableFrom(HashSet.class))
598                     collection = new HashSet<>(convertArrayToArrayList(array));
599                 else if (collectionType.isAssignableFrom(ArrayQueue.class))
600                 {
601                     ArrayQueue<Object> q= new ArrayQueue<>();
602                     q.addAll(convertArrayToArrayList(array));
603                     collection=q;
604                 }
605             }
606             if (collection==null)
607                 throw new IllegalArgumentException("Can't convert \"" + array.getClass() + "\" to " + collectionType);
608             return collection;
609         }
610 
611         private static ArrayList<Object> convertArrayToArrayList(Object array)
612         {
613             int length = Array.getLength(array);
614             ArrayList<Object> list = new ArrayList<>(length);
615             for (int i = 0; i < length; i++)
616                 list.add(Array.get(array,i));
617             return list;
618         }
619 
620         /*
621          * Call a put method.
622          *
623          * @param obj @param node
624          */
625         private void put(Object obj, XmlParser.Node node) throws Exception
626         {
627             if (!(obj instanceof Map))
628                 throw new IllegalArgumentException("Object for put is not a Map: " + obj);
629             @SuppressWarnings("unchecked")
630             Map<Object, Object> map = (Map<Object, Object>)obj;
631 
632             String name = node.getAttribute("name");
633             Object value = value(obj,node);
634             map.put(name,value);
635             if (LOG.isDebugEnabled())
636                 LOG.debug("XML " + obj + ".put(" + name + "," + value + ")");
637         }
638 
639         /*
640          * 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                 _configuration.getIdMap().put(id,obj);
679             return obj;
680         }
681 
682         /*
683          * 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
684          * 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
685          * class. i.e. we ignore any static methods in superclasses. @param obj
686          *
687          * @param node @return @exception Exception
688          */
689         private Object call(Object obj, XmlParser.Node node) throws Exception
690         {
691             String id = node.getAttribute("id");
692             Class<?> oClass = nodeClass(node);
693             if (oClass != null)
694                 obj = null;
695             else if (obj != null)
696                 oClass = obj.getClass();
697             if (oClass == null)
698                 throw new IllegalArgumentException(node.toString());
699             
700             int size = 0;
701             int argIndex = node.size();
702             for (int i = 0; i < node.size(); i++)
703             {
704                 Object o = node.get(i);
705                 if (o instanceof String)
706                     continue;
707                 if (!((XmlParser.Node)o).getTag().equals("Arg"))
708                 {
709                     argIndex = i;
710                     break;
711                 }
712                 size++;
713             }
714 
715             Object[] arg = new Object[size];
716             for (int i = 0, j = 0; j < size; i++)
717             {
718                 Object o = node.get(i);
719                 if (o instanceof String)
720                     continue;
721                 arg[j++] = value(obj,(XmlParser.Node)o);
722             }
723 
724             String method = node.getAttribute("name");
725             if (LOG.isDebugEnabled())
726                 LOG.debug("XML call " + method);
727 
728             try
729             {
730                 Object n= TypeUtil.call(oClass,method,obj,arg);
731                 if (id != null)
732                     _configuration.getIdMap().put(id,n);
733                 configure(n,node,argIndex);
734                 return n;
735             }
736             catch (NoSuchMethodException e)
737             {
738                 IllegalStateException ise = new IllegalStateException("No Method: " + node + " on " + oClass);
739                 ise.initCause(e);
740                 throw ise;
741             }
742         }
743 
744         /*
745          * Create a new value object.
746          *
747          * @param obj
748          * @param node
749          *
750          * @return @exception Exception
751          */
752         private Object newObj(Object obj, XmlParser.Node node) throws Exception
753         {
754             Class<?> oClass = nodeClass(node);
755             int argIndex = node.size();
756             
757             Map<String, Object> namedArgMap = new HashMap<>();
758             List<Object> arguments = new LinkedList<>();
759             XmlParser.Node child;
760 
761             // Find the <Arg> elements
762             for (int i = 0; i < node.size(); i++)
763             {
764                 Object o = node.get(i);
765                 if (o instanceof String)
766                 {
767                     // Skip raw String nodes
768                     continue;
769                 }
770                 
771                 child = (XmlParser.Node)o;
772                 if(child.getTag().equals("Arg"))
773                 {
774                     String namedAttribute = child.getAttribute("name");
775                     Object value=value(obj,child);
776                     if (namedAttribute != null)
777                     {
778                         // named arguments
779                         namedArgMap.put(namedAttribute,value);
780                     }
781                     // raw arguments
782                     arguments.add(value);
783                 } else {
784                     // The first non <Arg> child is the start of 
785                     // elements that configure the class, such as
786                     // <Set> and <Call> nodes
787                     argIndex = i;
788                     break;
789                 }
790             }
791 
792             if (LOG.isDebugEnabled())
793                 LOG.debug("XML new " + oClass);
794 
795             Object n;
796             try
797             {
798                 if (namedArgMap.size() > 0)
799                 {
800                    LOG.debug("using named mapping");
801                    n = TypeUtil.construct(oClass, arguments.toArray(), namedArgMap);
802                 }
803                 else
804                 {
805                     LOG.debug("using normal mapping");
806                     n = TypeUtil.construct(oClass, arguments.toArray());
807                 }
808             }
809             catch (NoSuchMethodException e)
810             {
811                 throw new IllegalStateException("No suitable constructor: " + node + " on " + obj);
812             }
813 
814             _configuration.initializeDefaults(n);
815             configure(n,node,argIndex);
816             return n;
817         }
818 
819         /*
820          * Reference an id value object.
821          *
822          * @param obj @param node @return @exception NoSuchMethodException @exception ClassNotFoundException @exception InvocationTargetException
823          */
824         private Object refObj(Object obj, XmlParser.Node node) throws Exception
825         {
826             String refid = node.getAttribute("refid");
827             if (refid==null)
828                 refid = node.getAttribute("id");
829             obj = _configuration.getIdMap().get(refid);
830             if (obj == null && node.size()>0)
831                 throw new IllegalStateException("No object for refid=" + refid);
832             configure(obj,node,0);
833             return obj;
834         }
835 
836         /*
837          * Create a new array object.
838          */
839         private Object newArray(Object obj, XmlParser.Node node) throws Exception
840         {
841             // Get the type
842             Class<?> aClass = java.lang.Object.class;
843             String type = node.getAttribute("type");
844             final String id = node.getAttribute("id");
845             if (type != null)
846             {
847                 aClass = TypeUtil.fromName(type);
848                 if (aClass == null)
849                 {
850                     switch (type)
851                     {
852                         case "String":
853                             aClass = String.class;
854                             break;
855                         case "URL":
856                             aClass = URL.class;
857                             break;
858                         case "InetAddress":
859                             aClass = InetAddress.class;
860                             break;
861                         default:
862                             aClass = Loader.loadClass(XmlConfiguration.class, type, true);
863                             break;
864                     }
865                 }
866             }
867 
868             Object al = null;
869 
870             for (Object nodeObject : node)
871             {
872                 XmlParser.Node item = (Node)nodeObject;
873                 String nid = item.getAttribute("id");
874                 Object v = value(obj,item);
875                 al = LazyList.add(al,(v == null && aClass.isPrimitive())?0:v);
876                 if (nid != null)
877                     _configuration.getIdMap().put(nid,v);
878             }
879 
880             Object array = LazyList.toArray(al,aClass);
881             if (id != null)
882                 _configuration.getIdMap().put(id,array);
883             return array;
884         }
885 
886         /*
887          * Create a new map object.
888          */
889         private Object newMap(Object obj, XmlParser.Node node) throws Exception
890         {
891             String id = node.getAttribute("id");
892 
893             Map<Object, Object> map = new HashMap<>();
894             if (id != null)
895                 _configuration.getIdMap().put(id,map);
896 
897             for (Object o : node)
898             {
899                 if (o instanceof String)
900                     continue;
901                 XmlParser.Node entry = (XmlParser.Node)o;
902                 if (!entry.getTag().equals("Entry"))
903                     throw new IllegalStateException("Not an Entry");
904 
905                 XmlParser.Node key = null;
906                 XmlParser.Node value = null;
907 
908                 for (Object object : entry)
909                 {
910                     if (object instanceof String)
911                         continue;
912                     XmlParser.Node item = (XmlParser.Node)object;
913                     if (!item.getTag().equals("Item"))
914                         throw new IllegalStateException("Not an Item");
915                     if (key == null)
916                         key = item;
917                     else
918                         value = item;
919                 }
920 
921                 if (key == null || value == null)
922                     throw new IllegalStateException("Missing Item in Entry");
923                 String kid = key.getAttribute("id");
924                 String vid = value.getAttribute("id");
925 
926                 Object k = value(obj,key);
927                 Object v = value(obj,value);
928                 map.put(k,v);
929 
930                 if (kid != null)
931                     _configuration.getIdMap().put(kid,k);
932                 if (vid != null)
933                     _configuration.getIdMap().put(vid,v);
934             }
935 
936             return map;
937         }
938 
939         /*
940          * Get a Property.
941          *
942          * @param node
943          * @return
944          * @exception Exception
945          */
946         private Object propertyObj(XmlParser.Node node) throws Exception
947         {
948             String id = node.getAttribute("id");
949             String name = node.getAttribute("name");
950             String defaultValue = node.getAttribute("default");
951             Object prop;
952             Map<String,String> property_map=_configuration.getProperties();
953             if (property_map != null && property_map.containsKey(name))
954                 prop = property_map.get(name);
955             else
956                 prop = defaultValue;
957             if (id != null)
958                 _configuration.getIdMap().put(id,prop);
959             if (prop != null)
960                 configure(prop,node,0);
961             return prop;
962         }
963 
964         /*
965          * Get the value of an element. If no value type is specified, then white space is trimmed out of the value. If it contains multiple value elements they
966          * are added as strings before being converted to any specified type. @param node
967          */
968         private Object value(Object obj, XmlParser.Node node) throws Exception
969         {
970             Object value;
971 
972             // Get the type
973             String type = node.getAttribute("type");
974 
975             // Try a ref lookup
976             String ref = node.getAttribute("ref");
977             if (ref != null)
978             {
979                 value = _configuration.getIdMap().get(ref);
980             }
981             else
982             {
983                 // handle trivial case
984                 if (node.size() == 0)
985                 {
986                     if ("String".equals(type))
987                         return "";
988                     return null;
989                 }
990 
991                 // Trim values
992                 int first = 0;
993                 int last = node.size() - 1;
994 
995                 // Handle default trim type
996                 if (type == null || !"String".equals(type))
997                 {
998                     // Skip leading white
999                     Object item;
1000                     while (first <= last)
1001                     {
1002                         item = node.get(first);
1003                         if (!(item instanceof String))
1004                             break;
1005                         item = ((String)item).trim();
1006                         if (((String)item).length() > 0)
1007                             break;
1008                         first++;
1009                     }
1010 
1011                     // Skip trailing white
1012                     while (first < last)
1013                     {
1014                         item = node.get(last);
1015                         if (!(item instanceof String))
1016                             break;
1017                         item = ((String)item).trim();
1018                         if (((String)item).length() > 0)
1019                             break;
1020                         last--;
1021                     }
1022 
1023                     // All white, so return null
1024                     if (first > last)
1025                         return null;
1026                 }
1027 
1028                 if (first == last)
1029                     // Single Item value
1030                     value = itemValue(obj,node.get(first));
1031                 else
1032                 {
1033                     // Get the multiple items as a single string
1034                     StringBuilder buf = new StringBuilder();
1035                     for (int i = first; i <= last; i++)
1036                     {
1037                         Object item = node.get(i);
1038                         buf.append(itemValue(obj,item));
1039                     }
1040                     value = buf.toString();
1041                 }
1042             }
1043 
1044             // Untyped or unknown
1045             if (value == null)
1046             {
1047                 if ("String".equals(type))
1048                     return "";
1049                 return null;
1050             }
1051 
1052             // Try to type the object
1053             if (type == null)
1054             {
1055                 if (value instanceof String)
1056                     return ((String)value).trim();
1057                 return value;
1058             }
1059 
1060             if (isTypeMatchingClass(type,String.class))
1061                 return value.toString();
1062 
1063             Class<?> pClass = TypeUtil.fromName(type);
1064             if (pClass != null)
1065                 return TypeUtil.valueOf(pClass,value.toString());
1066 
1067             if (isTypeMatchingClass(type,URL.class))
1068             {
1069                 if (value instanceof URL)
1070                     return value;
1071                 try
1072                 {
1073                     return new URL(value.toString());
1074                 }
1075                 catch (MalformedURLException e)
1076                 {
1077                     throw new InvocationTargetException(e);
1078                 }
1079             }
1080 
1081             if (isTypeMatchingClass(type,InetAddress.class))
1082             {
1083                 if (value instanceof InetAddress)
1084                     return value;
1085                 try
1086                 {
1087                     return InetAddress.getByName(value.toString());
1088                 }
1089                 catch (UnknownHostException e)
1090                 {
1091                     throw new InvocationTargetException(e);
1092                 }
1093             }
1094 
1095             for (Class<?> collectionClass : __supportedCollections)
1096             {
1097                 if (isTypeMatchingClass(type,collectionClass))
1098                     return convertArrayToCollection(value,collectionClass);
1099             }
1100 
1101             throw new IllegalStateException("Unknown type " + type);
1102         }
1103 
1104         private static boolean isTypeMatchingClass(String type, Class<?> classToMatch)
1105         {
1106             return classToMatch.getSimpleName().equalsIgnoreCase(type) || classToMatch.getName().equals(type);
1107         }
1108 
1109         /*
1110          * Get the value of a single element. @param obj @param item @return @exception Exception
1111          */
1112         private Object itemValue(Object obj, Object item) throws Exception
1113         {
1114             // String value
1115             if (item instanceof String)
1116                 return item;
1117 
1118             XmlParser.Node node = (XmlParser.Node)item;
1119             String tag = node.getTag();
1120             if ("Call".equals(tag))
1121                 return call(obj,node);
1122             if ("Get".equals(tag))
1123                 return get(obj,node);
1124             if ("New".equals(tag))
1125                 return newObj(obj,node);
1126             if ("Ref".equals(tag))
1127                 return refObj(obj,node);
1128             if ("Array".equals(tag))
1129                 return newArray(obj,node);
1130             if ("Map".equals(tag))
1131                 return newMap(obj,node);
1132             if ("Property".equals(tag))
1133                 return propertyObj(node);
1134             if ("SystemProperty".equals(tag))
1135             {
1136                 String name = node.getAttribute("name");
1137                 String defaultValue = node.getAttribute("default");
1138                 return System.getProperty(name,defaultValue);
1139             }
1140             if ("Env".equals(tag))
1141             {
1142                 String name = node.getAttribute("name");
1143                 String defaultValue = node.getAttribute("default");
1144                 String value=System.getenv(name);
1145                 return value==null?defaultValue:value;
1146             }
1147 
1148             LOG.warn("Unknown value tag: " + node,new Throwable());
1149             return null;
1150         }
1151     }
1152 
1153     /**
1154      * Run the XML configurations as a main application. The command line is used to obtain properties files (must be named '*.properties') and XmlConfiguration
1155      * files.
1156      * <p>
1157      * Any property file on the command line is added to a combined Property instance that is passed to each configuration file via
1158      * {@link XmlConfiguration#getProperties()}.
1159      * <p>
1160      * Each configuration file on the command line is used to create a new XmlConfiguration instance and the {@link XmlConfiguration#configure()} method is used
1161      * to create the configured object. If the resulting object is an instance of {@link LifeCycle}, then it is started.
1162      * <p>
1163      * Any IDs created in a configuration are passed to the next configuration file on the command line using {@link #getIdMap()}.
1164      * This allows objects with IDs created in one config file to be referenced in subsequent config files on the command line.
1165      *
1166      * @param args
1167      *            array of property and xml configuration filenames or {@link Resource}s.
1168      * @throws Exception if the XML configurations cannot be run
1169      */
1170     public static void main(final String... args) throws Exception
1171     {
1172         final AtomicReference<Throwable> exception = new AtomicReference<>();
1173 
1174         AccessController.doPrivileged(new PrivilegedAction<Object>()
1175         {
1176             public Object run()
1177             {
1178                 try
1179                 {
1180 
1181                     Properties properties = null;
1182 
1183                     // Look for properties from start.jar
1184                     try
1185                     {
1186                         Class<?> config = XmlConfiguration.class.getClassLoader().loadClass("org.eclipse.jetty.start.Config");
1187                         properties = (Properties)config.getMethod("getProperties").invoke(null);
1188                         LOG.debug("org.eclipse.jetty.start.Config properties = {}",properties);
1189                     }
1190                     catch (NoClassDefFoundError | ClassNotFoundException e)
1191                     {
1192                         LOG.ignore(e);
1193                     }
1194                     catch (Exception e)
1195                     {
1196                         LOG.warn(e);
1197                     }
1198 
1199                     // If no start.config properties, use clean slate
1200                     if (properties == null)
1201                     {
1202                         properties = new Properties();
1203                         // Add System Properties
1204                         Enumeration<?> ensysprop = System.getProperties().propertyNames();
1205                         while (ensysprop.hasMoreElements())
1206                         {
1207                             String name = (String)ensysprop.nextElement();
1208                             properties.put(name,System.getProperty(name));
1209                         }
1210                     }
1211 
1212                     // For all arguments, load properties
1213                     for (String arg : args)
1214                     {
1215                         if (arg.toLowerCase(Locale.ENGLISH).endsWith(".properties"))
1216                             properties.load(Resource.newResource(arg).getInputStream());
1217                     }
1218 
1219                     // For all arguments, parse XMLs
1220                     XmlConfiguration last = null;
1221                     Object[] obj = new Object[args.length];
1222                     for (int i = 0; i < args.length; i++)
1223                     {
1224                         if (!args[i].toLowerCase(Locale.ENGLISH).endsWith(".properties"))
1225                         {
1226                             XmlConfiguration configuration = new XmlConfiguration(Resource.newResource(args[i]).getURL());
1227                             if (last != null)
1228                                 configuration.getIdMap().putAll(last.getIdMap());
1229                             if (properties.size() > 0)
1230                             {
1231                                 Map<String, String> props = new HashMap<>();
1232                                 for (Object key : properties.keySet())
1233                                 {
1234                                     props.put(key.toString(),String.valueOf(properties.get(key)));
1235                                 }
1236                                 configuration.getProperties().putAll(props);
1237                             }
1238                             obj[i] = configuration.configure();
1239                             last = configuration;
1240                         }
1241                     }
1242 
1243                     // For all objects created by XmlConfigurations, start them if they are lifecycles.
1244                     for (int i = 0; i < args.length; i++)
1245                     {
1246                         if (obj[i] instanceof LifeCycle)
1247                         {
1248                             LifeCycle lc = (LifeCycle)obj[i];
1249                             if (!lc.isRunning())
1250                                 lc.start();
1251                         }
1252                     }
1253                 }
1254                 catch (Exception e)
1255                 {
1256                     LOG.debug(Log.EXCEPTION,e);
1257                     exception.set(e);
1258                 }
1259                 return null;
1260             }
1261         });
1262 
1263         Throwable th = exception.get();
1264         if (th != null)
1265         {
1266             if (th instanceof RuntimeException)
1267                 throw (RuntimeException)th;
1268             else if (th instanceof Exception)
1269                 throw (Exception)th;
1270             else if (th instanceof Error)
1271                 throw (Error)th;
1272             throw new Error(th);
1273         }
1274     }
1275 }