    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    }