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. <name>Pavel</name> 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><name>Pavel</name></CODE> will yield java.lang.String with value 'Pavel'</li> 148 * <li><CODE><age type="java.lang.Integer">33</age></CODE> will yield java.lang.Integer with value '33'</li> 149 * <li><CODE><config type="org.myself.myproject.MyConfig" url="http://myproject.myself.org/MyConfig.xml"/></CODE> will load 150 * configuration from URL and configure MyConfig object</li> 151 * <li><PRE><config type="org.myself.myproject.MyParameterizableConfig"> 152 * <parameter name="pi" type="java.lang.Double">3.14159</parameter> 153 * </config></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><config type="org.myself.myproject.MyParameterizableConfig"> 157 * <pi type="java.lang.Double">3.14159</pi> 158 * </config></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 }