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    }