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    }