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 * component(class:org.nanocontainer.testmodel.DefaultWebServerConfig)
036 * 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ø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 }