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    }