View Javadoc

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