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 static org.picocontainer.script.xml.XMLConstants.COMPONENT_INSTANCE_FACTORY; 011 import static org.picocontainer.script.xml.XMLConstants.PARAMETER_ZERO; 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.picocontainer.ComponentAdapter; 023 import org.picocontainer.ComponentFactory; 024 import org.picocontainer.DefaultPicoContainer; 025 import org.picocontainer.MutablePicoContainer; 026 import org.picocontainer.Parameter; 027 import org.picocontainer.PicoClassNotFoundException; 028 import org.picocontainer.PicoCompositionException; 029 import org.picocontainer.PicoContainer; 030 import org.picocontainer.classname.DefaultClassLoadingPicoContainer; 031 import org.picocontainer.behaviors.Caching; 032 import org.picocontainer.injectors.ConstructorInjection; 033 import org.picocontainer.parameters.ComponentParameter; 034 import org.picocontainer.parameters.ConstantParameter; 035 import org.picocontainer.script.LifecycleMode; 036 import org.picocontainer.script.ScriptedBuilder; 037 import org.picocontainer.script.ScriptedContainerBuilder; 038 import org.picocontainer.script.ScriptedPicoContainerMarkupException; 039 import org.w3c.dom.Document; 040 import org.w3c.dom.Element; 041 import org.w3c.dom.Node; 042 import org.w3c.dom.NodeList; 043 import org.xml.sax.InputSource; 044 import org.xml.sax.SAXException; 045 046 import com.thoughtworks.xstream.XStream; 047 import com.thoughtworks.xstream.io.HierarchicalStreamDriver; 048 import com.thoughtworks.xstream.io.xml.DomDriver; 049 import com.thoughtworks.xstream.io.xml.DomReader; 050 051 /** 052 * This class builds up a hierarchy of PicoContainers from an XML configuration 053 * file. 054 * 055 * @author Konstantin Pribluda 056 */ 057 public class XStreamContainerBuilder extends ScriptedContainerBuilder { 058 private final Element rootElement; 059 060 private final static String IMPLEMENTATION = "implementation"; 061 private final static String INSTANCE = "instance"; 062 private final static String ADAPTER = "adapter"; 063 private final static String CLASS = "class"; 064 private final static String KEY = "key"; 065 private final static String CONSTANT = "constant"; 066 private final static String DEPENDENCY = "dependency"; 067 private final static String CONSTRUCTOR = "constructor"; 068 069 private final HierarchicalStreamDriver xsdriver; 070 071 /** 072 * construct with just reader, use context classloader 073 * 074 * @param script 075 */ 076 public XStreamContainerBuilder(Reader script) { 077 this(script, Thread.currentThread().getContextClassLoader()); 078 } 079 080 /** 081 * construct with given script and specified classloader 082 * 083 * @param classLoader 084 * @param script 085 */ 086 public XStreamContainerBuilder(Reader script, ClassLoader classLoader) { 087 this(script, classLoader, new DomDriver()); 088 } 089 090 public XStreamContainerBuilder(Reader script, ClassLoader classLoader, HierarchicalStreamDriver driver) { 091 this(script, classLoader, driver, LifecycleMode.AUTO_LIFECYCLE); 092 } 093 094 public XStreamContainerBuilder(Reader script, ClassLoader classLoader, HierarchicalStreamDriver driver, 095 LifecycleMode lifecycleMode) { 096 super(script, classLoader, lifecycleMode); 097 xsdriver = driver; 098 InputSource inputSource = new InputSource(script); 099 try { 100 rootElement = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputSource) 101 .getDocumentElement(); 102 } catch (SAXException e) { 103 throw new ScriptedPicoContainerMarkupException(e); 104 } catch (IOException e) { 105 throw new ScriptedPicoContainerMarkupException(e); 106 } catch (ParserConfigurationException e) { 107 throw new ScriptedPicoContainerMarkupException(e); 108 } 109 } 110 111 public XStreamContainerBuilder(URL script, ClassLoader classLoader, HierarchicalStreamDriver driver) { 112 this(script, classLoader, driver, LifecycleMode.AUTO_LIFECYCLE); 113 } 114 115 public XStreamContainerBuilder(URL script, ClassLoader classLoader, HierarchicalStreamDriver driver, 116 LifecycleMode lifecycleMode) { 117 super(script, classLoader, lifecycleMode); 118 xsdriver = driver; 119 try { 120 InputSource inputSource = new InputSource(getScriptReader()); 121 rootElement = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputSource) 122 .getDocumentElement(); 123 } catch (SAXException e) { 124 throw new ScriptedPicoContainerMarkupException(e); 125 } catch (IOException e) { 126 throw new ScriptedPicoContainerMarkupException(e); 127 } catch (ParserConfigurationException e) { 128 throw new ScriptedPicoContainerMarkupException(e); 129 } 130 } 131 132 public void populateContainer(MutablePicoContainer container) { 133 populateContainer(container, rootElement); 134 } 135 136 /** 137 * just a convenience method, so we can work recursively with subcontainers 138 * for whatever puproses we see cool. 139 * 140 * @param container 141 * @param rootElement 142 */ 143 private void populateContainer(MutablePicoContainer container, Element rootElement) { 144 NodeList children = rootElement.getChildNodes(); 145 Node child; 146 String name; 147 short type; 148 for (int i = 0; i < children.getLength(); i++) { 149 child = children.item(i); 150 type = child.getNodeType(); 151 152 if (type == Document.ELEMENT_NODE) { 153 name = child.getNodeName(); 154 if (IMPLEMENTATION.equals(name)) { 155 try { 156 insertImplementation(container, (Element) child); 157 } catch (ClassNotFoundException e) { 158 throw new ScriptedPicoContainerMarkupException(e); 159 } 160 } else if (INSTANCE.equals(name)) { 161 insertInstance(container, (Element) child); 162 } else if (ADAPTER.equals(name)) { 163 insertAdapter(container, (Element) child); 164 } else { 165 throw new ScriptedPicoContainerMarkupException("Unsupported element:" + name); 166 } 167 } 168 } 169 170 } 171 172 /** 173 * process adapter node 174 * 175 * @param container 176 * @param rootElement 177 */ 178 @SuppressWarnings("unchecked") 179 protected void insertAdapter(MutablePicoContainer container, Element rootElement) { 180 String key = rootElement.getAttribute(KEY); 181 String klass = rootElement.getAttribute(CLASS); 182 try { 183 DefaultPicoContainer nested = new DefaultPicoContainer(); 184 populateContainer(nested, rootElement); 185 186 if (key != null) { 187 container.addAdapter((ComponentAdapter) nested.getComponent(key)); 188 } else if (klass != null) { 189 Class clazz = getClassLoader().loadClass(klass); 190 container.addAdapter((ComponentAdapter) nested.getComponent(clazz)); 191 } else { 192 container.addAdapter(nested.getComponent(ComponentAdapter.class)); 193 } 194 } catch (ClassNotFoundException ex) { 195 throw new ScriptedPicoContainerMarkupException(ex); 196 } 197 198 } 199 200 /** 201 * process implementation node 202 * 203 * @param container 204 * @param rootElement 205 * @throws ClassNotFoundException 206 */ 207 protected void insertImplementation(MutablePicoContainer container, Element rootElement) 208 throws ClassNotFoundException { 209 String key = rootElement.getAttribute(KEY); 210 String klass = rootElement.getAttribute(CLASS); 211 String constructor = rootElement.getAttribute(CONSTRUCTOR); 212 if (klass == null || "".equals(klass)) { 213 throw new ScriptedPicoContainerMarkupException( 214 "class specification is required for component implementation"); 215 } 216 217 Class<?> clazz = getClassLoader().loadClass(klass); 218 219 220 // ok , we processed our children. insert implementation 221 Parameter[] parameterArray = getParameters(rootElement); 222 if ("default".equals(constructor)){ 223 parameterArray = Parameter.ZERO; 224 } 225 226 NodeList children = rootElement.getChildNodes(); 227 if (children.getLength() > 0 || "default".equals(constructor)) { 228 if (key == null || "".equals(key)) { 229 // without key. clazz is our key 230 container.addComponent(clazz, clazz, parameterArray); 231 } else { 232 // with key 233 container.addComponent(key, clazz, parameterArray); 234 } 235 } else { 236 if (key == null || "".equals(key)) { 237 // without key. clazz is our key 238 container.addComponent(clazz, clazz); 239 } else { 240 // with key 241 container.addComponent(key, clazz); 242 } 243 244 } 245 } 246 247 private Parameter[] getParameters(Element rootElement) throws ClassNotFoundException { 248 List<Parameter> parameters = new ArrayList<Parameter>(); 249 250 NodeList children = rootElement.getChildNodes(); 251 Node child; 252 String name; 253 String dependencyKey; 254 String dependencyClass; 255 Object parseResult; 256 257 for (int i = 0; i < children.getLength(); i++) { 258 child = children.item(i); 259 if (child.getNodeType() == Document.ELEMENT_NODE) { 260 name = child.getNodeName(); 261 // constant parameter. it does not have any attributes. 262 if (CONSTANT.equals(name)) { 263 // create constant with xstream 264 parseResult = parseElementChild((Element) child); 265 if (parseResult == null) { 266 throw new ScriptedPicoContainerMarkupException("could not parse constant parameter"); 267 } 268 parameters.add(new ConstantParameter(parseResult)); 269 } else if (DEPENDENCY.equals(name)) { 270 // either key or class must be present. not both 271 // key has prececence 272 dependencyKey = ((Element) child).getAttribute(KEY); 273 if (dependencyKey == null || "".equals(dependencyKey)) { 274 dependencyClass = ((Element) child).getAttribute(CLASS); 275 if (dependencyClass == null || "".equals(dependencyClass)) { 276 throw new ScriptedPicoContainerMarkupException( 277 "either key or class must be present for dependency"); 278 } else { 279 parameters.add(new ComponentParameter(getClassLoader().loadClass(dependencyClass))); 280 } 281 } else { 282 parameters.add(new ComponentParameter(dependencyKey)); 283 } 284 } else if (PARAMETER_ZERO.equals(child.getNodeName())) { 285 //Check: We can't check everything here since we aren't schema validating 286 //But it will at least catch some goofs. 287 if (!parameters.isEmpty()) { 288 throw new PicoCompositionException("Cannot mix other parameters with '" + PARAMETER_ZERO +"' nodes." ); 289 } 290 291 return Parameter.ZERO; 292 } 293 } 294 } 295 return (Parameter[]) parameters.toArray(new Parameter[parameters.size()]); 296 } 297 298 /** 299 * process instance node. we get key from atributes ( if any ) and leave 300 * content to xstream. we allow only one child node inside. ( first one wins ) 301 * 302 * @param container 303 * @param rootElement 304 */ 305 protected void insertInstance(MutablePicoContainer container, Element rootElement) { 306 String key = rootElement.getAttribute(KEY); 307 Object result = parseElementChild(rootElement); 308 if (result == null) { 309 throw new ScriptedPicoContainerMarkupException("no content could be parsed in instance"); 310 } 311 if (key != null && !"".equals(key)) { 312 // insert with key 313 container.addComponent(key, result); 314 } else { 315 // or without 316 container.addComponent(result); 317 } 318 } 319 320 /** 321 * parse element child with xstream and provide object 322 * 323 * @return 324 * @param rootElement 325 */ 326 protected Object parseElementChild(Element rootElement) { 327 NodeList children = rootElement.getChildNodes(); 328 Node child; 329 for (int i = 0; i < children.getLength(); i++) { 330 child = children.item(i); 331 if (child.getNodeType() == Document.ELEMENT_NODE) { 332 return (new XStream(xsdriver)).unmarshal(new DomReader((Element) child)); 333 } 334 } 335 return null; 336 } 337 338 protected PicoContainer createContainerFromScript(PicoContainer parentContainer, Object assemblyScope) { 339 try { 340 // create ComponentInstanceFactory for the container 341 MutablePicoContainer childContainer = createMutablePicoContainer( 342 parentContainer, new ContainerOptions(rootElement)); 343 populateContainer(childContainer); 344 return childContainer; 345 } catch (PicoClassNotFoundException e) { 346 throw new ScriptedPicoContainerMarkupException("Class not found:" + e.getMessage(), e); 347 } 348 } 349 350 private MutablePicoContainer createMutablePicoContainer(PicoContainer parentContainer, ContainerOptions containerOptions) throws PicoCompositionException { 351 boolean caching = containerOptions.isCaching(); 352 boolean inherit = containerOptions.isInheritParentBehaviors(); 353 String monitorName = containerOptions.getMonitorName(); 354 String componentFactoryName = containerOptions.getComponentFactoryName(); 355 356 if (inherit) { 357 if (!(parentContainer instanceof MutablePicoContainer)) { 358 throw new PicoCompositionException("For behavior inheritance to be used, the parent picocontainer must be of type MutablePicoContainer"); 359 } 360 361 MutablePicoContainer parentPico = (MutablePicoContainer)parentContainer; 362 return parentPico.makeChildContainer(); 363 } 364 365 ScriptedBuilder builder = new ScriptedBuilder(parentContainer); 366 if (caching) builder.withCaching(); 367 return builder 368 .withClassLoader(getClassLoader()) 369 .withLifecycle() 370 .withComponentFactory(componentFactoryName) 371 .withMonitor(monitorName) 372 .build(); 373 374 } 375 376 }