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 }