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 }