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 }