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