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 }