    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     *                                                                           *
    009     *****************************************************************************/
    010
    011    package org.nanocontainer.script.xml;
    012
    013    import java.io.IOException;
    014    import java.io.Reader;
    015    import java.net.URL;
    016    import java.util.ArrayList;
    017    import java.util.List;
    018
    019    import javax.xml.parsers.DocumentBuilderFactory;
    020    import javax.xml.parsers.ParserConfigurationException;
    021
    022    import org.nanocontainer.DefaultNanoContainer;
    023    import org.nanocontainer.NanoContainer;
    024    import org.nanocontainer.integrationkit.ContainerPopulator;
    025    import org.nanocontainer.script.NanoContainerMarkupException;
    026    import org.nanocontainer.script.ScriptedContainerBuilder;
    027    import org.picocontainer.ComponentAdapter;
    028    import org.picocontainer.MutablePicoContainer;
    029    import org.picocontainer.Parameter;
    030    import org.picocontainer.PicoContainer;
    031    import org.picocontainer.defaults.ComponentAdapterFactory;
    032    import org.picocontainer.defaults.ComponentParameter;
    033    import org.picocontainer.defaults.ConstantParameter;
    034    import org.picocontainer.defaults.DefaultComponentAdapterFactory;
    035    import org.picocontainer.defaults.DefaultPicoContainer;
    036    import org.w3c.dom.Document;
    037    import org.w3c.dom.Element;
    038    import org.w3c.dom.Node;
    039    import org.w3c.dom.NodeList;
    040    import org.xml.sax.InputSource;
    041    import org.xml.sax.SAXException;
    042
    043    import com.thoughtworks.xstream.XStream;
    044    import com.thoughtworks.xstream.io.HierarchicalStreamDriver;
    045    import com.thoughtworks.xstream.io.xml.DomDriver;
    046    import com.thoughtworks.xstream.io.xml.DomReader;
    047
    048    /**
    049     * This class builds up a hierarchy of PicoContainers from an XML configuration file.
    050     *
    051     * @author Konstantin Pribluda
    052     * @version $Revision: 2164 $
    053     */
    054    public class XStreamContainerBuilder extends ScriptedContainerBuilder implements ContainerPopulator {
    055        private final Element rootElement;
    056
    057        private final static String IMPLEMENTATION = "implementation";
    058        private final static String INSTANCE = "instance";
    059        private final static String ADAPTER = "adapter";
    060        private final static String CLASS = "class";
    061        private final static String KEY = "key";
    062        private final static String CONSTANT = "constant";
    063        private final static String DEPENDENCY = "dependency";
    064        private final static String CONSTRUCTOR = "constructor";
    065
    066        private final HierarchicalStreamDriver xsdriver;
    067
    068        /**
    069        * construct with just reader, use context classloader
    070        */
    071        public XStreamContainerBuilder(Reader script) {
    072            this(script,Thread.currentThread().getContextClassLoader());
    073        }
    074
    075        /**
    076         * construct with given script and specified classloader
    077         */
    078        public XStreamContainerBuilder(Reader script, ClassLoader classLoader) {
    079            this(script, classLoader, new DomDriver());
    080        }
    081
    082        public XStreamContainerBuilder(Reader script, ClassLoader classLoader, HierarchicalStreamDriver driver) {
    083            super(script, classLoader);
    084            xsdriver = driver;
    085            InputSource inputSource = new InputSource(script);
    086            try {
    087                rootElement = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputSource).getDocumentElement();
    088            } catch (SAXException e) {
    089                throw new NanoContainerMarkupException(e);
    090            } catch (IOException e) {
    091                throw new NanoContainerMarkupException(e);
    092            } catch (ParserConfigurationException e) {
    093                throw new NanoContainerMarkupException(e);
    094            }
    095        }
    096
    097        public XStreamContainerBuilder(URL script, ClassLoader classLoader, HierarchicalStreamDriver driver) {
    098            super(script, classLoader);
    099            xsdriver = driver;
    100            try {
    101                InputSource inputSource = new InputSource(getScriptReader());
    102                rootElement = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputSource).getDocumentElement();
    103            } catch (SAXException e) {
    104                throw new NanoContainerMarkupException(e);
    105            } catch (IOException e) {
    106                throw new NanoContainerMarkupException(e);
    107            } catch (ParserConfigurationException e) {
    108                throw new NanoContainerMarkupException(e);
    109            }
    110        }
    111
    112        public void populateContainer(MutablePicoContainer container) {
    113            populateContainer(container, rootElement);
    114        }
    115
    116        /**
    117         * just a convenience method, so we can work recursively with subcontainers
    118         * for whatever puproses we see cool.
    119         */
    120        private void populateContainer(MutablePicoContainer container, Element rootElement) {
    121            NodeList children = rootElement.getChildNodes();
    122            Node child;
    123            String name;
    124            short type;
    125            for (int i = 0; i < children.getLength(); i++) {
    126                child = children.item(i);
    127                type = child.getNodeType();
    128
    129                if (type == Document.ELEMENT_NODE) {
    130                    name = child.getNodeName();
    131                    if (IMPLEMENTATION.equals(name)) {
    132                        try {
    133                            insertImplementation(container, (Element) child);
    134                        } catch (ClassNotFoundException e) {
    135                            throw new NanoContainerMarkupException(e);
    136                        }
    137                    } else if (INSTANCE.equals(name)) {
    138                        insertInstance(container, (Element) child);
    139                    } else if (ADAPTER.equals(name)) {
    140                        insertAdapter(container, (Element) child);
    141                    } else {
    142                        throw new NanoContainerMarkupException("Unsupported element:" + name);
    143                    }
    144                }
    145            }
    146
    147        }
    148
    149        /**
    150         * process adapter node
    151         */
    152        protected void insertAdapter(MutablePicoContainer container, Element rootElement) {
    153            String key = rootElement.getAttribute(KEY);
    154            String klass = rootElement.getAttribute(CLASS);
    155            try {
    156                DefaultPicoContainer nested = new DefaultPicoContainer();
    157                populateContainer(nested, rootElement);
    158
    159                if (key != null) {
    160                    container.registerComponent((ComponentAdapter) nested.getComponentInstance(key));
    161                } else if (klass != null) {
    162                    Class clazz = getClassLoader().loadClass(klass);
    163                    container.registerComponent((ComponentAdapter) nested.getComponentInstanceOfType(clazz));
    164                } else {
    165                    container.registerComponent((ComponentAdapter) nested.getComponentInstanceOfType(ComponentAdapter.class));
    166                }
    167            } catch (ClassNotFoundException ex) {
    168                throw new NanoContainerMarkupException(ex);
    169            }
    170
    171        }
    172
    173        /**
    174         * process implementation node
    175         */
    176        protected void insertImplementation(MutablePicoContainer container, Element rootElement) throws ClassNotFoundException {
    177            String key = rootElement.getAttribute(KEY);
    178            String klass = rootElement.getAttribute(CLASS);
    179            String constructor = rootElement.getAttribute(CONSTRUCTOR);
    180            if (klass == null || "".equals(klass)) {
    181                throw new NanoContainerMarkupException("class specification is required for component implementation");
    182            }
    183
    184            Class clazz = getClassLoader().loadClass(klass);
    185
    186            List parameters = new ArrayList();
    187
    188            NodeList children = rootElement.getChildNodes();
    189            Node child;
    190            String name;
    191            String dependencyKey;
    192            String dependencyClass;
    193            Object parseResult;
    194
    195            for (int i = 0; i < children.getLength(); i++) {
    196                child = children.item(i);
    197                if (child.getNodeType() == Document.ELEMENT_NODE) {
    198                    name = child.getNodeName();
    199                    // constant parameter. it does not have any attributes.
    200                    if (CONSTANT.equals(name)) {
    201                        // create constant with xstream
    202                        parseResult = parseElementChild((Element) child);
    203                        if (parseResult == null) {
    204                            throw new NanoContainerMarkupException("could not parse constant parameter");
    205                        }
    206                        parameters.add(new ConstantParameter(parseResult));
    207                    } else if (DEPENDENCY.equals(name)) {
    208                        // either key or class must be present. not both
    209                        // key has prececence
    210                        dependencyKey = ((Element) child).getAttribute(KEY);
    211                        if (dependencyKey == null || "".equals(dependencyKey)) {
    212                            dependencyClass = ((Element) child).getAttribute(CLASS);
    213                            if (dependencyClass == null || "".equals(dependencyClass)) {
    214                                throw new NanoContainerMarkupException("either key or class must be present for dependency");
    215                            } else {
    216                                parameters.add(new ComponentParameter(getClassLoader().loadClass(dependencyClass)));
    217                            }
    218                        } else {
    219                            parameters.add(new ComponentParameter(dependencyKey));
    220                        }
    221                    }
    222                }
    223            }
    224
    225            // ok , we processed our children. insert implementation
    226            Parameter[] parameterArray = (Parameter[]) parameters.toArray(new Parameter[parameters.size()]);
    227            if (parameters.size() > 0 || "default".equals(constructor)) {
    228                if (key == null || "".equals(key)) {
    229                    // without  key. clazz is our key
    230                    container.registerComponentImplementation(clazz, clazz, parameterArray);
    231                } else {
    232                    // with key
    233                    container.registerComponentImplementation(key, clazz, parameterArray);
    234                }
    235            } else {
    236                if (key == null || "".equals(key)) {
    237                    // without  key. clazz is our key
    238                    container.registerComponentImplementation(clazz, clazz);
    239                } else {
    240                    // with key
    241                    container.registerComponentImplementation(key, clazz);
    242                }
    243
    244            }
    245        }
    246
    247        /**
    248         * process instance node. we get key from atributes ( if any ) and leave content
    249         * to xstream. we allow only one child node inside. ( first  one wins )
    250         */
    251        protected void insertInstance(MutablePicoContainer container, Element rootElement) {
    252            String key = rootElement.getAttribute(KEY);
    253            Object result = parseElementChild(rootElement);
    254            if (result == null) {
    255                throw new NanoContainerMarkupException("no content could be parsed in instance");
    256            }
    257            if (key != null && !"".equals(key)) {
    258                // insert with key
    259                container.registerComponentInstance(key, result);
    260            } else {
    261                // or without
    262                container.registerComponentInstance(result);
    263            }
    264        }
    265
    266        /**
    267         * parse element child with xstream and provide object
    268         */
    269        protected Object parseElementChild(Element rootElement) {
    270            NodeList children = rootElement.getChildNodes();
    271            Node child;
    272            for (int i = 0; i < children.getLength(); i++) {
    273                child = children.item(i);
    274                if (child.getNodeType() == Document.ELEMENT_NODE) {
    275                    return (new XStream(xsdriver)).unmarshal(new DomReader((Element) child));
    276                }
    277            }
    278            return null;
    279        }
    280
    281        protected PicoContainer createContainerFromScript(PicoContainer parentContainer, Object assemblyScope) {
    282            try {
    283                String cafName = rootElement.getAttribute("componentadapterfactory");
    284                if ("".equals(cafName) || cafName == null) {
    285                    cafName = DefaultComponentAdapterFactory.class.getName();
    286                }
    287                Class cafClass = getClassLoader().loadClass(cafName);
    288                ComponentAdapterFactory componentAdapterFactory = (ComponentAdapterFactory) cafClass.newInstance();
    289                MutablePicoContainer picoContainer = new DefaultPicoContainer(componentAdapterFactory);
    290                NanoContainer nano = new DefaultNanoContainer(getClassLoader(), picoContainer);
    291                populateContainer(nano.getPico());
    292                return nano.getPico();
    293            } catch (ClassNotFoundException e) {
    294                throw new NanoContainerMarkupException(e);
    295            } catch (InstantiationException e) {
    296                throw new NanoContainerMarkupException(e);
    297            } catch (IllegalAccessException e) {
    298                throw new NanoContainerMarkupException(e);
    299            }
    300        }
    301    }