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ø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    }