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 * 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 }