    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        /**
    130         * Default constructor.
    131         */
    132        public GroovyNodeBuilder() {
    133            this(new NullNodeBuilderDecorationDelegate(), SKIP_ATTRIBUTE_VALIDATION);
    134        }
    135
    136
    137        protected void setParent(Object parent, Object child) {
    138        }
    139
    140        protected Object doInvokeMethod(String s, Object name, Object args) {
    141            //TODO use setDelegate() from Groovy JSR
    142            Object answer = super.doInvokeMethod(s, name, args);
    143            List list = InvokerHelper.asList(args);
    144            if (!list.isEmpty()) {
    145                Object o = list.get(list.size() - 1);
    146                if (o instanceof Closure) {
    147                    Closure closure = (Closure) o;
    148                    closure.setDelegate(answer);
    149                }
    150            }
    151            return answer;
    152        }
    153
    154        protected Object createNode(Object name) {
    155            return createNode(name, Collections.EMPTY_MAP);
    156        }
    157
    158        protected Object createNode(Object name, Object value) {
    159            Map attributes = new HashMap();
    160            attributes.put(CLASS, value);
    161            return createNode(name, attributes);
    162        }
    163
    164        /**
    165         * Override of create node.  Called by BuilderSupport.  It examines the
    166         * current state of the builder and the given parameters and dispatches the
    167         * code to one of the create private functions in this object.
    168         *
    169         * @param name       The name of the groovy node we're building.  Examples are
    170         *                   'container', and 'grant',
    171         * @param attributes Map  attributes of the current invocation.
    172         * @param value      A closure passed into the node.  Currently unused.
    173         * @return Object the created object.
    174         */
    175        protected Object createNode(Object name, Map attributes, Object value) {
    176            Object current = getCurrent();
    177            if (current != null && current instanceof GroovyObject) {
    178                GroovyObject groovyObject = (GroovyObject) current;
    179                return groovyObject.invokeMethod(name.toString(), attributes);
    180            } else if (current == null) {
    181                current = extractOrCreateValidRootNanoContainer(attributes);
    182            } else {
    183                if (attributes.containsKey(PARENT)) {
    184                    throw new NanoContainerMarkupException("You can't explicitly specify a parent in a child element.");
    185                }
    186            }
    187            if (name.equals("registerBuilder")) {
    188                return registerBuilder(attributes);
    189
    190            } else {
    191                return handleNode(name, attributes, current);
    192            }
    193
    194        }
    195
    196        private Object registerBuilder(Map attributes) {
    197            String builderName = (String) attributes.remove("name");
    198            Object clazz = attributes.remove("class");
    199            try {
    200                if (clazz instanceof String) {
    201                    clazz = this.getClass().getClassLoader().loadClass((String) clazz);
    202                }
    203            } catch (ClassNotFoundException e) {
    204                throw new NanoContainerMarkupException("ClassNotFoundException " + clazz);
    205            }
    206            nodeBuilders.put(builderName, clazz);
    207            return clazz;
    208        }
    209
    210        private Object handleNode(Object name, Map attributes, Object current) {
    211
    212            attributes = new HashMap(attributes);
    213
    214            BuilderNode nodeHandler = this.getNode(name.toString());
    215
    216            if (nodeHandler == null) {
    217                Class builderClass = (Class) nodeBuilders.get(name);
    218                if (builderClass != null) {
    219                    nodeHandler = this.getNode("newBuilder");
    220                    attributes.put("class",builderClass);
    221                }
    222            }
    223
    224            if (nodeHandler == null) {
    225                // we don't know how to handle it - delegate to the decorator.
    226                return getDecorationDelegate().createNode(name, attributes, current);
    227
    228            } else {
    229                //We found a handler.
    230
    231                if (performAttributeValidation) {
    232                    //Validate
    233                    nodeHandler.validateScriptedAttributes(attributes);
    234                }
    235
    236                return nodeHandler.createNewNode(current, attributes);
    237            }
    238        }
    239
    240        /**
    241         * Pulls the nanocontainer from the 'current' method or possibly creates
    242         * a new blank one if needed.
    243         *
    244         * @param attributes Map the attributes of the current node.
    245         * @return NanoContainer, never null.
    246         * @throws NanoContainerMarkupException
    247         */
    248        private NanoContainer extractOrCreateValidRootNanoContainer(final Map attributes) throws NanoContainerMarkupException {
    249            Object parentAttribute = attributes.get(PARENT);
    250            //
    251            //NanoPicoContainer implements MutablePicoCotainer AND NanoContainer
    252            //So we want to check for NanoContainer first.
    253            //
    254            if (parentAttribute instanceof NanoContainer) {
    255                // we're not in an enclosing scope - look at parent attribute instead
    256                return (NanoContainer) parentAttribute;
    257            }
    258            if (parentAttribute instanceof MutablePicoContainer) {
    259                // we're not in an enclosing scope - look at parent attribute instead
    260                return new DefaultNanoContainer((MutablePicoContainer) parentAttribute);
    261            }
    262            return null;
    263        }
    264
    265
    266        /**
    267         * Retrieve the current decoration delegate.
    268         *
    269         * @return NodeBuilderDecorationDelegate, should never be null.
    270         */
    271        public NodeBuilderDecorationDelegate getDecorationDelegate() {
    272            return this.decorationDelegate;
    273        }
    274
    275
    276        /**
    277         * Returns an appropriate node handler for a given node and
    278         *
    279         * @param tagName String
    280         * @return CustomGroovyNode the appropriate node builder for the given
    281         *         tag name, or null if no handler exists. (In which case, the Delegate
    282         *         receives the createChildContainer() call)
    283         */
    284        public synchronized BuilderNode getNode(final String tagName) {
    285            Object o = nodeBuilderHandlers.get(tagName);
    286            return (BuilderNode) o;
    287        }
    288
    289        /**
    290         * Add's a groovy node handler to the table of possible handlers. If a node
    291         * handler with the same node name already exists in the map of handlers, then
    292         * the <tt>GroovyNode</tt> replaces the existing node handler.
    293         *
    294         * @param newGroovyNode CustomGroovyNode
    295         * @return GroovyNodeBuilder to allow for method chaining.
    296         */
    297        public synchronized GroovyNodeBuilder setNode(final BuilderNode newGroovyNode) {
    298            nodeBuilderHandlers.put(newGroovyNode.getNodeName(), newGroovyNode);
    299            return this;
    300        }
    301
    302        protected Object createNode(Object name, Map attributes) {
    303            return createNode(name, attributes, null);
    304        }
    305
    306
    307    }