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 }