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 }