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 }