View Javadoc

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