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