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