001    /*
002    @license.text@
003     */
004    package biz.hammurapi.config;
005    
006    import java.io.File;
007    import java.io.IOException;
008    import java.io.InputStream;
009    import java.io.Reader;
010    import java.lang.reflect.Constructor;
011    import java.lang.reflect.Field;
012    import java.lang.reflect.InvocationTargetException;
013    import java.lang.reflect.Method;
014    import java.lang.reflect.Modifier;
015    import java.net.MalformedURLException;
016    import java.net.URL;
017    import java.util.Collection;
018    import java.util.Collections;
019    import java.util.HashMap;
020    import java.util.Iterator;
021    import java.util.LinkedList;
022    import java.util.Map;
023    
024    import javax.xml.parsers.DocumentBuilderFactory;
025    
026    import org.w3c.dom.Element;
027    import org.w3c.dom.Node;
028    import org.w3c.dom.NodeList;
029    import org.xml.sax.InputSource;
030    
031    import biz.hammurapi.config.adapters.File2InputStreamConfigurableAdapter;
032    import biz.hammurapi.config.adapters.InputStream2DomConfigurableAdapter;
033    import biz.hammurapi.config.adapters.URL2InputStreamConfigurableAdapter;
034    import biz.hammurapi.convert.CompositeConverter;
035    import biz.hammurapi.xml.dom.DOMUtils;
036    
037    
038    /**
039     * Creates and configures objects from DOM {@link org.w3c.dom.Element} (XML file).
040     * DOM Element can be read from InputStream, File or URL.
041     * Instantiation and configuration happens as follows:<P/>
042     * <B>Instantiation</B>
043     * <ul>
044     *      <li>If there is no <code>'type'</code> attribute then type defaults to 
045     * {@link java.lang.String} and text of the element will be returned. 
046     * E.g. &lt;name&gt;Pavel&lt;/name&gt; will yield string 'Pavel'. <code>'type'</code>
047     * attribute name can be changed through {@link biz.hammurapi.config.DomConfigInfo#setCodeExpression(String)}
048     * method. Create {@link biz.hammurapi.config.DomConfigInfo}, change code expression and then
049     * use {@link #DomConfigFactory(DomConfigInfo)} to instantiate DomConfigFactory.
050     * </li>
051     * 
052     * <li>Otherwise class specified in <code>'type'</code> attribute will be loaded and
053     * verified by classAcceptor (if any)</li>
054     * 
055     * <li>If there is no nested <code>'constructor'</code>' element and element text is blank then default
056     * constructor will be used</li>
057     * 
058     * <li>If there is no nested <code>'constructor'</code>' element and element text is not blank then constructor which takes a single argument of type
059     * java.lang.String will be used</li>  
060     * 
061     * <li>If there is nested <code>'constructor'</code> element then <code>'arg'</code> elements of
062     * <code>'constructor'</code> element are iterated to create a list of arguments.
063     * Arguments are constructed in the same way as described here. <code>'arg'</code> element
064     * also supports <code>'context-ref'</code> attribute. If this attribute is set argument
065     * value will be taken from context entry set by {@link DomConfigFactory#setContextEntry(String, Object)} method
066     * </li>
067     * </ul>
068     * <P/>
069     * 
070     * <B>Configuration</B>
071     * <ul>
072     * <li>If element has attribute <code>'url'</code> and instantiated object (instance) 
073     * is instance of {@link biz.hammurapi.config.URLConfigurable} then {@link biz.hammurapi.config.URLConfigurable#configure(URL, Map)}
074     * is invoked to configure instance</li>
075     * 
076     * <li>If element has attribute <code>'file'</code> and instance 
077     * is instance of {@link biz.hammurapi.config.FileConfigurable} then {@link biz.hammurapi.config.FileConfigurable#configure(File, Map)}
078     * is invoked to configure instance</li>
079     * 
080     * <li>If instance is instance of 
081     * {@link biz.hammurapi.config.InputStreamConfigurable} 
082     * then <ul>
083     *      <li>If element has attribute <code>'url'</code> then that url is opened as InsputStream</li>
084     *      <li>If element has attribute <code>'file'</code> then that file is opened as InputStream</li>
085     *      <li>If element has attribute <code>'resource'</code> then that resource is opened as InputStream. 
086     * Instance's class is used to obtain resource which allows to use relative resource names.</li>
087     * </ul> 
088     * then that InputStream is passed to 
089     * {@link biz.hammurapi.config.InputStreamConfigurable#configure(InputStream, Map)}
090     * to configure instance. If none of aforementioned attributes is present then ConfigurationException is thrown.</li>
091     * 
092     * <li>If instance is instance of {@link biz.hammurapi.config.DomConfigurable} then
093     * <ul>
094     *      <li>If element has attribute <code>'url'</code> then that url is opened as InsputStream and parsed to DOM tree</li>
095     *      <li>If element has attribute <code>'file'</code> then that file is opened as InputStream and parsed to DOM tree</li>
096     *      <li>If element has attribute <code>'resource'</code> then that resource is opened as InputStream and parsed to DOM tree. 
097     *      Instance's class is used to obtain resource which allows to use relative resource names.</li>
098     * </ul>
099     * then that parsed document is passed to {@link biz.hammurapi.config.DomConfigurable#configure(Node, Context)}.
100     * If none of the aforementioned attributes is present then element itself is passed to
101     * {@link biz.hammurapi.config.DomConfigurable#configure(Node, Context)}</li>
102     * 
103     * <li>If instance is instance of {@link biz.hammurapi.config.Parameterizable} then
104     *      <ul>
105     *      <li>If there are subelements <code>'parameter'</code> with attribute <code>'name'</code>
106     *      then value of <code>'name'</code> is used as parameter name</li>
107     *      <li>Otherwise names of nested elements used as parameter names</li>
108     *      </ul>
109     *  Parameter values are evaluated in the same way as <code>'arg'</code> elements for
110     * constructors.
111     *      {@link biz.hammurapi.config.Parameterizable#setParameter(String, Object)} is invoked for each of parameter elements.
112     * 
113     *  {@link biz.hammurapi.config.Parameterizable#setParameter(String, Object)} is also invoked for context entries 
114     * with names which did not match with parameter names. E.g. if there are two context entries 'age' and 'name' and parameter
115     * 'name' then setParameter("name", <I>value of parameter 'name'</I>) will be invoked and after that 
116     * setParameter("age", <I>value of context entry 'age'</I>) will be invoked.
117     * </li>
118     * 
119     * <li>If instance is instance of {@link biz.hammurapi.config.StringConfigurable} then element text is passed to
120     * {@link StringConfigurable#configure(String, Map)} method</li>
121     * 
122     * <li>If instance is instance of {@link java.util.Map} then <code>'entry'</code> subelements are iterated; <code>'key'</code>
123     * (Configurable through {@link biz.hammurapi.config.DomConfigInfo}) and <code>'value'</code>
124     * (Configurable through {@link biz.hammurapi.config.DomConfigInfo}) subelements are evaluated in the same way as 
125     * <code>'arg'</code> constructors subelements and put to instance by {@link java.util.Map#put(java.lang.Object, java.lang.Object)}</li>
126     * 
127     * <li>If instance is instance of {@link java.util.Collection} then <code>'element'</code> subelements are iterated, elements
128     * are istantiated in the same way as constructor arguments and then placed into instance by invoking {@link java.util.Collection#add(java.lang.Object)}
129     * method.</li>
130     * 
131     * <li>If none of above conditions are met then reflection is used to inject values into instance fields/properties in a similar way as parameters for 
132     * {@link biz.hammurapi.config.Parameterizable} are set. Special note about injection: If field type or setter parameter type (target type) is compatible with 
133     * instantiated value then the value is used as is. Otherwise if target type is compatible with source XML Element then the element is used. If value is instance of 
134     * {@link biz.hammurapi.config.Wrapper} and wrapper's master is compatible with the target type then the master is used. If wrapper is also a component then its setOwner() and start() methods
135     * are invoked before obtaining master. If none of aforementioned conditions are true then value is converted to target type.
136     * using {@link biz.hammurapi.convert.CompositeConverter}.</li> 
137     * 
138     * <li>If object acceptor is not null then its {@link biz.hammurapi.config.ObjectAcceptor#accept(Object)} is invoked
139     * to validate that object has been constructed and configured correctly</li>
140     * 
141     * <li>If instance is instance of {@link biz.hammurapi.config.Validatable} then {@link biz.hammurapi.config.Validatable#validate()} is
142     * invoked for the instance to validate itself. 
143     * </ul>
144     * 
145     * <B>Examples</B>
146     * <OL>
147     * <li><CODE>&lt;name&gt;Pavel&lt;/name&gt;</CODE> will yield java.lang.String with value 'Pavel'</li>
148     * <li><CODE>&lt;age type="java.lang.Integer"&gt;33&lt;/age&gt;</CODE> will yield java.lang.Integer with value '33'</li>
149     * <li><CODE>&lt;config type="org.myself.myproject.MyConfig" url="http://myproject.myself.org/MyConfig.xml"/&gt;</CODE> will load 
150     * configuration from URL and configure MyConfig object</li>
151     * <li><PRE>&lt;config type="org.myself.myproject.MyParameterizableConfig"&gt;
152     *     &lt;parameter name="pi" type="java.lang.Double"&gt;3.14159&lt;/parameter&gt;
153     * &lt;/config&gt;</PRE> will create MyParameterizableConfig object and then invoke its setParameter() method if MyParameterizableConfig 
154     * implements {@link biz.hammurapi.config.Parameterizable} or invoke setPi() method if there is such method. In lenient mode
155     * nothing will happen if there is no setPi() method. Otherwise exception will be thrown.</li>
156     * <li><PRE>&lt;config type="org.myself.myproject.MyParameterizableConfig"&gt;
157     *     &lt;pi type="java.lang.Double"&gt;3.14159&lt;/pi&gt;
158     * &lt;/config&gt;</PRE> same as above.</li>
159     * </OL>
160     * 
161     * It is recommended to use XML Beans generated classes instead of this factory.
162     * 
163     * @author Pavel Vlasov
164     * @version $Revision: 1.12 $
165     */
166    public class DomConfigFactory {
167            public static final String CODE_EXPRESSION = "@type";
168            public static final String MAP_KEY_EXPRESSION = "key";
169            public static final String MAP_VALUE_EXPRESSION = "value";      
170            
171            private static final String CONTEXT_REF = "context-ref";
172            public static final Map PRIMITIVES;
173            private Context context;
174            
175            static {
176                    Map primitives=new HashMap();
177                    primitives.put("boolean", Boolean.TYPE);
178                    primitives.put("byte", Byte.TYPE);
179                    primitives.put("char", Character.TYPE);
180                    primitives.put("double", Double.TYPE);
181                    primitives.put("float", Float.TYPE);
182                    primitives.put("int", Integer.TYPE);
183                    primitives.put("long", Long.TYPE);
184                    primitives.put("short", Short.TYPE);            
185                    PRIMITIVES=Collections.unmodifiableMap(primitives);
186            }
187            
188            private ClassLoader classLoader;
189    
190            /**
191             * Default constructor
192             */
193            public DomConfigFactory() {
194                    super();
195            }
196    
197            /**
198             * Default constructor
199             */
200            public DomConfigFactory(Context context) {
201                    super();
202                    this.context=context;
203            }
204    
205            public DomConfigFactory(ClassLoader classLoader) {              
206                    super();
207                    this.classLoader=classLoader;
208            }
209            
210            public DomConfigFactory(ClassLoader classLoader, Context context) {             
211                    super();
212                    this.classLoader=classLoader;
213                    this.context=context;
214            }
215                    
216            /**
217             * Creates object. Same as create(node, null, null)
218             * @param node
219             * @return
220             * @throws ConfigurationException
221             */
222            public Object create(Node node) throws ConfigurationException {
223                    return create(node, null, null);
224            }
225            
226            /**
227             * Parses file and returns object. Same as create(file, xPath, null, null)
228             * @param file XML configuration file
229             * @param xPath XPath expression, can be null
230             * @return configured object
231             */
232            public Object create(File file, String xPath) throws ConfigurationException, IOException {
233                    return create(file, xPath, null, null);
234            }
235            
236            /**
237             * Parses file and returns object
238             * @param file XML configuration file
239             * @param xPath XPath expression, can be null
240             * @param classAcceptor Class acceptor, validates that class about to be instantiated is 'the right one'
241             * @param objectAcceptor Object acceptor, validates instantiated object.
242             * @return Configured object
243             */
244            public Object create(File file, String xPath, ClassAcceptor classAcceptor, ObjectAcceptor objectAcceptor) throws ConfigurationException, IOException {
245                    try {
246                            Node node = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(file).getDocumentElement();
247                            if (xPath!=null) {
248                                    node=DOMUtils.selectSingleNode(node, xPath);
249                            }                       
250                            return create(node, classAcceptor, objectAcceptor);
251                    } catch (Exception e) {
252                            throw new ConfigurationException(e);
253                    }
254            }
255            
256            /**
257             * Same as create(in, xPath, null, null)
258             * @param in Input stream
259             * @param xPath XPath expression, can be null
260             * @return Configured object
261             * @throws ConfigurationException
262             * @throws IOException
263             */
264            public Object create(InputStream in, String xPath) throws ConfigurationException, IOException {
265                    return create(in, xPath, null, null);
266            }
267            
268            /**
269             * Creates and configures object from InputStream
270             * @param in Input stream
271             * @param xPath XPath expression, can be null
272             * @param classAcceptor
273             * @param objectAcceptor
274             * @return Configured object
275             * @throws ConfigurationException
276             * @throws IOException
277             */
278            public Object create(InputStream in, String xPath, ClassAcceptor classAcceptor, ObjectAcceptor objectAcceptor) throws ConfigurationException, IOException {
279                    try {
280                            Node node = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(in).getDocumentElement();
281                            if (xPath!=null) {
282                                    node=DOMUtils.selectSingleNode(node, xPath);
283                            }
284                            return create(node, classAcceptor, objectAcceptor);
285                    } catch (Exception e) {
286                            throw new ConfigurationException(e);
287                    }               
288            }
289            
290            /**
291             * Same as create(in, xPath, null, null)
292             * @param in Reader
293             * @param xPath XPath expression, can be null
294             * @return Configured object
295             * @throws ConfigurationException
296             * @throws IOException
297             */
298            public Object create(Reader in, String xPath) throws ConfigurationException, IOException {
299                    return create(in, xPath, null, null);
300            }
301            
302            /**
303             * Creates and configures object from InputStream
304             * @param in Reader
305             * @param xPath XPath expression, can be null
306             * @param classAcceptor
307             * @param objectAcceptor
308             * @return Configured object
309             * @throws ConfigurationException
310             * @throws IOException
311             */
312            public Object create(Reader in, String xPath, ClassAcceptor classAcceptor, ObjectAcceptor objectAcceptor) throws ConfigurationException, IOException {
313                    try {
314                            Node node = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new InputSource(in)).getDocumentElement();
315                            if (xPath!=null) {
316                                    node=DOMUtils.selectSingleNode(node, xPath);
317                            }
318                            return create(node, classAcceptor, objectAcceptor);
319                    } catch (Exception e) {
320                            throw new ConfigurationException(e);
321                    }               
322            }
323            
324            /**
325             * Same as create(url, xPath, null, null)
326             * @param url URL to read configuration from
327             * @param xPath XPath expression, can be null
328             * @return Configured object
329             * @throws ConfigurationException
330             * @throws IOException
331             */
332            public Object create(URL url, String xPath) throws ConfigurationException, IOException {
333                    return create(url, xPath, null, null);
334            }
335            
336            /**
337             * Creates and configures object from URL
338             * @param url Url
339             * @param xPath XPath expression, can be null
340             * @param classAcceptor
341             * @param objectAcceptor
342             * @return Configured object
343             * @throws ConfigurationException
344             * @throws IOException
345             */
346            public Object create(URL url, String xPath, ClassAcceptor classAcceptor, ObjectAcceptor objectAcceptor) throws ConfigurationException, IOException {
347                    try {
348                            Node node = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(url.openStream()).getDocumentElement();
349                            if (xPath!=null) {
350                                    node=DOMUtils.selectSingleNode(node, xPath);
351                            }                       
352                            return create(node, classAcceptor, objectAcceptor);
353                    } catch (Exception e) {
354                            throw new ConfigurationException(e);
355                    }               
356            }
357            
358            /**
359             * Creates and configures object
360             * @param node
361             * @param classAcceptor
362             * @param objectAcceptor
363             * @param cxpa Cached XPath API to accelerate XPath expressions evaluation.
364             * @return Configured object
365             * @throws ConfigurationException
366             */
367            public Object create(Node node, ClassAcceptor classAcceptor, ObjectAcceptor objectAcceptor) throws ConfigurationException {             
368                    try {                   
369                            String className=DOMUtils.eval(node, CODE_EXPRESSION).toString();                       
370                            if (className.trim().length()==0) {
371                                    if (classAcceptor!=null) {
372                                            classAcceptor.accept(String.class);
373                                    }
374                                    return DOMUtils.eval(node, "text()").toString();
375                            } 
376                            
377                            Class clazz = forName(className);
378                            if (classAcceptor!=null) {
379                                    classAcceptor.accept(clazz);
380                            }
381                            
382                            Object instance;
383                            Node constructorNode=DOMUtils.selectSingleNode(node, "constructor");
384                            if (constructorNode==null) {
385                                    String body=DOMUtils.eval(node, "text()").toString().trim();
386                                    if (body.length()==0 || DomConfigurable.class.isAssignableFrom(clazz)) {
387                                            instance=clazz.newInstance();
388                                    } else {
389                                            Constructor c=clazz.getConstructor(new Class[] {String.class});
390                                            instance=c.newInstance(new Object[] {body});
391                                    }
392                            } else {
393                                    NodeList argList=DOMUtils.selectNodeList(constructorNode, "arg");
394                                    Class[] argTypes=new Class[argList.getLength()];
395                                    for (int i=0; i<argList.getLength(); i++) {
396                                            String argTypeName=DOMUtils.eval(argList.item(i), CODE_EXPRESSION).toString();
397                                            if (argTypeName.trim().length()==0) {
398                                                    argTypes[i]=String.class;                                               
399                                            } else {
400                                                    argTypes[i]=(Class) PRIMITIVES.get(argTypeName);
401                                                    if (argTypes[i]==null) {
402                                                            argTypes[i]=forName(argTypeName);
403                                                    }
404                                            }
405                                    }                                                               
406                                    
407                                    Constructor constructor=clazz.getConstructor(argTypes);
408                                    Object[] args=new Object[argList.getLength()];
409                                    for (int i=0; i<argList.getLength(); i++) {
410                                            Element argElement = ((Element) argList.item(i));
411                                            if (argTypes[i].isPrimitive()) {
412                                                    args[i] = CompositeConverter.getDefaultConverter().convert(
413                                                                    DOMUtils.eval(argList.item(i), "text()").toString(),
414                                                                    argTypes[i],
415                                                                    false);                                                 
416                                            } else if (argElement.hasAttribute(CONTEXT_REF)) {
417                                                    args[i]=context.get(argElement.getAttribute(CONTEXT_REF));
418                                            } else {
419                                                    args[i]=create(argList.item(i),null,null);
420                                            }
421                                    }
422                                    instance=constructor.newInstance(args);
423                            }
424                                            
425                            if (hasAttribute(node, "url") && instance instanceof URLConfigurable) {
426                                    ((URLConfigurable) instance).configure(new URL(((Element) node).getAttribute("url")), context);
427                            } else if (hasAttribute(node, "file") && instance instanceof FileConfigurable) {
428                                    ((FileConfigurable) instance).configure(new File(((Element) node).getAttribute("file")), context);
429                            } else if (instance instanceof InputStreamConfigurable) {
430                                    if (hasAttribute(node, "url")) {
431                                            new URL2InputStreamConfigurableAdapter((InputStreamConfigurable) instance).configure(new URL(((Element) node).getAttribute("url")), context);
432                                    } else if (hasAttribute(node, "file")) {
433                                            new File2InputStreamConfigurableAdapter((InputStreamConfigurable) instance).configure(new File(((Element) node).getAttribute("file")), context);
434                                    } else if (hasAttribute(node, "resource")) {                                    
435                                            ((InputStreamConfigurable) instance).configure(clazz.getResourceAsStream(((Element) node).getAttribute("resource")), context);
436                                    } else {
437                                            throw new ConfigurationException("Cannot configure "+instance.getClass().getName());
438                                    }
439                            } else if (instance instanceof DomConfigurable) { // Dom configurable
440                                    if (hasAttribute(node, "url")) {
441                                            new URL2InputStreamConfigurableAdapter(
442                                                    new InputStream2DomConfigurableAdapter(
443                                                            (DomConfigurable) instance)).configure(new URL(((Element) node).getAttribute("url")), context);
444                                    } else if (hasAttribute(node, "file")) {
445                                            new File2InputStreamConfigurableAdapter(
446                                                    new InputStream2DomConfigurableAdapter(
447                                                            (DomConfigurable) instance)).configure(new File(((Element) node).getAttribute("file")), context);
448                                    } else if (hasAttribute(node, "resource")) {                                    
449                                                    new InputStream2DomConfigurableAdapter((DomConfigurable) instance).configure(getClass().getResourceAsStream(((Element) node).getAttribute("resource")), context);
450                                    } else {
451                                            ((DomConfigurable) instance).configure(node, context);
452                                    }
453                            } else if (instance instanceof Parameterizable) { // Parameterizable
454                                    Map localContext=new HashMap();
455                                    
456                                    if (DOMUtils.selectSingleNode(node, "parameter[@name]")==null) { 
457                                            // Use element names as parameter names
458                                            NodeList nl=node.getChildNodes();
459                                            for (int i=0; i<nl.getLength(); i++) {
460                                                    if (nl.item(i) instanceof Element) {
461                                                            String parameterName = nl.item(i).getNodeName();                                                        
462                                                            ((Parameterizable) instance).setParameter(parameterName, getValue((Element) nl.item(i)));
463                                                            localContext.remove(parameterName);
464                                                    }
465                                            }
466                                    } else {
467                                            NodeList nl=DOMUtils.selectNodeList(node, "parameter[@name]");
468                                            for (int i=0, l=nl.getLength(); i<l; ++i) {
469                                                    Element paramElement = (Element) nl.item(i);
470                                                    String parameterName = paramElement.getAttribute("name");
471                                                    ((Parameterizable) instance).setParameter(parameterName, getValue(paramElement));
472                                                    localContext.remove(parameterName);
473                                            } 
474                                    }                               
475                                    
476                                    Iterator it=localContext.entrySet().iterator();
477                                    while (it.hasNext()) {
478                                            Map.Entry entry=(Map.Entry) it.next();
479                                            ((Parameterizable) instance).setParameter((String) entry.getKey(), entry.getValue());
480                                    }
481                            } else if (instance instanceof StringConfigurable) { // String configurable
482                                    ((StringConfigurable) instance).configure(DOMUtils.eval(node, "text()").toString(), context);
483                            } else if (instance instanceof Map) { // Map
484                                    NodeList nl=DOMUtils.selectNodeList(node, "entry");
485                                    for (int i=0, l=nl.getLength(); i<l; ++i) {
486                                            Element entryElement = (Element) nl.item(i);
487                                            ((Map) instance).put(getValue((Element) DOMUtils.selectSingleNode(entryElement,MAP_KEY_EXPRESSION)),
488                                                    getValue((Element) DOMUtils.selectSingleNode(entryElement,MAP_VALUE_EXPRESSION)));
489                                    }                               
490                            } else if (instance instanceof Collection) { // Collection
491                                    NodeList nl=DOMUtils.selectNodeList(node, "element");
492                                    for (int i=0, l=nl.getLength(); i<l; ++i) {
493                                            Element element = (Element) nl.item(i);
494                                            ((Collection) instance).add(getValue(element));
495                                    }                                                               
496                            } else {
497                                    Map toInject=new HashMap();
498                                    Context localContext=new MapContext(toInject, context);
499                                    
500                                    if (DOMUtils.selectSingleNode(node, "parameter[@name]")==null) { 
501                                            // Use element names as parameter names
502                                            NodeList nl=node.getChildNodes();
503                                            for (int i=0; i<nl.getLength(); i++) {
504                                                    if (nl.item(i) instanceof Element) {
505                                                            String name = nl.item(i).getNodeName();                                                 
506                                                            Element element = (Element) nl.item(i);
507                                                            toInject.put(name, getInjectEntry(element));
508                                                    }
509                                            }
510                                    } else {
511                                            NodeList nl =DOMUtils.selectNodeList(node, "parameter[@name]");
512                                            for (int i=0, l=nl.getLength(); i<l; ++i) {
513                                                    Element paramElement = (Element) nl.item(i);
514                                                    String name = paramElement.getAttribute("name");
515                                                    toInject.put(name, getInjectEntry(paramElement));
516                                            } 
517                                    }
518                                    
519                                    inject(instance, localContext);
520                            }
521                            
522                            if (objectAcceptor!=null) {
523                                    objectAcceptor.accept(instance);
524                            }
525                            
526                            if (instance instanceof Validatable) {
527                                    ((Validatable) instance).validate();
528                            }
529                                                    
530                            return instance;
531                    } catch (Exception e) {
532                            throw new ConfigurationException(e);
533                    }
534            }
535            
536            /**
537             * Holder for element and its instantiation.
538             * @author Pavel Vlasov
539             * @revision $Revision$
540             */
541            private class InjectEntry {
542                    Element element;
543                    Object instance;
544                    
545                    /**
546                     * @param element
547                     * @param instance
548                     */
549                    public InjectEntry(Element element, Object instance) {
550                            super();
551                            this.element = element;
552                            this.instance = instance;
553                    }
554                                    
555            }
556    
557        /**
558             * @param className
559             * @return
560             * @throws ClassNotFoundException
561             */
562            private Class forName(String className) throws ClassNotFoundException {
563                    return classLoader==null ? Class.forName(className) : Class.forName(className, true, classLoader);
564            }
565    
566            /**
567             * Converts instantiated value to InjectEntry
568             * @param cxpa
569             * @param parameterElement
570             * @return
571             * @throws ConfigurationException
572             */
573            private InjectEntry getInjectEntry(Element parameterElement) throws ConfigurationException {
574                    return new InjectEntry(parameterElement, getValue(parameterElement));
575            }
576            
577            /**
578             * @param cxpa
579             * @param parameterElement
580             * @return
581             * @throws ConfigurationException
582             */
583            private Object getValue(Element parameterElement) throws ConfigurationException {
584                    if (parameterElement.hasAttribute(CONTEXT_REF)) {
585                            return context.get((parameterElement).getAttribute(CONTEXT_REF));
586                    }
587                    
588                    return create(parameterElement,null,null);
589            }
590            
591            
592            /**
593             * Converts InjectEntry to target class.
594             * Takes into account wrappers and org.w3c.Element
595             * @param entry
596             * @param targetClass
597             * @return
598             */
599            private static Object getValue(Object value, Class targetClass, Object owner) {
600                    
601                    if (value==null) {
602                            return null;
603                    }
604                    
605                    if (value instanceof InjectEntry) {
606                            InjectEntry injectEntry=(InjectEntry) value;
607                            
608                            // If instance is already compatible - return instance.
609                            if (targetClass.isInstance(injectEntry.instance)) {
610                                    return injectEntry.instance;
611                            }
612                            
613                            // If element is compatible - return element
614                            if (targetClass.isInstance(injectEntry.element)) {
615                                    return injectEntry.element;
616                            }
617                            
618                            // If is Wrapper and master is compatible - return master
619                            if (injectEntry.instance instanceof Wrapper) {                  
620                                    //Start wrapper if it is also a component.
621                                    if (injectEntry.instance instanceof Component) {
622                                            try {
623                                                    Component component = (Component) injectEntry.instance;
624                                                    component.setOwner(owner);
625                                                    component.start();
626                                            } catch (ConfigurationException e) {
627                                                    throw new RuntimeConfigurationException("Could not start wrapper component "+injectEntry.instance.getClass().getName()+": "+e, e);
628                                            }
629                                    }
630                                    Object master=((Wrapper) injectEntry.instance).getMaster();
631                                    if (targetClass.isInstance(master)) {
632                                            return master;
633                                    }
634                            }
635            
636                            // The last resort - use converter.
637                            return CompositeConverter.getDefaultConverter().convert(injectEntry.instance, targetClass, true);
638                    }
639                    
640                    // If instance is already compatible - return instance.
641                    if (targetClass.isInstance(value)) {
642                            return value;
643                    }
644                            
645                    // The last resort - use converter.
646                    return CompositeConverter.getDefaultConverter().convert(value, targetClass, true);
647            }
648    
649            /**
650         * Sets property (field or through setter) using reflection
651             * @param instance
652         * @param lenient If false then inject throws ConfigurationException if property does not exists
653         * @param string
654         * @param object
655             */
656            public static void inject(Object instance, Context context) throws ConfigurationException {
657                    Method[] ma=instance.getClass().getMethods();
658                    LinkedList setters=new LinkedList();
659                    for (int i=0; i<ma.length; i++) {
660                            if (Modifier.isPublic(ma[i].getModifiers()) && ma[i].getName().startsWith("set") && ma[i].getParameterTypes().length==1) {
661                                    setters.add(ma[i]);
662                            }
663                    }
664                    
665                    while (!setters.isEmpty()) {
666                            Method m=(Method) setters.getFirst();
667                            String methodName = m.getName();
668                            int methodNameLength = methodName.length();
669                            Class pt = m.getParameterTypes()[0];
670                            String key=methodNameLength==3 ? pt.getName() : (Character.toLowerCase(methodName.charAt(3)) + (methodNameLength<5 ? "" : methodName.substring(4)));
671                            
672                            Object value=getValue(context.get(key), pt, instance);
673                                                                            
674                            if (value!=null) {
675                                    Iterator jit=setters.iterator();
676                                    while (jit.hasNext()) {
677                                            Method m2=(Method) jit.next();
678                                            if (m2.getName().equals(m.getName()) && pt.isAssignableFrom(m2.getParameterTypes()[0])) {
679                                                    m=m2;
680                                            }
681                                    }
682                                    
683                                    try {
684                                            m.invoke(instance, new Object[] {value});
685                                    } catch (IllegalArgumentException e) {
686                                            throw new ConfigurationException(e);
687                                    } catch (IllegalAccessException e) {
688                                            throw new ConfigurationException(e);
689                                    } catch (InvocationTargetException e) {
690                                            throw new ConfigurationException(e);
691                                    }                               
692                            }                       
693                            setters.remove(m);
694                    }
695                    
696                    Field[] fa=instance.getClass().getFields();
697                    for (int i=0; i<fa.length; i++) {
698                            if (Modifier.isPublic(fa[i].getModifiers())) {
699                                    try {
700                                            Object value=getValue(context.get(fa[i].getName()), fa[i].getType(), instance);
701                                            if (value!=null) {
702                                                    fa[i].set(instance, value);
703                                            }
704                                    } catch (IllegalArgumentException e) {
705                                            throw new ConfigurationException(e);
706                                    } catch (IllegalAccessException e) {
707                                            throw new ConfigurationException(e);
708                                    }
709                            }
710                    }
711            }
712            
713            /**
714         * @param node
715         * @return
716         */
717        private boolean hasAttribute(Node node, String attribute) {
718            return node instanceof Element && ((Element) node).hasAttribute(attribute);
719        }  
720            
721            public static void main(final String[] args) {
722                    final long start=System.currentTimeMillis();
723                    if (args.length==0) {
724                            System.err.println("Usage: java <options> "+DomConfigFactory.class.getName()+" <configuration URL> [<additional parameters>]");
725                            System.exit(1);
726                    }
727                    
728                    final boolean stopInHook = "yes".equalsIgnoreCase(System.getProperty("biz.hammurapi.config.DomConfigFactory:shutdownHook"));
729                    final Object[] oa = {null};
730                    
731                    if (stopInHook) {
732                            Runtime.getRuntime().addShutdownHook(
733                                            new Thread() {
734                                                    public void run() {
735                                                            if (oa[0] instanceof Component) {
736                                                                    try {                                                   
737                                                                            ((Component) oa[0]).stop();
738                                                                    } catch (ConfigurationException e) {
739                                                                            System.err.println("Could not properly stop "+oa[0]);
740                                                                            e.printStackTrace();
741                                                                    }
742                                                                    System.out.println("Total execution time: "+((System.currentTimeMillis()-start)/1000)+" sec.");
743                                                            }
744                                                    }
745                                            });                     
746                    }
747                                                    
748                    RestartCommand run = new RestartCommand() {
749                            
750                            int attempt;
751                            
752                            public void run() {
753                                    try {
754                                            if (attempt > 0) {   
755                                                    long restartDelay = getRestartDelay();
756                                                    System.out.print("Restarting in "+restartDelay+" milliseconds. Attempt " + (attempt+1));
757                                                    try {
758                                                            Thread.sleep(restartDelay);
759                                                    } catch (InterruptedException ie) {
760                                                            ie.printStackTrace();
761                                                            System.exit(4);
762                                                    }
763                                            }
764                                            
765                                            ++attempt;
766                                            
767                                            DomConfigFactory factory=new DomConfigFactory();
768                                            
769                                            if (args[0].startsWith("url:")) {
770                                                    oa[0] = factory.create(new URL(args[0].substring("url:".length())), null);
771                                            } else if (args[0].startsWith("resource:")) {
772                                                    InputStream stream = DomConfigFactory.class.getClassLoader().getResourceAsStream(args[0].substring("resource:".length()));
773                                                    if (stream==null) {
774                                                            System.err.println("Resource does not exist.");
775                                                            System.exit(1);
776                                                    }
777                                                    oa[0] = factory.create(stream, null);
778                                            } else {
779                                                    File file = new File(args[0]);
780                                                    if (!file.exists()) {
781                                                            System.err.println("File does not exist or not a file.");
782                                                            System.exit(1);
783                                                    }
784                                                    if (!file.isFile()) {
785                                                            System.err.println("Not a file.");
786                                                            System.exit(1);
787                                                    }
788                                                    oa[0] = factory.create(file, null);
789                                            }
790    
791                                            if (oa[0] instanceof Component) {
792                                                    ((Component) oa[0]).start();
793                                            }
794                                            
795                                            if (oa[0] instanceof Restartable) {
796                                                    ((Restartable) oa[0]).setRestartCommand(this);
797                                            }
798                                            
799                                            try {
800                                                    if (oa[0] instanceof Context) {
801                                                            Context container=(Context) oa[0];
802                                                            for (int i=1; i<args.length; i++) {
803                                                                    Object toExecute=container.get(args[i]);
804                                                                    if (toExecute instanceof Command) {
805                                                                            ((Command) toExecute).execute(args);
806                                                                    } else if (toExecute==null) {                                           
807                                                                            System.err.print("[WARN] Name not found: " +args[i]);
808                                                                    } else {
809                                                                            System.err.print("[WARN] Not executable: (" +args[i]+") "+toExecute.getClass().getName());                                              
810                                                                    }
811                                                            }                                       
812                                                    } else if (oa[0] instanceof Command) {
813                                                            ((Command) oa[0]).execute(args);
814                                                    }
815                                            } finally {                     
816                                                    if (oa[0] instanceof Component) {
817                                                            if (!stopInHook) {
818                                                                    ((Component) oa[0]).stop();
819                                                            }
820                                                    }
821                                            }
822                                    } catch (MalformedURLException e) {
823                                            System.err.println("Bad configuration URL: "+args[0]);
824                                            System.exit(2);
825                                    } catch (Exception e) {
826                                            e.printStackTrace();
827                                            if (attempt > 1) {   
828                                                    if (oa[0] instanceof Component) {
829                                                            try {
830                                                                    ((Component) oa[0]).stop();
831                                                            } catch (Exception ex) {
832                                                                    System.err.println("Cannot stop component before restart: "+e);
833                                                                    ex.printStackTrace();
834                                                            }
835                                                    }
836                                                    new Thread(this, "Restart thread "+getAttempt()).start(); // Use a new thread to avoid stack overflowing in the case of too many attempts.
837                                            } else {                                        
838                                                    System.exit(3);
839                                            }
840                                    }
841                            }
842    
843                            public int getAttempt() {
844                                    return attempt;
845                            }
846    
847                            public long getRestartDelay() {
848                                    String rd = System.getProperty(RESTART_DELARY_PROPERTY);
849                                    if (rd!=null) {
850                                            try {
851                                                    return Long.parseLong(rd);
852                                            } catch (NumberFormatException e) {
853                                                    // Ignore
854                                            }
855                                    }
856                                    
857                                    return DEFAULT_RESTART_DELAY;
858                            }
859                    };
860                    
861                    run.run();
862                    
863            }           
864    }