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     * &nbsp;&nbsp;component(class:org.nanocontainer.testmodel.DefaultWebServerConfig)
058     * &nbsp;&nbsp;component(class:org.nanocontainer.testmodel.WebServerImpl)
059     * }
060     * </pre></code>
061     * </p>
062     * @author James Strachan
063     * @author Paul Hammant
064     * @author Aslak Helles&oslash;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    }