1 package org.apache.velocity.runtime; 2 3 /* 4 * Licensed to the Apache Software Foundation (ASF) under one 5 * or more contributor license agreements. See the NOTICE file 6 * distributed with this work for additional information 7 * regarding copyright ownership. The ASF licenses this file 8 * to you under the Apache License, Version 2.0 (the 9 * "License"); you may not use this file except in compliance 10 * with the License. You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, 15 * software distributed under the License is distributed on an 16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 * KIND, either express or implied. See the License for the 18 * specific language governing permissions and limitations 19 * under the License. 20 */ 21 22 import org.apache.velocity.Template; 23 import org.apache.velocity.exception.VelocityException; 24 import org.apache.velocity.runtime.directive.Macro; 25 import org.apache.velocity.runtime.directive.VelocimacroProxy; 26 import org.apache.velocity.runtime.parser.node.Node; 27 import org.apache.velocity.runtime.parser.node.SimpleNode; 28 29 import java.util.List; 30 import java.util.Map; 31 import java.util.concurrent.ConcurrentHashMap; 32 33 /** 34 * Manages VMs in namespaces. Currently, two namespace modes are 35 * supported: 36 * 37 * <ul> 38 * <li>flat - all allowable VMs are in the global namespace</li> 39 * <li>local - inline VMs are added to it's own template namespace</li> 40 * </ul> 41 * 42 * Thanks to <a href="mailto:JFernandez@viquity.com">Jose Alberto Fernandez</a> 43 * for some ideas incorporated here. 44 * 45 * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> 46 * @author <a href="mailto:JFernandez@viquity.com">Jose Alberto Fernandez</a> 47 * @version $Id$ 48 */ 49 public class VelocimacroManager 50 { 51 private boolean registerFromLib = false; 52 53 /** reference to global namespace hash */ 54 private final Map<String, MacroEntry> globalNamespace; 55 56 /** set of names of library templates/namespaces */ 57 private final Map<String, Template> libraries = new ConcurrentHashMap<>(17, 0.5f, 20); 58 59 private RuntimeServices rsvc = null; 60 61 /* 62 * big switch for namespaces. If true, then properties control 63 * usage. If false, no. 64 */ 65 private boolean namespacesOn = true; 66 private boolean inlineLocalMode = false; 67 private boolean inlineReplacesGlobal = false; 68 69 /** 70 * Adds the global namespace to the hash. 71 */ VelocimacroManager(RuntimeServices rsvc)72 VelocimacroManager(RuntimeServices rsvc) 73 { 74 /* 75 * add the global namespace to the namespace hash. We always have that. 76 */ 77 78 globalNamespace = new ConcurrentHashMap<>(101, 0.5f, 20); 79 this.rsvc = rsvc; 80 } 81 82 /** 83 * Adds a VM definition to the cache. 84 * 85 * Called by VelocimacroFactory.addVelociMacro (after parsing and discovery in Macro directive) 86 * 87 * @param vmName Name of the new VelociMacro. 88 * @param macroBody String representation of the macro body. 89 * @param macroArgs Array of macro arguments, containing the 90 * #macro() arguments and default values. the 0th is the name. 91 * @param definingTemplate The template from which this macro has been loaded. 92 * @param canReplaceGlobalMacro whether this macro can replace a global macro 93 * @return Whether everything went okay. 94 */ addVM(final String vmName, final Node macroBody, List<Macro.MacroArg> macroArgs, final Template definingTemplate, boolean canReplaceGlobalMacro)95 public boolean addVM(final String vmName, final Node macroBody, List<Macro.MacroArg> macroArgs, 96 final Template definingTemplate, boolean canReplaceGlobalMacro) 97 { 98 if (macroBody == null) 99 { 100 // happens only if someone uses this class without the Macro directive 101 // and provides a null value as an argument 102 throw new VelocityException("Null AST for "+vmName+" in " + definingTemplate.getName()); 103 } 104 105 MacroEntry me = new MacroEntry(vmName, macroBody, macroArgs, definingTemplate.getName(), rsvc); 106 107 me.setFromLibrary(registerFromLib); 108 109 /* 110 * the client (VMFactory) will signal to us via 111 * registerFromLib that we are in startup mode registering 112 * new VMs from libraries. Therefore, we want to 113 * addto the library map for subsequent auto reloads 114 */ 115 116 boolean isLib = true; 117 118 MacroEntry exist = globalNamespace.get(vmName); 119 120 if (registerFromLib) 121 { 122 libraries.put(definingTemplate.getName(), definingTemplate); 123 } 124 else 125 { 126 /* 127 * now, we first want to check to see if this namespace (template) 128 * is actually a library - if so, we need to use the global namespace 129 * we don't have to do this when registering, as namespaces should 130 * be shut off. If not, the default value is true, so we still go 131 * global 132 */ 133 134 isLib = libraries.containsKey(definingTemplate.getName()); 135 } 136 137 if ( !isLib && usingNamespaces() ) 138 { 139 definingTemplate.getMacros().put(vmName, me); 140 } 141 else 142 { 143 /* 144 * otherwise, add to global template. First, check if we 145 * already have it to preserve some of the autoload information 146 */ 147 148 149 if (exist != null) 150 { 151 me.setFromLibrary(exist.getFromLibrary()); 152 } 153 154 /* 155 * now add it 156 */ 157 158 globalNamespace.put(vmName, me); 159 160 } 161 return true; 162 } 163 164 /** 165 * Gets a VelocimacroProxy object by the name / source template duple. 166 * 167 * @param vmName Name of the VelocityMacro to look up. 168 * @param renderingTemplate Template we are currently rendering. 169 * @param template Source Template. 170 * @return A proxy representing the Macro. 171 */ get(final String vmName, final Template renderingTemplate, final Template template)172 public VelocimacroProxy get(final String vmName, final Template renderingTemplate, final Template template) 173 { 174 if( inlineReplacesGlobal && renderingTemplate != null ) 175 { 176 /* 177 * if VM_PERM_ALLOW_INLINE_REPLACE_GLOBAL is true (local macros can 178 * override global macros) and we know which template we are rendering at the 179 * moment, check if local namespace contains a macro we are looking for 180 * if so, return it instead of the global one 181 */ 182 183 MacroEntry me = (MacroEntry)renderingTemplate.getMacros().get(vmName); 184 if( me != null ) 185 { 186 return me.getProxy(); 187 } 188 } 189 190 if( usingNamespaces() && template != null ) 191 { 192 MacroEntry me = (MacroEntry)template.getMacros().get(vmName); 193 if( template.getMacros().size() > 0 && me != null ) 194 { 195 return me.getProxy(); 196 } 197 } 198 199 MacroEntry me = globalNamespace.get(vmName); 200 201 if (me != null) 202 { 203 return me.getProxy(); 204 } 205 206 return null; 207 } 208 209 /** 210 * public switch to let external user of manager to control namespace 211 * usage indep of properties. That way, for example, at startup the 212 * library files are loaded into global namespace 213 * 214 * @param namespaceOn True if namespaces should be used. 215 */ setNamespaceUsage(final boolean namespaceOn)216 public void setNamespaceUsage(final boolean namespaceOn) 217 { 218 this.namespacesOn = namespaceOn; 219 } 220 221 /** 222 * Should macros registered from Libraries be marked special? 223 * @param registerFromLib True if macros from Libs should be marked. 224 */ setRegisterFromLib(final boolean registerFromLib)225 public void setRegisterFromLib(final boolean registerFromLib) 226 { 227 this.registerFromLib = registerFromLib; 228 } 229 230 /** 231 * Should macros from the same template be inlined? 232 * 233 * @param inlineLocalMode True if macros should be inlined on the same template. 234 */ setTemplateLocalInlineVM(final boolean inlineLocalMode)235 public void setTemplateLocalInlineVM(final boolean inlineLocalMode) 236 { 237 this.inlineLocalMode = inlineLocalMode; 238 } 239 240 /** 241 * determines if currently using namespaces. 242 * 243 * @return true if using namespaces, false if not 244 */ usingNamespaces()245 private boolean usingNamespaces() 246 { 247 /* 248 * if the big switch turns of namespaces, then ignore the rules 249 */ 250 251 if (!namespacesOn) 252 { 253 return false; 254 } 255 256 /* 257 * currently, we only support the local template namespace idea 258 */ 259 260 return inlineLocalMode; 261 262 } 263 264 /** 265 * Return the library name for a given macro. 266 * @param vmName Name of the Macro to look up. 267 * @param template Template 268 * @return The name of the library which registered this macro in a namespace. 269 */ getLibraryName(final String vmName, Template template)270 public String getLibraryName(final String vmName, Template template) 271 { 272 if (usingNamespaces()) 273 { 274 /* 275 * if we have this macro defined in this namespace, then 276 * it is masking the global, library-based one, so 277 * just return null 278 */ 279 MacroEntry me = (MacroEntry)template.getMacros().get(vmName); 280 if( me != null ) 281 return null; 282 } 283 284 MacroEntry me = globalNamespace.get(vmName); 285 286 if (me != null) 287 { 288 return me.getSourceTemplate(); 289 } 290 291 return null; 292 } 293 294 /** 295 * @param is 296 * @since 1.6 297 */ setInlineReplacesGlobal(boolean is)298 public void setInlineReplacesGlobal(boolean is) 299 { 300 inlineReplacesGlobal = is; 301 } 302 303 304 /** 305 * wrapper class for holding VM information 306 */ 307 private static class MacroEntry 308 { 309 private final String sourceTemplate; 310 private boolean fromLibrary = false; 311 private VelocimacroProxy vp; 312 MacroEntry(final String vmName, final Node macro, List<Macro.MacroArg> macroArgs, final String sourceTemplate, RuntimeServices rsvc)313 private MacroEntry(final String vmName, final Node macro, 314 List<Macro.MacroArg> macroArgs, final String sourceTemplate, 315 RuntimeServices rsvc) 316 { 317 this.sourceTemplate = sourceTemplate; 318 319 vp = new VelocimacroProxy(); 320 vp.init(rsvc); 321 vp.setName(vmName); 322 vp.setMacroArgs(macroArgs); 323 vp.setNodeTree((SimpleNode)macro); 324 vp.setLocation(macro.getLine(), macro.getColumn(), macro.getTemplate()); 325 } 326 327 /** 328 * Has the macro been registered from a library. 329 * @param fromLibrary True if the macro was registered from a Library. 330 */ setFromLibrary(final boolean fromLibrary)331 public void setFromLibrary(final boolean fromLibrary) 332 { 333 this.fromLibrary = fromLibrary; 334 } 335 336 /** 337 * Returns true if the macro was registered from a library. 338 * @return True if the macro was registered from a library. 339 */ getFromLibrary()340 public boolean getFromLibrary() 341 { 342 return fromLibrary; 343 } 344 getSourceTemplate()345 public String getSourceTemplate() 346 { 347 return sourceTemplate; 348 } 349 getProxy()350 VelocimacroProxy getProxy() 351 { 352 return vp; 353 } 354 } 355 } 356