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