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