0001: /*
0002: * hgcommons 7
0003: * Hammurapi Group Common Library
0004: * Copyright (C) 2003 Hammurapi Group
0005: *
0006: * This program is free software; you can redistribute it and/or
0007: * modify it under the terms of the GNU Lesser General Public
0008: * License as published by the Free Software Foundation; either
0009: * version 2 of the License, or (at your option) any later version.
0010: *
0011: * This program is distributed in the hope that it will be useful,
0012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0014: * Lesser General Public License for more details.
0015: *
0016: * You should have received a copy of the GNU Lesser General Public
0017: * License along with this library; if not, write to the Free Software
0018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0019: *
0020: * URL: http://www.hammurapi.biz/hammurapi-biz/ef/xmenu/hammurapi-group/products/products/hgcommons/index.html
0021: * e-Mail: support@hammurapi.biz
0022: */
0023: package biz.hammurapi.config;
0024:
0025: import java.io.File;
0026: import java.io.IOException;
0027: import java.io.InputStream;
0028: import java.io.Reader;
0029: import java.lang.reflect.Constructor;
0030: import java.lang.reflect.Field;
0031: import java.lang.reflect.InvocationTargetException;
0032: import java.lang.reflect.Method;
0033: import java.lang.reflect.Modifier;
0034: import java.net.MalformedURLException;
0035: import java.net.URL;
0036: import java.util.Collection;
0037: import java.util.Collections;
0038: import java.util.HashMap;
0039: import java.util.Iterator;
0040: import java.util.LinkedList;
0041: import java.util.Map;
0042:
0043: import javax.xml.parsers.DocumentBuilderFactory;
0044: import javax.xml.parsers.FactoryConfigurationError;
0045: import javax.xml.parsers.ParserConfigurationException;
0046: import javax.xml.transform.TransformerException;
0047:
0048: import org.apache.xpath.CachedXPathAPI;
0049: import org.apache.xpath.XPathAPI;
0050: import org.w3c.dom.Element;
0051: import org.w3c.dom.Node;
0052: import org.w3c.dom.NodeList;
0053: import org.w3c.dom.traversal.NodeIterator;
0054: import org.xml.sax.InputSource;
0055: import org.xml.sax.SAXException;
0056:
0057: import biz.hammurapi.config.adapters.File2InputStreamConfigurableAdapter;
0058: import biz.hammurapi.config.adapters.InputStream2DomConfigurableAdapter;
0059: import biz.hammurapi.config.adapters.URL2InputStreamConfigurableAdapter;
0060: import biz.hammurapi.convert.CompositeConverter;
0061:
0062: /**
0063: * Creates and configures objects from DOM {@link org.w3c.dom.Element} (XML file).
0064: * DOM Element can be read from InputStream, File or URL.
0065: * Instantiation and configuration happens as follows:<P/>
0066: * <B>Instantiation</B>
0067: * <ul>
0068: * <li>If there is no <code>'type'</code> attribute then type defaults to
0069: * {@link java.lang.String} and text of the element will be returned.
0070: * E.g. <name>Pavel</name> will yield string 'Pavel'. <code>'type'</code>
0071: * attribute name can be changed through {@link biz.hammurapi.config.DomConfigInfo#setCodeExpression(String)}
0072: * method. Create {@link biz.hammurapi.config.DomConfigInfo}, change code expression and then
0073: * use {@link #DomConfigFactory(DomConfigInfo)} to instantiate DomConfigFactory.
0074: * </li>
0075: *
0076: * <li>Otherwise class specified in <code>'type'</code> attribute will be loaded and
0077: * verified by classAcceptor (if any)</li>
0078: *
0079: * <li>If there is no nested <code>'constructor'</code>' element and element text is blank then default
0080: * constructor will be used</li>
0081: *
0082: * <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
0083: * java.lang.String will be used</li>
0084: *
0085: * <li>If there is nested <code>'constructor'</code> element then <code>'arg'</code> elements of
0086: * <code>'constructor'</code> element are iterated to create a list of arguments.
0087: * Arguments are constructed in the same way as described here. <code>'arg'</code> element
0088: * also supports <code>'context-ref'</code> attribute. If this attribute is set argument
0089: * value will be taken from context entry set by {@link DomConfigFactory#setContextEntry(String, Object)} method
0090: * </li>
0091: * </ul>
0092: * <P/>
0093: *
0094: * <B>Configuration</B>
0095: * <ul>
0096: * <li>If element has attribute <code>'url'</code> and instantiated object (instance)
0097: * is instance of {@link biz.hammurapi.config.URLConfigurable} then {@link biz.hammurapi.config.URLConfigurable#configure(URL, Map)}
0098: * is invoked to configure instance</li>
0099: *
0100: * <li>If element has attribute <code>'file'</code> and instance
0101: * is instance of {@link biz.hammurapi.config.FileConfigurable} then {@link biz.hammurapi.config.FileConfigurable#configure(File, Map)}
0102: * is invoked to configure instance</li>
0103: *
0104: * <li>If instance is instance of
0105: * {@link biz.hammurapi.config.InputStreamConfigurable}
0106: * then <ul>
0107: * <li>If element has attribute <code>'url'</code> then that url is opened as InsputStream</li>
0108: * <li>If element has attribute <code>'file'</code> then that file is opened as InputStream</li>
0109: * <li>If element has attribute <code>'resource'</code> then that resource is opened as InputStream.
0110: * Instance's class is used to obtain resource which allows to use relative resource names.</li>
0111: * </ul>
0112: * then that InputStream is passed to
0113: * {@link biz.hammurapi.config.InputStreamConfigurable#configure(InputStream, Map)}
0114: * to configure instance. If none of aforementioned attributes is present then ConfigurationException is thrown.</li>
0115: *
0116: * <li>If instance is instance of {@link biz.hammurapi.config.DomConfigurable} then
0117: * <ul>
0118: * <li>If element has attribute <code>'url'</code> then that url is opened as InsputStream and parsed to DOM tree</li>
0119: * <li>If element has attribute <code>'file'</code> then that file is opened as InputStream and parsed to DOM tree</li>
0120: * <li>If element has attribute <code>'resource'</code> then that resource is opened as InputStream and parsed to DOM tree.
0121: * Instance's class is used to obtain resource which allows to use relative resource names.</li>
0122: * </ul>
0123: * then that parsed document is passed to {@link biz.hammurapi.config.DomConfigurable#configure(Node, Context)}.
0124: * If none of the aforementioned attributes is present then element itself is passed to
0125: * {@link biz.hammurapi.config.DomConfigurable#configure(Node, Context)}</li>
0126: *
0127: * <li>If instance is instance of {@link biz.hammurapi.config.Parameterizable} then
0128: * <ul>
0129: * <li>If there are subelements <code>'parameter'</code> with attribute <code>'name'</code>
0130: * then value of <code>'name'</code> is used as parameter name</li>
0131: * <li>Otherwise names of nested elements used as parameter names</li>
0132: * </ul>
0133: * Parameter values are evaluated in the same way as <code>'arg'</code> elements for
0134: * constructors.
0135: * {@link biz.hammurapi.config.Parameterizable#setParameter(String, Object)} is invoked for each of parameter elements.
0136: *
0137: * {@link biz.hammurapi.config.Parameterizable#setParameter(String, Object)} is also invoked for context entries
0138: * with names which did not match with parameter names. E.g. if there are two context entries 'age' and 'name' and parameter
0139: * 'name' then setParameter("name", <I>value of parameter 'name'</I>) will be invoked and after that
0140: * setParameter("age", <I>value of context entry 'age'</I>) will be invoked.
0141: * </li>
0142: *
0143: * <li>If instance is instance of {@link biz.hammurapi.config.StringConfigurable} then element text is passed to
0144: * {@link StringConfigurable#configure(String, Map)} method</li>
0145: *
0146: * <li>If instance is instance of {@link java.util.Map} then <code>'entry'</code> subelements are iterated; <code>'key'</code>
0147: * (Configurable through {@link biz.hammurapi.config.DomConfigInfo}) and <code>'value'</code>
0148: * (Configurable through {@link biz.hammurapi.config.DomConfigInfo}) subelements are evaluated in the same way as
0149: * <code>'arg'</code> constructors subelements and put to instance by {@link java.util.Map#put(java.lang.Object, java.lang.Object)}</li>
0150: *
0151: * <li>If instance is instance of {@link java.util.Collection} then <code>'element'</code> subelements are iterated, elements
0152: * are istantiated in the same way as constructor arguments and then placed into instance by invoking {@link java.util.Collection#add(java.lang.Object)}
0153: * method.</li>
0154: *
0155: * <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
0156: * {@link biz.hammurapi.config.Parameterizable} are set. Special note about injection: If field type or setter parameter type (target type) is compatible with
0157: * 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
0158: * {@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
0159: * are invoked before obtaining master. If none of aforementioned conditions are true then value is converted to target type.
0160: * using {@link biz.hammurapi.convert.CompositeConverter}.</li>
0161: *
0162: * <li>If object acceptor is not null then its {@link biz.hammurapi.config.ObjectAcceptor#accept(Object)} is invoked
0163: * to validate that object has been constructed and configured correctly</li>
0164: *
0165: * <li>If instance is instance of {@link biz.hammurapi.config.Validatable} then {@link biz.hammurapi.config.Validatable#validate()} is
0166: * invoked for the instance to validate itself.
0167: * </ul>
0168: *
0169: * <B>Examples</B>
0170: * <OL>
0171: * <li><CODE><name>Pavel</name></CODE> will yield java.lang.String with value 'Pavel'</li>
0172: * <li><CODE><age type="java.lang.Integer">33</age></CODE> will yield java.lang.Integer with value '33'</li>
0173: * <li><CODE><config type="org.myself.myproject.MyConfig" url="http://myproject.myself.org/MyConfig.xml"/></CODE> will load
0174: * configuration from URL and configure MyConfig object</li>
0175: * <li><PRE><config type="org.myself.myproject.MyParameterizableConfig">
0176: * <parameter name="pi" type="java.lang.Double">3.14159</parameter>
0177: * </config></PRE> will create MyParameterizableConfig object and then invoke its setParameter() method if MyParameterizableConfig
0178: * implements {@link biz.hammurapi.config.Parameterizable} or invoke setPi() method if there is such method. In lenient mode
0179: * nothing will happen if there is no setPi() method. Otherwise exception will be thrown.</li>
0180: * <li><PRE><config type="org.myself.myproject.MyParameterizableConfig">
0181: * <pi type="java.lang.Double">3.14159</pi>
0182: * </config></PRE> same as above.</li>
0183: * </OL>
0184: *
0185: * @author Pavel Vlasov
0186: * @version $Revision: 1.12 $
0187: */
0188: public class DomConfigFactory {
0189: public static final String CODE_EXPRESSION = "@type";
0190: public static final String MAP_KEY_EXPRESSION = "key";
0191: public static final String MAP_VALUE_EXPRESSION = "value";
0192:
0193: private static final String CONTEXT_REF = "context-ref";
0194: public static final Map PRIMITIVES;
0195: private Context context;
0196:
0197: static {
0198: Map primitives = new HashMap();
0199: primitives.put("boolean", Boolean.TYPE);
0200: primitives.put("byte", Byte.TYPE);
0201: primitives.put("char", Character.TYPE);
0202: primitives.put("double", Double.TYPE);
0203: primitives.put("float", Float.TYPE);
0204: primitives.put("int", Integer.TYPE);
0205: primitives.put("long", Long.TYPE);
0206: primitives.put("short", Short.TYPE);
0207: PRIMITIVES = Collections.unmodifiableMap(primitives);
0208: }
0209:
0210: private ClassLoader classLoader;
0211:
0212: /**
0213: * Default constructor
0214: */
0215: public DomConfigFactory() {
0216: super ();
0217: }
0218:
0219: /**
0220: * Default constructor
0221: */
0222: public DomConfigFactory(Context context) {
0223: super ();
0224: this .context = context;
0225: }
0226:
0227: public DomConfigFactory(ClassLoader classLoader) {
0228: super ();
0229: this .classLoader = classLoader;
0230: }
0231:
0232: public DomConfigFactory(ClassLoader classLoader, Context context) {
0233: super ();
0234: this .classLoader = classLoader;
0235: this .context = context;
0236: }
0237:
0238: /**
0239: * Creates object. Same as create(node, null, null)
0240: * @param node
0241: * @return
0242: * @throws ConfigurationException
0243: */
0244: public Object create(Node node) throws ConfigurationException {
0245: return create(node, null, null);
0246: }
0247:
0248: /**
0249: * Parses file and returns object. Same as create(file, xPath, null, null)
0250: * @param file XML configuration file
0251: * @param xPath XPath expression, can be null
0252: * @return configured object
0253: */
0254: public Object create(File file, String xPath)
0255: throws ConfigurationException, IOException {
0256: return create(file, xPath, null, null);
0257: }
0258:
0259: /**
0260: * Parses file and returns object
0261: * @param file XML configuration file
0262: * @param xPath XPath expression, can be null
0263: * @param classAcceptor Class acceptor, validates that class about to be instantiated is 'the right one'
0264: * @param objectAcceptor Object acceptor, validates instantiated object.
0265: * @return Configured object
0266: */
0267: public Object create(File file, String xPath,
0268: ClassAcceptor classAcceptor, ObjectAcceptor objectAcceptor)
0269: throws ConfigurationException, IOException {
0270: try {
0271: Node node = DocumentBuilderFactory.newInstance()
0272: .newDocumentBuilder().parse(file)
0273: .getDocumentElement();
0274: if (xPath != null) {
0275: node = XPathAPI.selectSingleNode(node, xPath);
0276: }
0277: return create(node, classAcceptor, objectAcceptor);
0278: } catch (SAXException e) {
0279: throw new ConfigurationException(e);
0280: } catch (ParserConfigurationException e) {
0281: throw new ConfigurationException(e);
0282: } catch (FactoryConfigurationError e) {
0283: throw new ConfigurationException(e);
0284: } catch (TransformerException e) {
0285: throw new ConfigurationException(e);
0286: }
0287: }
0288:
0289: /**
0290: * Same as create(in, xPath, null, null)
0291: * @param in Input stream
0292: * @param xPath XPath expression, can be null
0293: * @return Configured object
0294: * @throws ConfigurationException
0295: * @throws IOException
0296: */
0297: public Object create(InputStream in, String xPath)
0298: throws ConfigurationException, IOException {
0299: return create(in, xPath, null, null);
0300: }
0301:
0302: /**
0303: * Creates and configures object from InputStream
0304: * @param in Input stream
0305: * @param xPath XPath expression, can be null
0306: * @param classAcceptor
0307: * @param objectAcceptor
0308: * @return Configured object
0309: * @throws ConfigurationException
0310: * @throws IOException
0311: */
0312: public Object create(InputStream in, String xPath,
0313: ClassAcceptor classAcceptor, ObjectAcceptor objectAcceptor)
0314: throws ConfigurationException, IOException {
0315: try {
0316: Node node = DocumentBuilderFactory.newInstance()
0317: .newDocumentBuilder().parse(in)
0318: .getDocumentElement();
0319: if (xPath != null) {
0320: node = XPathAPI.selectSingleNode(node, xPath);
0321: }
0322: return create(node, classAcceptor, objectAcceptor);
0323: } catch (SAXException e) {
0324: throw new ConfigurationException(e);
0325: } catch (ParserConfigurationException e) {
0326: throw new ConfigurationException(e);
0327: } catch (FactoryConfigurationError e) {
0328: throw new ConfigurationException(e);
0329: } catch (TransformerException e) {
0330: throw new ConfigurationException(e);
0331: }
0332: }
0333:
0334: /**
0335: * Same as create(in, xPath, null, null)
0336: * @param in Reader
0337: * @param xPath XPath expression, can be null
0338: * @return Configured object
0339: * @throws ConfigurationException
0340: * @throws IOException
0341: */
0342: public Object create(Reader in, String xPath)
0343: throws ConfigurationException, IOException {
0344: return create(in, xPath, null, null);
0345: }
0346:
0347: /**
0348: * Creates and configures object from InputStream
0349: * @param in Reader
0350: * @param xPath XPath expression, can be null
0351: * @param classAcceptor
0352: * @param objectAcceptor
0353: * @return Configured object
0354: * @throws ConfigurationException
0355: * @throws IOException
0356: */
0357: public Object create(Reader in, String xPath,
0358: ClassAcceptor classAcceptor, ObjectAcceptor objectAcceptor)
0359: throws ConfigurationException, IOException {
0360: try {
0361: Node node = DocumentBuilderFactory.newInstance()
0362: .newDocumentBuilder().parse(new InputSource(in))
0363: .getDocumentElement();
0364: if (xPath != null) {
0365: node = XPathAPI.selectSingleNode(node, xPath);
0366: }
0367: return create(node, classAcceptor, objectAcceptor);
0368: } catch (SAXException e) {
0369: throw new ConfigurationException(e);
0370: } catch (ParserConfigurationException e) {
0371: throw new ConfigurationException(e);
0372: } catch (FactoryConfigurationError e) {
0373: throw new ConfigurationException(e);
0374: } catch (TransformerException e) {
0375: throw new ConfigurationException(e);
0376: }
0377: }
0378:
0379: /**
0380: * Same as create(url, xPath, null, null)
0381: * @param url URL to read configuration from
0382: * @param xPath XPath expression, can be null
0383: * @return Configured object
0384: * @throws ConfigurationException
0385: * @throws IOException
0386: */
0387: public Object create(URL url, String xPath)
0388: throws ConfigurationException, IOException {
0389: return create(url, xPath, null, null);
0390: }
0391:
0392: /**
0393: * Creates and configures object from URL
0394: * @param url Url
0395: * @param xPath XPath expression, can be null
0396: * @param classAcceptor
0397: * @param objectAcceptor
0398: * @return Configured object
0399: * @throws ConfigurationException
0400: * @throws IOException
0401: */
0402: public Object create(URL url, String xPath,
0403: ClassAcceptor classAcceptor, ObjectAcceptor objectAcceptor)
0404: throws ConfigurationException, IOException {
0405: try {
0406: Node node = DocumentBuilderFactory.newInstance()
0407: .newDocumentBuilder().parse(url.openStream())
0408: .getDocumentElement();
0409: if (xPath != null) {
0410: node = XPathAPI.selectSingleNode(node, xPath);
0411: }
0412: return create(node, classAcceptor, objectAcceptor);
0413: } catch (SAXException e) {
0414: throw new ConfigurationException(e);
0415: } catch (ParserConfigurationException e) {
0416: throw new ConfigurationException(e);
0417: } catch (FactoryConfigurationError e) {
0418: throw new ConfigurationException(e);
0419: } catch (TransformerException e) {
0420: throw new ConfigurationException(e);
0421: }
0422: }
0423:
0424: /**
0425: * Creates object from {@link org.w3c.dom.Node}
0426: * @param node Node
0427: * @param classAcceptor
0428: * @param objectAcceptor
0429: * @return Configured object
0430: * @throws ConfigurationException
0431: */
0432: public Object create(Node node, ClassAcceptor classAcceptor,
0433: ObjectAcceptor objectAcceptor)
0434: throws ConfigurationException {
0435: return create(node, classAcceptor, objectAcceptor,
0436: new CachedXPathAPI());
0437: }
0438:
0439: /**
0440: * Creates and configures object
0441: * @param node
0442: * @param classAcceptor
0443: * @param objectAcceptor
0444: * @param cxpa Cached XPath API to accelerate XPath expressions evaluation.
0445: * @return Configured object
0446: * @throws ConfigurationException
0447: */
0448: protected Object create(Node node, ClassAcceptor classAcceptor,
0449: ObjectAcceptor objectAcceptor, CachedXPathAPI cxpa)
0450: throws ConfigurationException {
0451: try {
0452: String className = cxpa.eval(node, CODE_EXPRESSION)
0453: .toString();
0454: if (className.trim().length() == 0) {
0455: if (classAcceptor != null) {
0456: classAcceptor.accept(String.class);
0457: }
0458: return cxpa.eval(node, "text()").toString();
0459: }
0460:
0461: Class clazz = forName(className);
0462: if (classAcceptor != null) {
0463: classAcceptor.accept(clazz);
0464: }
0465:
0466: Object instance;
0467: Node constructorNode = cxpa.selectSingleNode(node,
0468: "constructor");
0469: if (constructorNode == null) {
0470: String body = cxpa.eval(node, "text()").toString()
0471: .trim();
0472: if (body.length() == 0
0473: || DomConfigurable.class
0474: .isAssignableFrom(clazz)) {
0475: instance = clazz.newInstance();
0476: } else {
0477: Constructor c = clazz
0478: .getConstructor(new Class[] { String.class });
0479: instance = c.newInstance(new Object[] { body });
0480: }
0481: } else {
0482: NodeList argList = cxpa.selectNodeList(constructorNode,
0483: "arg");
0484: Class[] argTypes = new Class[argList.getLength()];
0485: for (int i = 0; i < argList.getLength(); i++) {
0486: String argTypeName = cxpa.eval(argList.item(i),
0487: CODE_EXPRESSION).toString();
0488: if (argTypeName.trim().length() == 0) {
0489: argTypes[i] = String.class;
0490: } else {
0491: argTypes[i] = (Class) PRIMITIVES
0492: .get(argTypeName);
0493: if (argTypes[i] == null) {
0494: argTypes[i] = forName(argTypeName);
0495: }
0496: }
0497: }
0498:
0499: Constructor constructor = clazz
0500: .getConstructor(argTypes);
0501: Object[] args = new Object[argList.getLength()];
0502: for (int i = 0; i < argList.getLength(); i++) {
0503: Element argElement = ((Element) argList.item(i));
0504: if (argTypes[i].isPrimitive()) {
0505: args[i] = CompositeConverter
0506: .getDefaultConverter().convert(
0507: cxpa.eval(argList.item(i),
0508: "text()").toString(),
0509: argTypes[i], false);
0510: } else if (argElement.hasAttribute(CONTEXT_REF)) {
0511: args[i] = context.get(argElement
0512: .getAttribute(CONTEXT_REF));
0513: } else {
0514: args[i] = create(argList.item(i), null, null,
0515: cxpa);
0516: }
0517: }
0518: instance = constructor.newInstance(args);
0519: }
0520:
0521: if (hasAttribute(node, "url")
0522: && instance instanceof URLConfigurable) {
0523: ((URLConfigurable) instance).configure(new URL(
0524: ((Element) node).getAttribute("url")), context);
0525: } else if (hasAttribute(node, "file")
0526: && instance instanceof FileConfigurable) {
0527: ((FileConfigurable) instance)
0528: .configure(new File(((Element) node)
0529: .getAttribute("file")), context);
0530: } else if (instance instanceof InputStreamConfigurable) {
0531: if (hasAttribute(node, "url")) {
0532: new URL2InputStreamConfigurableAdapter(
0533: (InputStreamConfigurable) instance)
0534: .configure(new URL(((Element) node)
0535: .getAttribute("url")), context);
0536: } else if (hasAttribute(node, "file")) {
0537: new File2InputStreamConfigurableAdapter(
0538: (InputStreamConfigurable) instance)
0539: .configure(new File(((Element) node)
0540: .getAttribute("file")), context);
0541: } else if (hasAttribute(node, "resource")) {
0542: ((InputStreamConfigurable) instance)
0543: .configure(
0544: clazz
0545: .getResourceAsStream(((Element) node)
0546: .getAttribute("resource")),
0547: context);
0548: } else {
0549: throw new ConfigurationException(
0550: "Cannot configure "
0551: + instance.getClass().getName());
0552: }
0553: } else if (instance instanceof DomConfigurable) { // Dom configurable
0554: if (hasAttribute(node, "url")) {
0555: new URL2InputStreamConfigurableAdapter(
0556: new InputStream2DomConfigurableAdapter(
0557: (DomConfigurable) instance))
0558: .configure(new URL(((Element) node)
0559: .getAttribute("url")), context);
0560: } else if (hasAttribute(node, "file")) {
0561: new File2InputStreamConfigurableAdapter(
0562: new InputStream2DomConfigurableAdapter(
0563: (DomConfigurable) instance))
0564: .configure(new File(((Element) node)
0565: .getAttribute("file")), context);
0566: } else if (hasAttribute(node, "resource")) {
0567: new InputStream2DomConfigurableAdapter(
0568: (DomConfigurable) instance).configure(
0569: getClass().getResourceAsStream(
0570: ((Element) node)
0571: .getAttribute("resource")),
0572: context);
0573: } else {
0574: ((DomConfigurable) instance).configure(node,
0575: context);
0576: }
0577: } else if (instance instanceof Parameterizable) { // Parameterizable
0578: Map localContext = new HashMap();
0579:
0580: if (cxpa.selectSingleNode(node, "parameter[@name]") == null) {
0581: // Use element names as parameter names
0582: NodeList nl = node.getChildNodes();
0583: for (int i = 0; i < nl.getLength(); i++) {
0584: if (nl.item(i) instanceof Element) {
0585: String parameterName = nl.item(i)
0586: .getNodeName();
0587: ((Parameterizable) instance).setParameter(
0588: parameterName, getValue(cxpa,
0589: (Element) nl.item(i)));
0590: localContext.remove(parameterName);
0591: }
0592: }
0593: } else {
0594: NodeIterator nit = cxpa.selectNodeIterator(node,
0595: "parameter[@name]");
0596: Element paramElement;
0597: while ((paramElement = (Element) nit.nextNode()) != null) {
0598: String parameterName = paramElement
0599: .getAttribute("name");
0600: ((Parameterizable) instance).setParameter(
0601: parameterName, getValue(cxpa,
0602: paramElement));
0603: localContext.remove(parameterName);
0604: }
0605: }
0606:
0607: Iterator it = localContext.entrySet().iterator();
0608: while (it.hasNext()) {
0609: Map.Entry entry = (Map.Entry) it.next();
0610: ((Parameterizable) instance).setParameter(
0611: (String) entry.getKey(), entry.getValue());
0612: }
0613: } else if (instance instanceof StringConfigurable) { // String configurable
0614: ((StringConfigurable) instance).configure(cxpa.eval(
0615: node, "text()").toString(), context);
0616: } else if (instance instanceof Map) { // Map
0617: NodeIterator nit = cxpa.selectNodeIterator(node,
0618: "entry");
0619: Element entryElement;
0620: while ((entryElement = (Element) nit.nextNode()) != null) {
0621: ((Map) instance).put(getValue(cxpa, (Element) cxpa
0622: .selectSingleNode(entryElement,
0623: MAP_KEY_EXPRESSION)),
0624: getValue(cxpa, (Element) cxpa
0625: .selectSingleNode(entryElement,
0626: MAP_VALUE_EXPRESSION)));
0627: }
0628: } else if (instance instanceof Collection) { // Collection
0629: NodeIterator nit = cxpa.selectNodeIterator(node,
0630: "element");
0631: Element element;
0632: while ((element = (Element) nit.nextNode()) != null) {
0633: ((Collection) instance)
0634: .add(getValue(cxpa, element));
0635: }
0636: } else {
0637: Map toInject = new HashMap();
0638: Context localContext = new MapContext(toInject, context);
0639:
0640: if (cxpa.selectSingleNode(node, "parameter[@name]") == null) {
0641: // Use element names as parameter names
0642: NodeList nl = node.getChildNodes();
0643: for (int i = 0; i < nl.getLength(); i++) {
0644: if (nl.item(i) instanceof Element) {
0645: String name = nl.item(i).getNodeName();
0646: Element element = (Element) nl.item(i);
0647: toInject.put(name, getInjectEntry(cxpa,
0648: element));
0649: }
0650: }
0651: } else {
0652: NodeIterator nit = cxpa.selectNodeIterator(node,
0653: "parameter[@name]");
0654: Element paramElement;
0655: while ((paramElement = (Element) nit.nextNode()) != null) {
0656: String name = paramElement.getAttribute("name");
0657: toInject.put(name, getInjectEntry(cxpa,
0658: paramElement));
0659: }
0660: }
0661:
0662: inject(instance, localContext);
0663: }
0664:
0665: if (objectAcceptor != null) {
0666: objectAcceptor.accept(instance);
0667: }
0668:
0669: if (instance instanceof Validatable) {
0670: ((Validatable) instance).validate();
0671: }
0672:
0673: return instance;
0674: } catch (TransformerException e) {
0675: throw new ConfigurationException(e);
0676: } catch (ClassNotFoundException e) {
0677: throw new ConfigurationException(e);
0678: } catch (InstantiationException e) {
0679: throw new ConfigurationException(e);
0680: } catch (IllegalAccessException e) {
0681: throw new ConfigurationException(e);
0682: } catch (SecurityException e) {
0683: throw new ConfigurationException(e);
0684: } catch (NoSuchMethodException e) {
0685: throw new ConfigurationException(e);
0686: } catch (IllegalArgumentException e) {
0687: throw new ConfigurationException(e);
0688: } catch (InvocationTargetException e) {
0689: throw new ConfigurationException(e);
0690: } catch (MalformedURLException e) {
0691: throw new ConfigurationException(e);
0692: }
0693: }
0694:
0695: /**
0696: * Holder for element and its instantiation.
0697: * @author Pavel Vlasov
0698: * @revision $Revision$
0699: */
0700: private class InjectEntry {
0701: Element element;
0702: Object instance;
0703:
0704: /**
0705: * @param element
0706: * @param instance
0707: */
0708: public InjectEntry(Element element, Object instance) {
0709: super ();
0710: this .element = element;
0711: this .instance = instance;
0712: }
0713:
0714: }
0715:
0716: /**
0717: * @param className
0718: * @return
0719: * @throws ClassNotFoundException
0720: */
0721: private Class forName(String className)
0722: throws ClassNotFoundException {
0723: return classLoader == null ? Class.forName(className) : Class
0724: .forName(className, true, classLoader);
0725: }
0726:
0727: /**
0728: * Converts instantiated value to InjectEntry
0729: * @param cxpa
0730: * @param parameterElement
0731: * @return
0732: * @throws ConfigurationException
0733: */
0734: private InjectEntry getInjectEntry(CachedXPathAPI cxpa,
0735: Element parameterElement) throws ConfigurationException {
0736: return new InjectEntry(parameterElement, getValue(cxpa,
0737: parameterElement));
0738: }
0739:
0740: /**
0741: * @param cxpa
0742: * @param parameterElement
0743: * @return
0744: * @throws ConfigurationException
0745: */
0746: private Object getValue(CachedXPathAPI cxpa,
0747: Element parameterElement) throws ConfigurationException {
0748: if (parameterElement.hasAttribute(CONTEXT_REF)) {
0749: return context.get((parameterElement)
0750: .getAttribute(CONTEXT_REF));
0751: }
0752:
0753: return create(parameterElement, null, null, cxpa);
0754: }
0755:
0756: /**
0757: * Converts InjectEntry to target class.
0758: * Takes into account wrappers and org.w3c.Element
0759: * @param entry
0760: * @param targetClass
0761: * @return
0762: */
0763: private static Object getValue(Object value, Class targetClass,
0764: Object owner) {
0765:
0766: if (value == null) {
0767: return null;
0768: }
0769:
0770: if (value instanceof InjectEntry) {
0771: InjectEntry injectEntry = (InjectEntry) value;
0772:
0773: // If instance is already compatible - return instance.
0774: if (targetClass.isInstance(injectEntry.instance)) {
0775: return injectEntry.instance;
0776: }
0777:
0778: // If element is compatible - return element
0779: if (targetClass.isInstance(injectEntry.element)) {
0780: return injectEntry.element;
0781: }
0782:
0783: // If is Wrapper and master is compatible - return master
0784: if (injectEntry.instance instanceof Wrapper) {
0785: //Start wrapper if it is also a component.
0786: if (injectEntry.instance instanceof Component) {
0787: try {
0788: Component component = (Component) injectEntry.instance;
0789: component.setOwner(owner);
0790: component.start();
0791: } catch (ConfigurationException e) {
0792: throw new RuntimeConfigurationException(
0793: "Could not start wrapper component "
0794: + injectEntry.instance
0795: .getClass().getName()
0796: + ": " + e, e);
0797: }
0798: }
0799: Object master = ((Wrapper) injectEntry.instance)
0800: .getMaster();
0801: if (targetClass.isInstance(master)) {
0802: return master;
0803: }
0804: }
0805:
0806: // The last resort - use converter.
0807: return CompositeConverter.getDefaultConverter().convert(
0808: injectEntry.instance, targetClass, true);
0809: }
0810:
0811: // If instance is already compatible - return instance.
0812: if (targetClass.isInstance(value)) {
0813: return value;
0814: }
0815:
0816: // The last resort - use converter.
0817: return CompositeConverter.getDefaultConverter().convert(value,
0818: targetClass, true);
0819: }
0820:
0821: /**
0822: * Sets property (field or through setter) using reflection
0823: * @param instance
0824: * @param lenient If false then inject throws ConfigurationException if property does not exists
0825: * @param string
0826: * @param object
0827: */
0828: public static void inject(Object instance, Context context)
0829: throws ConfigurationException {
0830: Method[] ma = instance.getClass().getMethods();
0831: LinkedList setters = new LinkedList();
0832: for (int i = 0; i < ma.length; i++) {
0833: if (Modifier.isPublic(ma[i].getModifiers())
0834: && ma[i].getName().startsWith("set")
0835: && ma[i].getParameterTypes().length == 1) {
0836: setters.add(ma[i]);
0837: }
0838: }
0839:
0840: while (!setters.isEmpty()) {
0841: Method m = (Method) setters.getFirst();
0842: String methodName = m.getName();
0843: int methodNameLength = methodName.length();
0844: Class pt = m.getParameterTypes()[0];
0845: String key = methodNameLength == 3 ? pt.getName()
0846: : (Character.toLowerCase(methodName.charAt(3)) + (methodNameLength < 5 ? ""
0847: : methodName.substring(4)));
0848:
0849: Object value = getValue(context.get(key), pt, instance);
0850:
0851: if (value != null) {
0852: Iterator jit = setters.iterator();
0853: while (jit.hasNext()) {
0854: Method m2 = (Method) jit.next();
0855: if (m2.getName().equals(m.getName())
0856: && pt.isAssignableFrom(m2
0857: .getParameterTypes()[0])) {
0858: m = m2;
0859: }
0860: }
0861:
0862: try {
0863: m.invoke(instance, new Object[] { value });
0864: } catch (IllegalArgumentException e) {
0865: throw new ConfigurationException(e);
0866: } catch (IllegalAccessException e) {
0867: throw new ConfigurationException(e);
0868: } catch (InvocationTargetException e) {
0869: throw new ConfigurationException(e);
0870: }
0871: }
0872: setters.remove(m);
0873: }
0874:
0875: Field[] fa = instance.getClass().getFields();
0876: for (int i = 0; i < fa.length; i++) {
0877: if (Modifier.isPublic(fa[i].getModifiers())) {
0878: try {
0879: Object value = getValue(context
0880: .get(fa[i].getName()), fa[i].getType(),
0881: instance);
0882: if (value != null) {
0883: fa[i].set(instance, value);
0884: }
0885: } catch (IllegalArgumentException e) {
0886: throw new ConfigurationException(e);
0887: } catch (IllegalAccessException e) {
0888: throw new ConfigurationException(e);
0889: }
0890: }
0891: }
0892: }
0893:
0894: /**
0895: * @param node
0896: * @return
0897: */
0898: private boolean hasAttribute(Node node, String attribute) {
0899: return node instanceof Element
0900: && ((Element) node).hasAttribute(attribute);
0901: }
0902:
0903: public static void main(final String[] args) {
0904: final long start = System.currentTimeMillis();
0905: if (args.length == 0) {
0906: System.err.println("Usage: java <options> "
0907: + DomConfigFactory.class.getName()
0908: + " <configuration URL> [<additional parameters>]");
0909: System.exit(1);
0910: }
0911:
0912: final boolean stopInHook = "yes"
0913: .equalsIgnoreCase(System
0914: .getProperty("biz.hammurapi.config.DomConfigFactory:shutdownHook"));
0915: final Object[] oa = { null };
0916:
0917: if (stopInHook) {
0918: Runtime.getRuntime().addShutdownHook(new Thread() {
0919: public void run() {
0920: if (oa[0] instanceof Component) {
0921: try {
0922: ((Component) oa[0]).stop();
0923: } catch (ConfigurationException e) {
0924: System.err
0925: .println("Could not properly stop "
0926: + oa[0]);
0927: e.printStackTrace();
0928: }
0929: System.out
0930: .println("Total execution time: "
0931: + ((System.currentTimeMillis() - start) / 1000)
0932: + " sec.");
0933: }
0934: }
0935: });
0936: }
0937:
0938: RestartCommand run = new RestartCommand() {
0939:
0940: int attempt;
0941:
0942: public void run() {
0943: try {
0944: if (attempt > 0) {
0945: long restartDelay = getRestartDelay();
0946: System.out.print("Restarting in "
0947: + restartDelay
0948: + " milliseconds. Attempt "
0949: + (attempt + 1));
0950: try {
0951: Thread.sleep(restartDelay);
0952: } catch (InterruptedException ie) {
0953: ie.printStackTrace();
0954: System.exit(4);
0955: }
0956: }
0957:
0958: ++attempt;
0959:
0960: DomConfigFactory factory = new DomConfigFactory();
0961:
0962: if (args[0].startsWith("url:")) {
0963: oa[0] = factory.create(new URL(args[0]
0964: .substring("url:".length())), null);
0965: } else if (args[0].startsWith("resource:")) {
0966: InputStream stream = DomConfigFactory.class
0967: .getClassLoader().getResourceAsStream(
0968: args[0].substring("resource:"
0969: .length()));
0970: if (stream == null) {
0971: System.err
0972: .println("Resource does not exist.");
0973: System.exit(1);
0974: }
0975: oa[0] = factory.create(stream, null);
0976: } else {
0977: File file = new File(args[0]);
0978: if (!file.exists()) {
0979: System.err
0980: .println("File does not exist or not a file.");
0981: System.exit(1);
0982: }
0983: if (!file.isFile()) {
0984: System.err.println("Not a file.");
0985: System.exit(1);
0986: }
0987: oa[0] = factory.create(file, null);
0988: }
0989:
0990: if (oa[0] instanceof Component) {
0991: ((Component) oa[0]).start();
0992: }
0993:
0994: if (oa[0] instanceof Restartable) {
0995: ((Restartable) oa[0]).setRestartCommand(this );
0996: }
0997:
0998: try {
0999: if (oa[0] instanceof Context) {
1000: Context container = (Context) oa[0];
1001: for (int i = 1; i < args.length; i++) {
1002: Object toExecute = container
1003: .get(args[i]);
1004: if (toExecute instanceof Command) {
1005: ((Command) toExecute).execute(args);
1006: } else if (toExecute == null) {
1007: System.err
1008: .print("[WARN] Name not found: "
1009: + args[i]);
1010: } else {
1011: System.err
1012: .print("[WARN] Not executable: ("
1013: + args[i]
1014: + ") "
1015: + toExecute
1016: .getClass()
1017: .getName());
1018: }
1019: }
1020: } else if (oa[0] instanceof Command) {
1021: ((Command) oa[0]).execute(args);
1022: }
1023: } finally {
1024: if (oa[0] instanceof Component) {
1025: if (!stopInHook) {
1026: ((Component) oa[0]).stop();
1027: }
1028: }
1029: }
1030: } catch (MalformedURLException e) {
1031: System.err.println("Bad configuration URL: "
1032: + args[0]);
1033: System.exit(2);
1034: } catch (Exception e) {
1035: e.printStackTrace();
1036: if (attempt > 1) {
1037: if (oa[0] instanceof Component) {
1038: try {
1039: ((Component) oa[0]).stop();
1040: } catch (Exception ex) {
1041: System.err
1042: .println("Cannot stop component before restart: "
1043: + e);
1044: ex.printStackTrace();
1045: }
1046: }
1047: new Thread(this , "Restart thread "
1048: + getAttempt()).start(); // Use a new thread to avoid stack overflowing in the case of too many attempts.
1049: } else {
1050: System.exit(3);
1051: }
1052: }
1053: }
1054:
1055: public int getAttempt() {
1056: return attempt;
1057: }
1058:
1059: public long getRestartDelay() {
1060: String rd = System.getProperty(RESTART_DELARY_PROPERTY);
1061: if (rd != null) {
1062: try {
1063: return Long.parseLong(rd);
1064: } catch (NumberFormatException e) {
1065: // Ignore
1066: }
1067: }
1068:
1069: return DEFAULT_RESTART_DELAY;
1070: }
1071: };
1072:
1073: run.run();
1074:
1075: }
1076: }
|