    001    /*****************************************************************************
    002     * Copyright (C) NanoContainer Organization. All rights reserved.            *
    003     * ------------------------------------------------------------------------- *
    004     * The software in this package is published under the terms of the BSD      *
    005     * style license a copy of which has been included with this distribution in *
    006     * the LICENSE.txt file.                                                     *
    007     *                                                                           *
    008     * Original code by Aslak Hellesoy and Paul Hammant                          *
    009     *****************************************************************************/
    010
    011    package org.nanocontainer.script.xml;
    012
    013    import java.io.File;
    014    import java.io.IOException;
    015    import java.io.Reader;
    016    import java.net.MalformedURLException;
    017    import java.net.URL;
    018    import java.security.Permission;
    019    import java.util.ArrayList;
    020    import java.util.List;
    021
    022    import javax.xml.parsers.DocumentBuilder;
    023    import javax.xml.parsers.DocumentBuilderFactory;
    024    import javax.xml.parsers.ParserConfigurationException;
    025
    026    import org.nanocontainer.ClassNameKey;
    027    import org.nanocontainer.ClassPathElement;
    028    import org.nanocontainer.DefaultNanoContainer;
    029    import org.nanocontainer.NanoContainer;
    030    import org.nanocontainer.reflection.DefaultNanoPicoContainer;
    031    import org.nanocontainer.integrationkit.ContainerPopulator;
    032    import org.nanocontainer.integrationkit.PicoCompositionException;
    033    import org.nanocontainer.script.NanoContainerMarkupException;
    034    import org.nanocontainer.script.ScriptedContainerBuilder;
    035    import org.picocontainer.ComponentMonitor;
    036    import org.picocontainer.MutablePicoContainer;
    037    import org.picocontainer.Parameter;
    038    import org.picocontainer.PicoContainer;
    039    import org.picocontainer.defaults.ComponentAdapterFactory;
    040    import org.picocontainer.defaults.ComponentMonitorStrategy;
    041    import org.picocontainer.defaults.ComponentParameter;
    042    import org.picocontainer.defaults.ConstantParameter;
    043    import org.picocontainer.defaults.DefaultComponentAdapterFactory;
    044    import org.picocontainer.defaults.DefaultPicoContainer;
    045    import org.picocontainer.defaults.DelegatingComponentMonitor;
    046    import org.w3c.dom.Element;
    047    import org.w3c.dom.NodeList;
    048    import org.xml.sax.EntityResolver;
    049    import org.xml.sax.InputSource;
    050    import org.xml.sax.SAXException;
    051
    052    /**
    053     * This class builds up a hierarchy of PicoContainers from an XML configuration file.
    054     *
    055     * @author Paul Hammant
    056     * @author Aslak Helles&oslash;y
    057     * @author Jeppe Cramon
    058     * @author Mauro Talevi
    059     * @version $Revision: 2953 $
    060     */
    061    public class XMLContainerBuilder extends ScriptedContainerBuilder implements ContainerPopulator {
    062
    063        private final static String DEFAULT_COMPONENT_ADAPTER_FACTORY = DefaultComponentAdapterFactory.class.getName();
    064        private final static String DEFAULT_COMPONENT_INSTANCE_FACTORY = BeanComponentInstanceFactory.class.getName();
    065        private final static String DEFAULT_COMPONENT_MONITOR = DelegatingComponentMonitor.class.getName();
    066
    067        private final static String CONTAINER = "container";
    068        private final static String CLASSPATH = "classpath";
    069        private final static String CLASSLOADER = "classloader";
    070        private static final String CLASS_NAME_KEY = "class-name-key";
    071        private final static String COMPONENT = "component";
    072        private final static String COMPONENT_IMPLEMENTATION = "component-implementation";
    073        private final static String COMPONENT_INSTANCE = "component-instance";
    074        private final static String COMPONENT_ADAPTER = "component-adapter";
    075        private final static String COMPONENT_ADAPTER_FACTORY = "component-adapter-factory";
    076        private final static String COMPONENT_INSTANCE_FACTORY = "component-instance-factory";
    077        private final static String COMPONENT_MONITOR = "component-monitor";
    078        private final static String DECORATING_PICOCONTAINER = "decorating-picocontainer";
    079        private final static String CLASS = "class";
    080        private final static String FACTORY = "factory";
    081        private final static String FILE = "file";
    082        private final static String KEY = "key";
    083        private final static String PARAMETER = "parameter";
    084        private final static String URL = "url";
    085
    086        private final static String CLASSNAME = "classname";
    087        private final static String CONTEXT = "context";
    088        private final static String VALUE = "value";
    089
    090        private static final String EMPTY = "";
    091
    092        private Element rootElement;
    093        /**
    094         * The XMLComponentInstanceFactory globally defined for the container.
    095         * It may be overridden at node level.
    096         */
    097        private XMLComponentInstanceFactory componentInstanceFactory;
    098
    099        public XMLContainerBuilder(Reader script, ClassLoader classLoader) {
    100            super(script, classLoader);
    101            try {
    102                DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
    103                parse(documentBuilder, new InputSource(script));
    104            } catch (ParserConfigurationException e) {
    105                throw new NanoContainerMarkupException(e);
    106            }
    107        }
    108
    109        public XMLContainerBuilder(final URL script, ClassLoader classLoader) {
    110            super(script, classLoader);
    111            try {
    112                DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
    113                documentBuilder.setEntityResolver(new EntityResolver() {
    114                    public InputSource resolveEntity(String publicId, String systemId) throws IOException {
    115                        URL url = new URL(script, systemId);
    116                        return new InputSource(url.openStream());
    117                    }
    118                });
    119                parse(documentBuilder, new InputSource(script.toString()));
    120            } catch (ParserConfigurationException e) {
    121                throw new NanoContainerMarkupException(e);
    122            }
    123        }
    124
    125        private void parse(DocumentBuilder documentBuilder, InputSource inputSource) {
    126            try {
    127                rootElement = documentBuilder.parse(inputSource).getDocumentElement();
    128            } catch (SAXException e) {
    129                throw new NanoContainerMarkupException(e);
    130            } catch (IOException e) {
    131                throw new NanoContainerMarkupException(e);
    132            }
    133        }
    134
    135        protected PicoContainer createContainerFromScript(PicoContainer parentContainer, Object assemblyScope) {
    136            try {
    137                // create ComponentInstanceFactory for the container
    138                componentInstanceFactory = createComponentInstanceFactory(rootElement.getAttribute(COMPONENT_INSTANCE_FACTORY));
    139                MutablePicoContainer childContainer = createMutablePicoContainer(rootElement.getAttribute(COMPONENT_ADAPTER_FACTORY),
    140                        rootElement.getAttribute(COMPONENT_MONITOR), parentContainer);
    141                populateContainer(childContainer);
    142                return childContainer;
    143            } catch (ClassNotFoundException e) {
    144                throw new NanoContainerMarkupException("Class not found:" + e.getMessage(), e);
    145            }
    146        }
    147
    148        private MutablePicoContainer createMutablePicoContainer(String cafName, String monitorName, PicoContainer parentContainer) throws PicoCompositionException, ClassNotFoundException {
    149            MutablePicoContainer container = new DefaultNanoPicoContainer(getClassLoader(),createComponentAdapterFactory(cafName, new DefaultNanoContainer(getClassLoader())), parentContainer);
    150            if ( !notSet(monitorName) ){
    151                ComponentMonitor monitor = createComponentMonitor(monitorName);
    152                ((ComponentMonitorStrategy)container).changeMonitor(monitor);
    153            }
    154            return container;
    155        }
    156
    157        public void populateContainer(MutablePicoContainer container) {
    158            try {
    159                String parentClass = rootElement.getAttribute("parentclassloader");
    160                ClassLoader classLoader = getClassLoader();
    161                if (parentClass != null && !EMPTY.equals(parentClass)) {
    162                    classLoader = classLoader.loadClass(parentClass).getClassLoader();
    163                }
    164                NanoContainer nanoContainer = new DefaultNanoContainer(classLoader, container);
    165                registerComponentsAndChildContainers(nanoContainer, rootElement, new DefaultNanoContainer(getClassLoader()));
    166            } catch (ClassNotFoundException e) {
    167                throw new NanoContainerMarkupException("Class not found: " + e.getMessage(), e);
    168            } catch (IOException e) {
    169                throw new NanoContainerMarkupException(e);
    170            } catch (SAXException e) {
    171                throw new NanoContainerMarkupException(e);
    172            }
    173        }
    174
    175        private void registerComponentsAndChildContainers(NanoContainer parentContainer, Element containerElement, NanoContainer knownComponentAdapterFactories) throws ClassNotFoundException, IOException, SAXException {
    176
    177            NanoContainer metaContainer = new DefaultNanoContainer(getClassLoader(), knownComponentAdapterFactories.getPico());
    178            NodeList children = containerElement.getChildNodes();
    179            // register classpath first, regardless of order in the document.
    180            for (int i = 0; i < children.getLength(); i++) {
    181                if (children.item(i) instanceof Element) {
    182                    Element childElement = (Element) children.item(i);
    183                    String name = childElement.getNodeName();
    184                    if (CLASSPATH.equals(name)) {
    185                        registerClasspath(parentContainer, childElement);
    186                    }
    187                }
    188            }
    189            for (int i = 0; i < children.getLength(); i++) {
    190                if (children.item(i) instanceof Element) {
    191                    Element childElement = (Element) children.item(i);
    192                    String name = childElement.getNodeName();
    193                    if (CONTAINER.equals(name)) {
    194                        MutablePicoContainer childContainer = parentContainer.getPico().makeChildContainer();
    195                        NanoContainer childNanoContainer = new DefaultNanoContainer(parentContainer.getComponentClassLoader(), childContainer);
    196                        registerComponentsAndChildContainers(childNanoContainer, childElement, metaContainer);
    197                    } else if (COMPONENT_IMPLEMENTATION.equals(name)
    198                            || COMPONENT.equals(name)) {
    199                        registerComponentImplementation(parentContainer, childElement);
    200                    } else if (COMPONENT_INSTANCE.equals(name)) {
    201                        registerComponentInstance(parentContainer, childElement);
    202                    } else if (COMPONENT_ADAPTER.equals(name)) {
    203                        registerComponentAdapter(parentContainer, childElement, metaContainer);
    204                    } else if (COMPONENT_ADAPTER_FACTORY.equals(name)) {
    205                        addComponentAdapterFactory(childElement, metaContainer);
    206                    } else if (CLASSLOADER.equals(name)) {
    207                        registerClassLoader(parentContainer, childElement, metaContainer);
    208                    } else if (DECORATING_PICOCONTAINER.equals(name)) {
    209                        addDecoratingPicoContainer(parentContainer, childElement);
    210                    } else if (CLASSPATH.equals(name) != true) {
    211                        throw new NanoContainerMarkupException("Unsupported element:" + name);
    212                    }
    213                }
    214            }
    215        }
    216
    217
    218        private void addComponentAdapterFactory(Element element, NanoContainer metaContainer) throws MalformedURLException, ClassNotFoundException {
    219            if (notSet(element.getAttribute(KEY))) {
    220                throw new NanoContainerMarkupException("'" + KEY + "' attribute not specified for " + element.getNodeName());
    221            }
    222            Element node = (Element)element.cloneNode(false);
    223            NodeList children = element.getChildNodes();
    224            for (int i = 0; i < children.getLength(); i++) {
    225                if (children.item(i) instanceof Element) {
    226                    Element childElement = (Element) children.item(i);
    227                    String name = childElement.getNodeName();
    228                    if (COMPONENT_ADAPTER_FACTORY.equals(name)) {
    229                        if (!"".equals(childElement.getAttribute(KEY))) {
    230                            throw new NanoContainerMarkupException("'" + KEY + "' attribute must not be specified for nested " + element.getNodeName());
    231                        }
    232                        childElement = (Element)childElement.cloneNode(true);
    233                        String key = String.valueOf(System.identityHashCode(childElement));
    234                        childElement.setAttribute(KEY, key);
    235                        addComponentAdapterFactory(childElement, metaContainer);
    236                        // replace nested CAF with a ComponentParameter using an internally generated key
    237                        Element parameter = node.getOwnerDocument().createElement(PARAMETER);
    238                        parameter.setAttribute(KEY, key);
    239                        node.appendChild(parameter);
    240                    } else if (PARAMETER.equals(name)) {
    241                        node.appendChild(childElement.cloneNode(true));
    242                    }
    243                }
    244            }
    245            // handle CAF now as standard component in the metaContainer
    246            registerComponentImplementation(metaContainer, node);
    247        }
    248
    249        private void registerClassLoader(NanoContainer parentContainer, Element childElement, NanoContainer metaContainer) throws IOException, SAXException, ClassNotFoundException {
    250            String parentClass = childElement.getAttribute("parentclassloader");
    251            ClassLoader parentClassLoader = parentContainer.getComponentClassLoader();
    252            if (parentClass != null && !EMPTY.equals(parentClass)) {
    253                parentClassLoader = parentClassLoader.loadClass(parentClass).getClassLoader();
    254            }
    255            NanoContainer nano = new DefaultNanoContainer(parentClassLoader, parentContainer.getPico());
    256            registerComponentsAndChildContainers(nano, childElement, metaContainer);
    257        }
    258
    259        private void registerClasspath(NanoContainer container, Element classpathElement) throws IOException, ClassNotFoundException {
    260            NodeList children = classpathElement.getChildNodes();
    261            for (int i = 0; i < children.getLength(); i++) {
    262                if (children.item(i) instanceof Element) {
    263                    Element childElement = (Element) children.item(i);
    264
    265                    String fileName = childElement.getAttribute(FILE);
    266                    String urlSpec = childElement.getAttribute(URL);
    267                    URL url = null;
    268                    if (urlSpec != null && !EMPTY.equals(urlSpec)) {
    269                        url = new URL(urlSpec);
    270                    } else {
    271                        File file = new File(fileName);
    272                        if (!file.exists()) {
    273                            throw new IOException(file.getAbsolutePath() + " doesn't exist");
    274                        }
    275                        url = file.toURL();
    276                    }
    277                    ClassPathElement cpe = container.addClassLoaderURL(url);
    278                    registerPermissions(cpe, childElement);
    279                }
    280            }
    281        }
    282
    283        private void registerPermissions(ClassPathElement classPathElement, Element classPathXmlElement) throws ClassNotFoundException {
    284            NodeList children = classPathXmlElement.getChildNodes();
    285            for (int i = 0; i < children.getLength(); i++) {
    286                if (children.item(i) instanceof Element) {
    287                    Element childElement = (Element) children.item(i);
    288
    289                    String permissionClassName = childElement.getAttribute(CLASSNAME);
    290                    String action = childElement.getAttribute(CONTEXT);
    291                    String value = childElement.getAttribute(VALUE);
    292                    MutablePicoContainer mpc = new DefaultPicoContainer();
    293                    mpc.registerComponentImplementation(Permission.class, Class.forName(permissionClassName),new Parameter[] {new ConstantParameter(action), new ConstantParameter(value)});
    294
    295                    Permission permission = (Permission) mpc.getComponentInstanceOfType(Permission.class);
    296                    classPathElement.grantPermission(permission);
    297                }
    298            }
    299
    300        }
    301
    302        private void registerComponentImplementation(NanoContainer container, Element element) throws ClassNotFoundException, MalformedURLException {
    303            String className = element.getAttribute(CLASS);
    304            if (notSet(className)) {
    305                throw new NanoContainerMarkupException("'" + CLASS + "' attribute not specified for " + element.getNodeName());
    306            }
    307
    308            Parameter[] parameters = createChildParameters(container, element);
    309            Class clazz = container.getComponentClassLoader().loadClass(className);
    310            Object key = element.getAttribute(KEY);
    311            String classKey = element.getAttribute(CLASS_NAME_KEY);
    312            if (notSet(key)) {
    313                if (!notSet(classKey)) {
    314                    key = getClassLoader().loadClass(classKey);
    315                } else {
    316                    key = clazz;
    317                }
    318            }
    319            if (parameters == null) {
    320                container.getPico().registerComponentImplementation(key, clazz);
    321            } else {
    322                container.getPico().registerComponentImplementation(key, clazz, parameters);
    323            }
    324        }
    325
    326        private void addDecoratingPicoContainer(NanoContainer parentContainer, Element childElement) throws ClassNotFoundException {
    327            String className = childElement.getAttribute("class");
    328
    329            parentContainer.addDecoratingPicoContainer(getClassLoader().loadClass(className));
    330
    331        }
    332
    333
    334
    335        private Parameter[] createChildParameters(NanoContainer container, Element element) throws ClassNotFoundException, MalformedURLException {
    336            List parametersList = new ArrayList();
    337            NodeList children = element.getChildNodes();
    338            for (int i = 0; i < children.getLength(); i++) {
    339                if (children.item(i) instanceof Element) {
    340                    Element childElement = (Element) children.item(i);
    341                    if (PARAMETER.equals(childElement.getNodeName())) {
    342                        parametersList.add(createParameter(container.getPico(), childElement));
    343                    }
    344                }
    345            }
    346
    347            Parameter[] parameters = null;
    348            if (!parametersList.isEmpty()) {
    349                parameters = (Parameter[]) parametersList.toArray(new Parameter[parametersList.size()]);
    350            }
    351            return parameters;
    352        }
    353
    354        private Parameter createParameter(PicoContainer pico, Element element) throws ClassNotFoundException, MalformedURLException {
    355            final Parameter parameter;
    356            String key = element.getAttribute(KEY);
    357            if (key != null && !EMPTY.equals(key)) {
    358                parameter = new ComponentParameter(key);
    359            } else if (getFirstChildElement(element, false) == null) {
    360                parameter = new ComponentParameter();
    361            } else {
    362                Object instance = createInstance(pico, element);
    363                parameter = new ConstantParameter(instance);
    364            }
    365            return parameter;
    366        }
    367
    368        private void registerComponentInstance(NanoContainer container, Element element) throws ClassNotFoundException, PicoCompositionException, MalformedURLException {
    369            Object instance = createInstance(container.getPico(), element);
    370            String key = element.getAttribute(KEY);
    371            String classKey = element.getAttribute(CLASS_NAME_KEY);
    372            if (notSet(key)) {
    373                if (!notSet(classKey)) {
    374                    container.getPico().registerComponentInstance(getClassLoader().loadClass(classKey), instance);
    375                } else {
    376                    container.getPico().registerComponentInstance(instance);
    377                }
    378            } else {
    379                container.getPico().registerComponentInstance(key, instance);
    380            }
    381        }
    382
    383        private Object createInstance(PicoContainer pico, Element element) throws ClassNotFoundException, MalformedURLException {
    384            XMLComponentInstanceFactory factory = createComponentInstanceFactory(element.getAttribute(FACTORY));
    385            Element instanceElement = getFirstChildElement(element, true);
    386            return factory.makeInstance(pico, instanceElement, getClassLoader());
    387        }
    388
    389        private Element getFirstChildElement(Element parent, boolean fail) {
    390            NodeList children = parent.getChildNodes();
    391            Element child = null;
    392            for (int i = 0; i < children.getLength(); i++) {
    393                if (children.item(i) instanceof Element) {
    394                    child = (Element) children.item(i);
    395                    break;
    396                }
    397            }
    398            if (child == null && fail) {
    399                throw new NanoContainerMarkupException(parent.getNodeName() + " needs a child element");
    400            }
    401            return child;
    402        }
    403
    404        private XMLComponentInstanceFactory createComponentInstanceFactory(String factoryClass) throws ClassNotFoundException {
    405            if ( notSet(factoryClass)) {
    406                // no factory has been specified for the node
    407                // return globally defined factory for the container - if there is one
    408                if (componentInstanceFactory != null) {
    409                    return componentInstanceFactory;
    410                }
    411                factoryClass = DEFAULT_COMPONENT_INSTANCE_FACTORY;
    412            }
    413
    414            NanoContainer adapter = new DefaultNanoContainer(getClassLoader());
    415            adapter.registerComponentImplementation(XMLComponentInstanceFactory.class.getName(), factoryClass);
    416            return (XMLComponentInstanceFactory) adapter.getPico().getComponentInstances().get(0);
    417        }
    418
    419        private void registerComponentAdapter(NanoContainer container, Element element, NanoContainer metaContainer) throws ClassNotFoundException, PicoCompositionException, MalformedURLException {
    420            String className = element.getAttribute(CLASS);
    421            if (notSet(className)) {
    422                throw new NanoContainerMarkupException("'" + CLASS + "' attribute not specified for " + element.getNodeName());
    423            }
    424            Class implementationClass = getClassLoader().loadClass(className);
    425            Object key = element.getAttribute(KEY);
    426            String classKey = element.getAttribute(CLASS_NAME_KEY);
    427            if (notSet(key)) {
    428                if (!notSet(classKey)) {
    429                    key = getClassLoader().loadClass(classKey);
    430                } else {
    431                    key = implementationClass;
    432                }
    433            }
    434            Parameter[] parameters = createChildParameters(container, element);
    435            ComponentAdapterFactory componentAdapterFactory = createComponentAdapterFactory(element.getAttribute(FACTORY), metaContainer);
    436            container.getPico().registerComponent(componentAdapterFactory.createComponentAdapter(key, implementationClass, parameters));
    437        }
    438
    439        private ComponentAdapterFactory createComponentAdapterFactory(String factoryName, NanoContainer metaContainer) throws ClassNotFoundException, PicoCompositionException {
    440            if ( notSet(factoryName)) {
    441                factoryName = DEFAULT_COMPONENT_ADAPTER_FACTORY;
    442            }
    443            final Object key;
    444            if (metaContainer.getPico().getComponentAdapter(factoryName) != null) {
    445                key = factoryName;
    446            } else {
    447                metaContainer.registerComponentImplementation(new ClassNameKey(ComponentAdapterFactory.class.getName()), factoryName);
    448                key = ComponentAdapterFactory.class;
    449            }
    450            return (ComponentAdapterFactory) metaContainer.getPico().getComponentInstance(key);
    451        }
    452
    453        private ComponentMonitor createComponentMonitor(String monitorName) throws ClassNotFoundException, PicoCompositionException {
    454            if (notSet(monitorName)) {
    455                monitorName = DEFAULT_COMPONENT_MONITOR;
    456            }
    457            Class monitorClass = getClassLoader().loadClass(monitorName);
    458            try {
    459                return (ComponentMonitor) monitorClass.newInstance();
    460            } catch (InstantiationException e) {
    461                throw new NanoContainerMarkupException(e);
    462            } catch (IllegalAccessException e) {
    463                throw new NanoContainerMarkupException(e);
    464            }
    465        }
    466
    467        private boolean notSet(Object string) {
    468            return string == null || string.equals(EMPTY);
    469        }
    470
    471    }