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 * Original code by James Strachan *
009 *****************************************************************************/
010
011 package org.nanocontainer.script.groovy;
012
013 import groovy.lang.Closure;
014 import groovy.lang.GroovyObject;
015 import groovy.util.BuilderSupport;
016
017 import java.io.File;
018 import java.net.MalformedURLException;
019 import java.net.URL;
020 import java.security.AccessController;
021 import java.security.Permission;
022 import java.security.PrivilegedAction;
023 import java.util.Collections;
024 import java.util.HashMap;
025 import java.util.Iterator;
026 import java.util.List;
027 import java.util.Map;
028
029 import org.codehaus.groovy.runtime.InvokerHelper;
030 import org.nanocontainer.ClassNameKey;
031 import org.nanocontainer.ClassPathElement;
032 import org.nanocontainer.DefaultNanoContainer;
033 import org.nanocontainer.NanoContainer;
034 import org.nanocontainer.script.NanoContainerMarkupException;
035 import org.nanocontainer.script.NodeBuilderDecorationDelegate;
036 import org.nanocontainer.script.NullNodeBuilderDecorationDelegate;
037 import org.picocontainer.ComponentMonitor;
038 import org.picocontainer.MutablePicoContainer;
039 import org.picocontainer.Parameter;
040 import org.picocontainer.PicoContainer;
041 import org.picocontainer.ComponentAdapter;
042 import org.picocontainer.defaults.ComponentAdapterFactory;
043 import org.picocontainer.defaults.ComponentMonitorStrategy;
044 import org.picocontainer.defaults.ConstantParameter;
045 import org.picocontainer.defaults.DefaultComponentAdapterFactory;
046 import org.picocontainer.defaults.DefaultPicoContainer;
047 import org.picocontainer.defaults.DelegatingComponentMonitor;
048
049 /**
050 * <p>
051 * Builds node trees of PicoContainers and Pico components using GroovyMarkup.
052 * </p>
053 * <p>Simple example usage in your groovy script:
054 * <code><pre>
055 * builder = new org.nanocontainer.script.groovy.OldGroovyNodeBuilder()
056 * pico = builder.container(parent:parent) {
057 * component(class:org.nanocontainer.testmodel.DefaultWebServerConfig)
058 * component(class:org.nanocontainer.testmodel.WebServerImpl)
059 * }
060 * </pre></code>
061 * </p>
062 * @author James Strachan
063 * @author Paul Hammant
064 * @author Aslak Hellesøy
065 * @author Michael Rimov
066 * @author Mauro Talevi
067 * @version $Revision: 3144 $
068 * @deprecated Since version 1.0-RC-3, use GroovyNodeBuilder instead.
069 */
070 public class OldGroovyNodeBuilder extends BuilderSupport {
071
072 private static final String NEW_BUILDER = "newBuilder";
073 private static final String CONTAINER = "container";
074 private static final String COMPONENT = "component";
075 private static final String INSTANCE = "instance";
076 private static final String KEY = "key";
077 private static final String PARAMETERS = "parameters";
078 private static final String BEAN = "bean";
079 private static final String BEAN_CLASS = "beanClass";
080 private static final String CLASS = "class";
081 private static final String CLASS_NAME_KEY = "classNameKey";
082 private static final String CLASSPATH_ELEMENT = "classPathElement";
083 private static final String CLASSLOADER = "classLoader";
084 private static final String PATH = "path";
085 private static final String GRANT = "grant";
086 private static final String HTTP = "http://";
087 private static final String PARENT = "parent";
088 private static final String COMPONENT_ADAPTER_FACTORY = "componentAdapterFactory";
089 private static final String COMPONENT_MONITOR = "componentMonitor";
090 private static final String EMPTY = "";
091 private static final String DO_CALL = "doCall";
092
093 private final NodeBuilderDecorationDelegate decorationDelegate;
094
095 public OldGroovyNodeBuilder(NodeBuilderDecorationDelegate decorationDelegate) {
096 this.decorationDelegate = decorationDelegate;
097 }
098
099 public OldGroovyNodeBuilder() {
100 this(new NullNodeBuilderDecorationDelegate());
101 }
102
103 protected void setParent(Object parent, Object child) {
104 }
105
106 protected Object doInvokeMethod(String s, Object name, Object args) {
107 //TODO use setClosureDelegate() from Groovy JSR
108 Object answer = super.doInvokeMethod(s, name, args);
109 List list = InvokerHelper.asList(args);
110 if (!list.isEmpty()) {
111 Object o = list.get(list.size() - 1);
112 if (o instanceof Closure) {
113 Closure closure = (Closure) o;
114 closure.setDelegate(answer);
115 }
116 }
117 return answer;
118 }
119
120 protected void setClosureDelegate(Closure closure, Object o) {
121 super.setClosureDelegate(closure, o);
122 }
123
124 protected Object createNode(Object name) {
125 return createNode(name, Collections.EMPTY_MAP);
126 }
127
128 protected Object createNode(Object name, Object value) {
129 Map attributes = new HashMap();
130 attributes.put(CLASS, value);
131 return createNode(name, attributes);
132 }
133
134 /**
135 * Override of create node. Called by BuilderSupport. It examines the
136 * current state of the builder and the given parameters and dispatches the
137 * code to one of the create private functions in this object.
138 * @param name The name of the groovy node we're building. Examples are
139 * 'container', and 'grant',
140 * @param attributes Map attributes of the current invocation.
141 * @return Object the created object.
142 */
143 protected Object createNode(Object name, Map attributes, Object value) {
144 Object current = getCurrent();
145 if (current != null && current instanceof GroovyObject) {
146 return createChildBuilder(current, name, attributes);
147 } else if (current == null || current instanceof NanoContainer) {
148 NanoContainer parent = (NanoContainer) current;
149 Object parentAttribute = attributes.get(PARENT);
150 if (parent != null && parentAttribute != null) {
151 throw new NanoContainerMarkupException("You can't explicitly specify a parent in a child element.");
152 }
153 if (parent == null && (parentAttribute instanceof MutablePicoContainer)) {
154 // we're not in an enclosing scope - look at parent attribute instead
155 parent = new DefaultNanoContainer((MutablePicoContainer) parentAttribute);
156 }
157 if (parent == null && (parentAttribute instanceof NanoContainer)) {
158 // we're not in an enclosing scope - look at parent attribute instead
159 parent = (NanoContainer) parentAttribute;
160 }
161 if (name.equals(CONTAINER)) {
162 return createChildContainer(attributes, parent);
163 } else {
164 try {
165 return createChildOfContainerNode(parent, name, attributes, current);
166 } catch (ClassNotFoundException e) {
167 throw new NanoContainerMarkupException("ClassNotFoundException: " + e.getMessage(), e);
168 }
169 }
170 } else if (current instanceof ClassPathElement) {
171 if (name.equals(GRANT)) {
172 return createGrantPermission(attributes, (ClassPathElement) current);
173 }
174 return EMPTY;
175 } else if (current instanceof ComponentAdapter && name.equals("instance")) {
176
177 // TODO - Michael.
178 // Michael, we could implement key() implementation() and possibly (with many limits on use) instance() here.
179 // Not what you outline in NANO-138 as is though.
180
181 return decorationDelegate.createNode(name, attributes, current);
182
183 } else {
184 // we don't know how to handle it - delegate to the decorator.
185 return decorationDelegate.createNode(name, attributes, current);
186 }
187 }
188
189 private Object createChildBuilder(Object current, Object name, Map attributes) {
190 GroovyObject groovyObject = (GroovyObject) current;
191 return groovyObject.invokeMethod(name.toString(), attributes);
192 }
193
194 private Object createGrantPermission(Map attributes, ClassPathElement cpe) {
195 Permission perm = (Permission) attributes.remove(CLASS);
196 return cpe.grantPermission(perm);
197
198 }
199
200 private Object createChildOfContainerNode(NanoContainer parentContainer, Object name, Map attributes, Object current) throws ClassNotFoundException {
201 if (name.equals(COMPONENT)) {
202 decorationDelegate.rememberComponentKey(attributes);
203 return createComponentNode(attributes, parentContainer, name);
204 } else if (name.equals(BEAN)) {
205 return createBeanNode(attributes, parentContainer.getPico());
206 } else if (name.equals(CLASSPATH_ELEMENT)) {
207 return createClassPathElementNode(attributes, parentContainer);
208 } else if (name.equals(DO_CALL)) {
209 // TODO does this node need to be handled?
210 return null;
211 } else if (name.equals(NEW_BUILDER)) {
212 return createNewBuilderNode(attributes, parentContainer);
213 } else if (name.equals(CLASSLOADER)) {
214 return createComponentClassLoader(parentContainer);
215 } else {
216 // we don't know how to handle it - delegate to the decorator.
217 return decorationDelegate.createNode(name, attributes, current);
218 }
219
220 }
221
222 private Object createNewBuilderNode(Map attributes, NanoContainer parentContainer) {
223 String builderClass = (String) attributes.remove(CLASS);
224 NanoContainer factory = new DefaultNanoContainer();
225 MutablePicoContainer parentPico = parentContainer.getPico();
226 factory.getPico().registerComponentInstance(MutablePicoContainer.class, parentPico);
227 try {
228 factory.registerComponentImplementation(GroovyObject.class, builderClass);
229 } catch (ClassNotFoundException e) {
230 throw new NanoContainerMarkupException("ClassNotFoundException " + builderClass);
231 }
232 Object componentInstance = factory.getPico().getComponentInstance(GroovyObject.class);
233 return componentInstance;
234 }
235
236 private ClassPathElement createClassPathElementNode(Map attributes, NanoContainer nanoContainer) {
237
238 final String path = (String) attributes.remove(PATH);
239 URL pathURL = null;
240 try {
241 if (path.toLowerCase().startsWith(HTTP)) {
242 pathURL = new URL(path);
243 } else {
244 Object rVal = AccessController.doPrivileged(new PrivilegedAction() {
245 public Object run() {
246 try {
247 File file = new File(path);
248 if (!file.exists()) {
249 return new NanoContainerMarkupException("classpath '" + path + "' does not exist ");
250 }
251 return file.toURL();
252 } catch (MalformedURLException e) {
253 return e;
254 }
255
256 }
257 });
258 if (rVal instanceof MalformedURLException) {
259 throw (MalformedURLException) rVal;
260 }
261 if (rVal instanceof NanoContainerMarkupException) {
262 throw (NanoContainerMarkupException) rVal;
263 }
264 pathURL = (URL) rVal;
265 }
266 } catch (MalformedURLException e) {
267 throw new NanoContainerMarkupException("classpath '" + path + "' malformed ", e);
268 }
269 return nanoContainer.addClassLoaderURL(pathURL);
270 }
271
272 private Object createBeanNode(Map attributes, MutablePicoContainer pico) {
273 Object bean = createBean(attributes);
274 pico.registerComponentInstance(bean);
275 return bean;
276 }
277
278 private Object createComponentNode(Map attributes, NanoContainer nano, Object name) throws ClassNotFoundException {
279 Object key = attributes.remove(KEY);
280 Object cnkey = attributes.remove(CLASS_NAME_KEY);
281 Object classValue = attributes.remove(CLASS);
282 Object instance = attributes.remove(INSTANCE);
283 Object retval = null;
284 List parameters = (List) attributes.remove(PARAMETERS);
285
286 MutablePicoContainer pico = nano.getPico();
287
288 if (cnkey != null) {
289 key = new ClassNameKey((String)cnkey);
290 }
291
292 Parameter[] parameterArray = getParameters(parameters);
293 if (classValue instanceof Class) {
294 Class clazz = (Class) classValue;
295 key = key == null ? clazz : key;
296 retval = pico.registerComponentImplementation(key, clazz, parameterArray);
297 } else if (classValue instanceof String) {
298 String className = (String) classValue;
299 key = key == null ? className : key;
300 retval = nano.registerComponentImplementation(key, className, parameterArray);
301 } else if (instance != null) {
302 key = key == null ? instance.getClass() : key;
303 retval = pico.registerComponentInstance(key, instance);
304 } else {
305 throw new NanoContainerMarkupException("Must specify a class attribute for a component as a class name (string) or Class. Attributes:" + attributes);
306 }
307
308 return retval;
309 }
310
311 protected Object createNode(Object name, Map attributes) {
312 return createNode(name, attributes, null);
313 }
314
315 /**
316 * Creates a new container. There may or may not be a parent to this container.
317 * Supported attributes are:
318 * <ul>
319 * <li><tt>componentAdapterFactory</tt>: The ComponentAdapterFactory used for new container</li>
320 * <li><tt>componentMonitor</tt>: The ComponentMonitor used for new container</li>
321 * </ul>
322 * @param attributes Map Attributes defined by the builder in the script.
323 * @param parent The parent container
324 * @return The NanoContainer
325 */
326 protected NanoContainer createChildContainer(Map attributes, NanoContainer parent) {
327
328 ClassLoader parentClassLoader = null;
329 MutablePicoContainer childContainer = null;
330 if (parent != null) {
331 parentClassLoader = parent.getComponentClassLoader();
332 if ( isAttribute(attributes, COMPONENT_ADAPTER_FACTORY) ) {
333 ComponentAdapterFactory componentAdapterFactory = createComponentAdapterFactory(attributes);
334 childContainer = new DefaultPicoContainer(
335 decorationDelegate.decorate(componentAdapterFactory, attributes), parent.getPico());
336 if ( isAttribute(attributes, COMPONENT_MONITOR) ) {
337 changeComponentMonitor(childContainer, createComponentMonitor(attributes));
338 }
339 parent.getPico().addChildContainer(childContainer);
340 } else if ( isAttribute(attributes, COMPONENT_MONITOR) ) {
341 ComponentAdapterFactory componentAdapterFactory = new DefaultComponentAdapterFactory(
342 createComponentMonitor(attributes));
343 childContainer = new DefaultPicoContainer(
344 decorationDelegate.decorate(componentAdapterFactory, attributes), parent.getPico());
345 } else {
346 childContainer = parent.getPico().makeChildContainer();
347 }
348 } else {
349 parentClassLoader = (ClassLoader) AccessController.doPrivileged(new PrivilegedAction() {
350 public Object run() {
351 return PicoContainer.class.getClassLoader();
352 }
353 });
354 ComponentAdapterFactory componentAdapterFactory = createComponentAdapterFactory(attributes);
355 childContainer = new DefaultPicoContainer(
356 decorationDelegate.decorate(componentAdapterFactory, attributes));
357 if ( isAttribute(attributes, COMPONENT_MONITOR) ) {
358 changeComponentMonitor(childContainer, createComponentMonitor(attributes));
359 }
360 }
361
362 MutablePicoContainer decoratedPico = decorationDelegate.decorate(childContainer);
363 if ( isAttribute(attributes, CLASS) ) {
364 Class clazz = (Class) attributes.get(CLASS);
365 return createNanoContainer(clazz, decoratedPico, parentClassLoader);
366 } else {
367 return new DefaultNanoContainer(parentClassLoader, decoratedPico);
368 }
369 }
370
371 private void changeComponentMonitor(MutablePicoContainer childContainer, ComponentMonitor monitor) {
372 if ( childContainer instanceof ComponentMonitorStrategy ){
373 ((ComponentMonitorStrategy)childContainer).changeMonitor(monitor);
374 }
375 }
376
377 private NanoContainer createNanoContainer(Class clazz, MutablePicoContainer decoratedPico, ClassLoader parentClassLoader) {
378 DefaultPicoContainer instantiatingContainer = new DefaultPicoContainer();
379 instantiatingContainer.registerComponentInstance(ClassLoader.class, parentClassLoader);
380 instantiatingContainer.registerComponentInstance(MutablePicoContainer.class, decoratedPico);
381 instantiatingContainer.registerComponentImplementation(NanoContainer.class, clazz);
382 Object componentInstance = instantiatingContainer.getComponentInstance(NanoContainer.class);
383 return (NanoContainer) componentInstance;
384 }
385
386 private boolean isAttribute(Map attributes, String key) {
387 return attributes.containsKey(key) && attributes.get(key) != null;
388 }
389
390 private ComponentAdapterFactory createComponentAdapterFactory(Map attributes) {
391 final ComponentAdapterFactory factory = (ComponentAdapterFactory) attributes.remove(COMPONENT_ADAPTER_FACTORY);
392 if ( factory == null ){
393 return new DefaultComponentAdapterFactory();
394 }
395 return factory;
396 }
397
398 private ComponentMonitor createComponentMonitor(Map attributes) {
399 final ComponentMonitor monitor = (ComponentMonitor) attributes.remove(COMPONENT_MONITOR);
400 if ( monitor == null ){
401 return new DelegatingComponentMonitor();
402 }
403 return monitor;
404 }
405
406 protected NanoContainer createComponentClassLoader(NanoContainer parent) {
407 return new DefaultNanoContainer(parent.getComponentClassLoader(), parent.getPico());
408 }
409
410
411 protected Object createBean(Map attributes) {
412 Class type = (Class) attributes.remove(BEAN_CLASS);
413 if (type == null) {
414 throw new NanoContainerMarkupException("Bean must have a beanClass attribute");
415 }
416 try {
417 Object bean = type.newInstance();
418 // now let's set the properties on the bean
419 for (Iterator iter = attributes.entrySet().iterator(); iter.hasNext();) {
420 Map.Entry entry = (Map.Entry) iter.next();
421 String name = entry.getKey().toString();
422 Object value = entry.getValue();
423 InvokerHelper.setProperty(bean, name, value);
424 }
425 return bean;
426 } catch (IllegalAccessException e) {
427 throw new NanoContainerMarkupException("Failed to create bean of type '" + type + "'. Reason: " + e, e);
428 } catch (InstantiationException e) {
429 throw new NanoContainerMarkupException("Failed to create bean of type " + type + "'. Reason: " + e, e);
430 }
431 }
432
433 private Parameter[] getParameters(List paramsList) {
434 if (paramsList == null) {
435 return null;
436 }
437 int n = paramsList.size();
438 Parameter[] parameters = new Parameter[n];
439 for (int i = 0; i < n; ++i) {
440 parameters[i] = toParameter(paramsList.get(i));
441 }
442 return parameters;
443 }
444
445 private Parameter toParameter(Object obj) {
446 return obj instanceof Parameter ? (Parameter) obj : new ConstantParameter(obj);
447 }
448
449 }