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