    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    package org.nanocontainer.script.groovy;
    011
    012    import groovy.lang.Closure;
    013    import groovy.lang.GroovyObject;
    014    import groovy.util.BuilderSupport;
    015    import org.codehaus.groovy.runtime.InvokerHelper;
    016    import org.nanocontainer.DefaultNanoContainer;
    017    import org.nanocontainer.NanoContainer;
    018    import org.nanocontainer.script.NanoContainerMarkupException;
    019    import org.nanocontainer.script.NodeBuilderDecorationDelegate;
    020    import org.nanocontainer.script.NullNodeBuilderDecorationDelegate;
    021    import org.nanocontainer.script.groovy.buildernodes.*;
    022    import org.picocontainer.MutablePicoContainer;
    023
    024    import java.util.Collections;
    025    import java.util.HashMap;
    026    import java.util.List;
    027    import java.util.Map;
    028
    029    /**
    030     * Builds node trees of PicoContainers and Pico components using GroovyMarkup.
    031     * <p>Simple example usage in your groovy script:
    032     * <code><pre>
    033     * builder = new org.nanocontainer.script.groovy.GroovyNodeBuilder()
    034     * pico = builder.container(parent:parent) {
    035     * &nbsp;&nbsp;component(class:org.nanocontainer.testmodel.DefaultWebServerConfig)
    036     * &nbsp;&nbsp;component(class:org.nanocontainer.testmodel.WebServerImpl)
    037     * }
    038     * </pre></code>
    039     * </p>
    040     * <h4>Extending/Enhancing GroovyNodeBuilder</h4>
    041     * <p>Often-times people need there own assembly commands that are needed
    042     * for extending/enhancing the node builder tree.  The perfect example of this
    043     * is <tt>DynaopGroovyNodeBuilder</tt> which provides a new vocabulary for
    044     * the groovy node builder with terms such as 'aspect', 'pointcut', etc.</p>
    045     * <p>GroovyNodeBuilder provides two primary ways of enhancing the nodes supported
    046     * by the groovy builder: {@link org.nanocontainer.script.NodeBuilderDecorationDelegate}
    047     * and special node handlers {@link BuilderNode}.
    048     * Using NodeBuilderDecorationDelegate is often a preferred method because it is
    049     * ultimately script independent.  However, replacing an existing GroovyNodeBuilder's
    050     * behavior is currently the only way to replace the behavior of an existing
    051     * groovy node handler.
    052     * </p>
    053     *
    054     * @author James Strachan
    055     * @author Paul Hammant
    056     * @author Aslak Helles&oslash;y
    057     * @author Michael Rimov
    058     * @author Mauro Talevi
    059     * @version $Revision: 2695 $
    060     */
    061    public class GroovyNodeBuilder extends BuilderSupport {
    062
    063        private static final String CLASS = "class";
    064
    065        private static final String PARENT = "parent";
    066
    067
    068        /**
    069         * Flag indicating that the attribute validation should be performed.
    070         */
    071        public static boolean PERFORM_ATTRIBUTE_VALIDATION = true;
    072
    073
    074        /**
    075         * Flag indicating that attribute validation should be skipped.
    076         */
    077        public static boolean SKIP_ATTRIBUTE_VALIDATION = false;
    078
    079
    080        /**
    081         * Decoration delegate. The traditional method of adding functionality to
    082         * the Groovy builder.
    083         */
    084        private final NodeBuilderDecorationDelegate decorationDelegate;
    085
    086        /**
    087         * Map of node handlers.
    088         */
    089        private Map nodeBuilderHandlers = new HashMap();
    090        private Map nodeBuilders = new HashMap();
    091
    092        private final boolean performAttributeValidation;
    093
    094
    095        /**
    096         * Allows the composition of a <tt>{@link NodeBuilderDecorationDelegate}</tt> -- an
    097         * object that extends the capabilities of the <tt>GroovyNodeBuilder</tt>
    098         * with new tags, new capabilities, etc.
    099         *
    100         * @param decorationDelegate         NodeBuilderDecorationDelegate
    101         * @param performAttributeValidation should be set to PERFORM_ATTRIBUTE_VALIDATION
    102         *                                   or SKIP_ATTRIBUTE_VALIDATION
    103         * @see org.nanocontainer.aop.defaults.AopNodeBuilderDecorationDelegate
    104         */
    105        public GroovyNodeBuilder(NodeBuilderDecorationDelegate decorationDelegate, boolean performAttributeValidation) {
    106            this.decorationDelegate = decorationDelegate;
    107            this.performAttributeValidation = performAttributeValidation;
    108
    109            //Build and register node handlers.
    110            this.setNode(new ComponentNode(decorationDelegate))
    111                    .setNode(new ChildContainerNode(decorationDelegate))
    112                    .setNode(new BeanNode())
    113                    .setNode(new ClasspathNode())
    114                    .setNode(new DoCallNode())
    115                    .setNode(new NewBuilderNode())
    116                    .setNode(new ClassLoaderNode())
    117                    .setNode(new DecoratingPicoContainerNode())
    118                    .setNode(new GrantNode())
    119                    .setNode(new AppendContainerNode());
    120            NanoContainer factory = new DefaultNanoContainer();
    121            try {
    122                factory.registerComponentImplementation("wc", "org.nanocontainer.webcontainer.groovy.WebContainerBuilder");
    123                setNode((BuilderNode) factory.getPico().getComponentInstance("wc"));
    124            } catch (ClassNotFoundException cnfe) {
    125            }
    126        }
    127
    128        /**
    129         * Default constructor.
    130         */
    131        public GroovyNodeBuilder() {
    132            this(new NullNodeBuilderDecorationDelegate(), SKIP_ATTRIBUTE_VALIDATION);
    133        }
    134
    135
    136        protected void setParent(Object parent, Object child) {
    137        }
    138
    139        protected Object doInvokeMethod(String s, Object name, Object args) {
    140            //TODO use setDelegate() from Groovy JSR
    141            Object answer = super.doInvokeMethod(s, name, args);
    142            List list = InvokerHelper.asList(args);
    143            if (!list.isEmpty()) {
    144                Object o = list.get(list.size() - 1);
    145                if (o instanceof Closure) {
    146                    Closure closure = (Closure) o;
    147                    closure.setDelegate(answer);
    148                }
    149            }
    150            return answer;
    151        }
    152
    153        protected Object createNode(Object name) {
    154            return createNode(name, Collections.EMPTY_MAP);
    155        }
    156
    157        protected Object createNode(Object name, Object value) {
    158            Map attributes = new HashMap();
    159            attributes.put(CLASS, value);
    160            return createNode(name, attributes);
    161        }
    162
    163        /**
    164         * Override of create node.  Called by BuilderSupport.  It examines the
    165         * current state of the builder and the given parameters and dispatches the
    166         * code to one of the create private functions in this object.
    167         *
    168         * @param name       The name of the groovy node we're building.  Examples are
    169         *                   'container', and 'grant',
    170         * @param attributes Map  attributes of the current invocation.
    171         * @param value      A closure passed into the node.  Currently unused.
    172         * @return Object the created object.
    173         */
    174        protected Object createNode(Object name, Map attributes, Object value) {
    175            Object current = getCurrent();
    176            if (current != null && current instanceof GroovyObject) {
    177                GroovyObject groovyObject = (GroovyObject) current;
    178                return groovyObject.invokeMethod(name.toString(), attributes);
    179            } else if (current == null) {
    180                current = extractOrCreateValidRootNanoContainer(attributes);
    181            } else {
    182                if (attributes.containsKey(PARENT)) {
    183                    throw new NanoContainerMarkupException("You can't explicitly specify a parent in a child element.");
    184                }
    185            }
    186            if (name.equals("registerBuilder")) {
    187                return registerBuilder(attributes);
    188
    189            } else {
    190                return handleNode(name, attributes, current);
    191            }
    192
    193        }
    194
    195        private Object registerBuilder(Map attributes) {
    196            String builderName = (String) attributes.remove("name");
    197            Object clazz = attributes.remove("class");
    198            try {
    199                if (clazz instanceof String) {
    200                    clazz = this.getClass().getClassLoader().loadClass((String) clazz);
    201                }
    202            } catch (ClassNotFoundException e) {
    203                throw new NanoContainerMarkupException("ClassNotFoundException " + clazz);
    204            }
    205            nodeBuilders.put(builderName, clazz);
    206            return clazz;
    207        }
    208
    209        private Object handleNode(Object name, Map attributes, Object current) {
    210
    211            attributes = new HashMap(attributes);
    212
    213            BuilderNode nodeHandler = this.getNode(name.toString());
    214
    215            if (nodeHandler == null) {
    216                Class builderClass = (Class) nodeBuilders.get(name);
    217                if (builderClass != null) {
    218                    nodeHandler = this.getNode("newBuilder");
    219                    attributes.put("class",builderClass);
    220                }
    221            }
    222
    223            if (nodeHandler == null) {
    224                // we don't know how to handle it - delegate to the decorator.
    225                return getDecorationDelegate().createNode(name, attributes, current);
    226
    227            } else {
    228                //We found a handler.
    229
    230                if (performAttributeValidation) {
    231                    //Validate
    232                    nodeHandler.validateScriptedAttributes(attributes);
    233                }
    234
    235                return nodeHandler.createNewNode(current, attributes);
    236            }
    237        }
    238
    239        /**
    240         * Pulls the nanocontainer from the 'current' method or possibly creates
    241         * a new blank one if needed.
    242         *
    243         * @param attributes Map the attributes of the current node.
    244         * @return NanoContainer, never null.
    245         * @throws NanoContainerMarkupException
    246         */
    247        private NanoContainer extractOrCreateValidRootNanoContainer(final Map attributes) throws NanoContainerMarkupException {
    248            Object parentAttribute = attributes.get(PARENT);
    249            //
    250            //NanoPicoContainer implements MutablePicoCotainer AND NanoContainer
    251            //So we want to check for NanoContainer first.
    252            //
    253            if (parentAttribute instanceof NanoContainer) {
    254                // we're not in an enclosing scope - look at parent attribute instead
    255                return (NanoContainer) parentAttribute;
    256            }
    257            if (parentAttribute instanceof MutablePicoContainer) {
    258                // we're not in an enclosing scope - look at parent attribute instead
    259                return new DefaultNanoContainer((MutablePicoContainer) parentAttribute);
    260            }
    261            return null;
    262        }
    263
    264
    265        /**
    266         * Retrieve the current decoration delegate.
    267         *
    268         * @return NodeBuilderDecorationDelegate, should never be null.
    269         */
    270        public NodeBuilderDecorationDelegate getDecorationDelegate() {
    271            return this.decorationDelegate;
    272        }
    273
    274
    275        /**
    276         * Returns an appropriate node handler for a given node and
    277         *
    278         * @param tagName String
    279         * @return CustomGroovyNode the appropriate node builder for the given
    280         *         tag name, or null if no handler exists. (In which case, the Delegate
    281         *         receives the createChildContainer() call)
    282         */
    283        public synchronized BuilderNode getNode(final String tagName) {
    284            Object o = nodeBuilderHandlers.get(tagName);
    285            return (BuilderNode) o;
    286        }
    287
    288        /**
    289         * Add's a groovy node handler to the table of possible handlers. If a node
    290         * handler with the same node name already exists in the map of handlers, then
    291         * the <tt>GroovyNode</tt> replaces the existing node handler.
    292         *
    293         * @param newGroovyNode CustomGroovyNode
    294         * @return GroovyNodeBuilder to allow for method chaining.
    295         */
    296        public synchronized GroovyNodeBuilder setNode(final BuilderNode newGroovyNode) {
    297            nodeBuilderHandlers.put(newGroovyNode.getNodeName(), newGroovyNode);
    298            return this;
    299        }
    300
    301        protected Object createNode(Object name, Map attributes) {
    302            return createNode(name, attributes, null);
    303        }
    304
    305
    306    }