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