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 * * 009 *****************************************************************************/ 010 011 package org.nanocontainer.script.xml; 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.nanocontainer.DefaultNanoContainer; 023 import org.nanocontainer.NanoContainer; 024 import org.nanocontainer.integrationkit.ContainerPopulator; 025 import org.nanocontainer.script.NanoContainerMarkupException; 026 import org.nanocontainer.script.ScriptedContainerBuilder; 027 import org.picocontainer.ComponentAdapter; 028 import org.picocontainer.MutablePicoContainer; 029 import org.picocontainer.Parameter; 030 import org.picocontainer.PicoContainer; 031 import org.picocontainer.defaults.ComponentAdapterFactory; 032 import org.picocontainer.defaults.ComponentParameter; 033 import org.picocontainer.defaults.ConstantParameter; 034 import org.picocontainer.defaults.DefaultComponentAdapterFactory; 035 import org.picocontainer.defaults.DefaultPicoContainer; 036 import org.w3c.dom.Document; 037 import org.w3c.dom.Element; 038 import org.w3c.dom.Node; 039 import org.w3c.dom.NodeList; 040 import org.xml.sax.InputSource; 041 import org.xml.sax.SAXException; 042 043 import com.thoughtworks.xstream.XStream; 044 import com.thoughtworks.xstream.io.HierarchicalStreamDriver; 045 import com.thoughtworks.xstream.io.xml.DomDriver; 046 import com.thoughtworks.xstream.io.xml.DomReader; 047 048 /** 049 * This class builds up a hierarchy of PicoContainers from an XML configuration file. 050 * 051 * @author Konstantin Pribluda 052 * @version $Revision: 2164 $ 053 */ 054 public class XStreamContainerBuilder extends ScriptedContainerBuilder implements ContainerPopulator { 055 private final Element rootElement; 056 057 private final static String IMPLEMENTATION = "implementation"; 058 private final static String INSTANCE = "instance"; 059 private final static String ADAPTER = "adapter"; 060 private final static String CLASS = "class"; 061 private final static String KEY = "key"; 062 private final static String CONSTANT = "constant"; 063 private final static String DEPENDENCY = "dependency"; 064 private final static String CONSTRUCTOR = "constructor"; 065 066 private final HierarchicalStreamDriver xsdriver; 067 068 /** 069 * construct with just reader, use context classloader 070 */ 071 public XStreamContainerBuilder(Reader script) { 072 this(script,Thread.currentThread().getContextClassLoader()); 073 } 074 075 /** 076 * construct with given script and specified classloader 077 */ 078 public XStreamContainerBuilder(Reader script, ClassLoader classLoader) { 079 this(script, classLoader, new DomDriver()); 080 } 081 082 public XStreamContainerBuilder(Reader script, ClassLoader classLoader, HierarchicalStreamDriver driver) { 083 super(script, classLoader); 084 xsdriver = driver; 085 InputSource inputSource = new InputSource(script); 086 try { 087 rootElement = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputSource).getDocumentElement(); 088 } catch (SAXException e) { 089 throw new NanoContainerMarkupException(e); 090 } catch (IOException e) { 091 throw new NanoContainerMarkupException(e); 092 } catch (ParserConfigurationException e) { 093 throw new NanoContainerMarkupException(e); 094 } 095 } 096 097 public XStreamContainerBuilder(URL script, ClassLoader classLoader, HierarchicalStreamDriver driver) { 098 super(script, classLoader); 099 xsdriver = driver; 100 try { 101 InputSource inputSource = new InputSource(getScriptReader()); 102 rootElement = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputSource).getDocumentElement(); 103 } catch (SAXException e) { 104 throw new NanoContainerMarkupException(e); 105 } catch (IOException e) { 106 throw new NanoContainerMarkupException(e); 107 } catch (ParserConfigurationException e) { 108 throw new NanoContainerMarkupException(e); 109 } 110 } 111 112 public void populateContainer(MutablePicoContainer container) { 113 populateContainer(container, rootElement); 114 } 115 116 /** 117 * just a convenience method, so we can work recursively with subcontainers 118 * for whatever puproses we see cool. 119 */ 120 private void populateContainer(MutablePicoContainer container, Element rootElement) { 121 NodeList children = rootElement.getChildNodes(); 122 Node child; 123 String name; 124 short type; 125 for (int i = 0; i < children.getLength(); i++) { 126 child = children.item(i); 127 type = child.getNodeType(); 128 129 if (type == Document.ELEMENT_NODE) { 130 name = child.getNodeName(); 131 if (IMPLEMENTATION.equals(name)) { 132 try { 133 insertImplementation(container, (Element) child); 134 } catch (ClassNotFoundException e) { 135 throw new NanoContainerMarkupException(e); 136 } 137 } else if (INSTANCE.equals(name)) { 138 insertInstance(container, (Element) child); 139 } else if (ADAPTER.equals(name)) { 140 insertAdapter(container, (Element) child); 141 } else { 142 throw new NanoContainerMarkupException("Unsupported element:" + name); 143 } 144 } 145 } 146 147 } 148 149 /** 150 * process adapter node 151 */ 152 protected void insertAdapter(MutablePicoContainer container, Element rootElement) { 153 String key = rootElement.getAttribute(KEY); 154 String klass = rootElement.getAttribute(CLASS); 155 try { 156 DefaultPicoContainer nested = new DefaultPicoContainer(); 157 populateContainer(nested, rootElement); 158 159 if (key != null) { 160 container.registerComponent((ComponentAdapter) nested.getComponentInstance(key)); 161 } else if (klass != null) { 162 Class clazz = getClassLoader().loadClass(klass); 163 container.registerComponent((ComponentAdapter) nested.getComponentInstanceOfType(clazz)); 164 } else { 165 container.registerComponent((ComponentAdapter) nested.getComponentInstanceOfType(ComponentAdapter.class)); 166 } 167 } catch (ClassNotFoundException ex) { 168 throw new NanoContainerMarkupException(ex); 169 } 170 171 } 172 173 /** 174 * process implementation node 175 */ 176 protected void insertImplementation(MutablePicoContainer container, Element rootElement) throws ClassNotFoundException { 177 String key = rootElement.getAttribute(KEY); 178 String klass = rootElement.getAttribute(CLASS); 179 String constructor = rootElement.getAttribute(CONSTRUCTOR); 180 if (klass == null || "".equals(klass)) { 181 throw new NanoContainerMarkupException("class specification is required for component implementation"); 182 } 183 184 Class clazz = getClassLoader().loadClass(klass); 185 186 List parameters = new ArrayList(); 187 188 NodeList children = rootElement.getChildNodes(); 189 Node child; 190 String name; 191 String dependencyKey; 192 String dependencyClass; 193 Object parseResult; 194 195 for (int i = 0; i < children.getLength(); i++) { 196 child = children.item(i); 197 if (child.getNodeType() == Document.ELEMENT_NODE) { 198 name = child.getNodeName(); 199 // constant parameter. it does not have any attributes. 200 if (CONSTANT.equals(name)) { 201 // create constant with xstream 202 parseResult = parseElementChild((Element) child); 203 if (parseResult == null) { 204 throw new NanoContainerMarkupException("could not parse constant parameter"); 205 } 206 parameters.add(new ConstantParameter(parseResult)); 207 } else if (DEPENDENCY.equals(name)) { 208 // either key or class must be present. not both 209 // key has prececence 210 dependencyKey = ((Element) child).getAttribute(KEY); 211 if (dependencyKey == null || "".equals(dependencyKey)) { 212 dependencyClass = ((Element) child).getAttribute(CLASS); 213 if (dependencyClass == null || "".equals(dependencyClass)) { 214 throw new NanoContainerMarkupException("either key or class must be present for dependency"); 215 } else { 216 parameters.add(new ComponentParameter(getClassLoader().loadClass(dependencyClass))); 217 } 218 } else { 219 parameters.add(new ComponentParameter(dependencyKey)); 220 } 221 } 222 } 223 } 224 225 // ok , we processed our children. insert implementation 226 Parameter[] parameterArray = (Parameter[]) parameters.toArray(new Parameter[parameters.size()]); 227 if (parameters.size() > 0 || "default".equals(constructor)) { 228 if (key == null || "".equals(key)) { 229 // without key. clazz is our key 230 container.registerComponentImplementation(clazz, clazz, parameterArray); 231 } else { 232 // with key 233 container.registerComponentImplementation(key, clazz, parameterArray); 234 } 235 } else { 236 if (key == null || "".equals(key)) { 237 // without key. clazz is our key 238 container.registerComponentImplementation(clazz, clazz); 239 } else { 240 // with key 241 container.registerComponentImplementation(key, clazz); 242 } 243 244 } 245 } 246 247 /** 248 * process instance node. we get key from atributes ( if any ) and leave content 249 * to xstream. we allow only one child node inside. ( first one wins ) 250 */ 251 protected void insertInstance(MutablePicoContainer container, Element rootElement) { 252 String key = rootElement.getAttribute(KEY); 253 Object result = parseElementChild(rootElement); 254 if (result == null) { 255 throw new NanoContainerMarkupException("no content could be parsed in instance"); 256 } 257 if (key != null && !"".equals(key)) { 258 // insert with key 259 container.registerComponentInstance(key, result); 260 } else { 261 // or without 262 container.registerComponentInstance(result); 263 } 264 } 265 266 /** 267 * parse element child with xstream and provide object 268 */ 269 protected Object parseElementChild(Element rootElement) { 270 NodeList children = rootElement.getChildNodes(); 271 Node child; 272 for (int i = 0; i < children.getLength(); i++) { 273 child = children.item(i); 274 if (child.getNodeType() == Document.ELEMENT_NODE) { 275 return (new XStream(xsdriver)).unmarshal(new DomReader((Element) child)); 276 } 277 } 278 return null; 279 } 280 281 protected PicoContainer createContainerFromScript(PicoContainer parentContainer, Object assemblyScope) { 282 try { 283 String cafName = rootElement.getAttribute("componentadapterfactory"); 284 if ("".equals(cafName) || cafName == null) { 285 cafName = DefaultComponentAdapterFactory.class.getName(); 286 } 287 Class cafClass = getClassLoader().loadClass(cafName); 288 ComponentAdapterFactory componentAdapterFactory = (ComponentAdapterFactory) cafClass.newInstance(); 289 MutablePicoContainer picoContainer = new DefaultPicoContainer(componentAdapterFactory); 290 NanoContainer nano = new DefaultNanoContainer(getClassLoader(), picoContainer); 291 populateContainer(nano.getPico()); 292 return nano.getPico(); 293 } catch (ClassNotFoundException e) { 294 throw new NanoContainerMarkupException(e); 295 } catch (InstantiationException e) { 296 throw new NanoContainerMarkupException(e); 297 } catch (IllegalAccessException e) { 298 throw new NanoContainerMarkupException(e); 299 } 300 } 301 }