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.commons.lang3.StringUtils; 23 import org.apache.velocity.Template; 24 import org.apache.velocity.exception.VelocityException; 25 import org.apache.velocity.runtime.directive.Directive; 26 import org.apache.velocity.runtime.directive.Macro; 27 import org.apache.velocity.runtime.directive.VelocimacroProxy; 28 import org.apache.velocity.runtime.parser.node.Node; 29 30 import org.slf4j.Logger; 31 32 import java.util.ArrayList; 33 import java.util.HashMap; 34 import java.util.List; 35 import java.util.Map; 36 import java.util.Vector; 37 38 /** 39 * VelocimacroFactory.java 40 * 41 * manages the set of VMs in a running Velocity engine. 42 * 43 * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> 44 * @version $Id$ 45 */ 46 public class VelocimacroFactory 47 { 48 /** 49 * runtime services for this instance 50 */ 51 private final RuntimeServices rsvc; 52 53 /** 54 * the log for this instance 55 */ 56 private Logger log = null; 57 58 /** 59 * VMManager: deal with namespace management 60 * and actually keeps all the VM definitions 61 */ 62 private VelocimacroManager vmManager = null; 63 64 /** 65 * determines if replacement of global VMs are allowed 66 * controlled by VM_PERM_ALLOW_INLINE_REPLACE_GLOBAL 67 */ 68 private boolean replaceAllowed = false; 69 70 /** 71 * controls if new VMs can be added. Set by 72 * VM_PERM_ALLOW_INLINE Note the assumption that only 73 * through inline defs can this happen. 74 * additions through autoloaded VMs is allowed 75 */ 76 private boolean addNewAllowed = true; 77 78 /** 79 * sets if template-local namespace in used 80 */ 81 private boolean templateLocal = false; 82 83 /** 84 * determines if the libraries are auto-loaded 85 * when they change 86 */ 87 private boolean autoReloadLibrary = false; 88 89 /** 90 * vector of the library names 91 */ 92 private List<String> macroLibVec = null; 93 94 /** 95 * map of the library Template objects 96 * used for reload determination 97 */ 98 private Map<String, Twonk> libModMap; 99 100 /** 101 * C'tor for the VelociMacro factory. 102 * 103 * @param rsvc Reference to a runtime services object. 104 */ VelocimacroFactory(final RuntimeServices rsvc)105 public VelocimacroFactory(final RuntimeServices rsvc) 106 { 107 this.rsvc = rsvc; 108 109 /* 110 * we always access in a synchronized(), so we 111 * can use an unsynchronized hashmap 112 */ 113 libModMap = new HashMap<>(); 114 vmManager = new VelocimacroManager(rsvc); 115 } 116 117 /** 118 * initialize the factory - setup all permissions 119 * load all global libraries. 120 */ initVelocimacro()121 public void initVelocimacro() 122 { 123 /* 124 * maybe I'm just paranoid... 125 */ 126 synchronized(this) 127 { 128 log = rsvc.getLog("macro"); 129 log.trace("initialization starting."); 130 131 /* 132 * allow replacements while we add the libraries, if exist 133 */ 134 setReplacementPermission(true); 135 136 /* 137 * add all library macros to the global namespace 138 */ 139 140 vmManager.setNamespaceUsage(false); 141 142 /* 143 * now, if there is a global or local libraries specified, use them. 144 * All we have to do is get the template. The template will be parsed; 145 * VM's are added during the parse phase 146 */ 147 148 Object libfiles = rsvc.getProperty(RuntimeConstants.VM_LIBRARY); 149 150 if (libfiles == null) 151 { 152 log.debug("\"{}\" is not set. Trying default library: {}", RuntimeConstants.VM_LIBRARY, RuntimeConstants.VM_LIBRARY_DEFAULT); 153 154 // try the default library. 155 if (rsvc.getLoaderNameForResource(RuntimeConstants.VM_LIBRARY_DEFAULT) != null) 156 { 157 libfiles = RuntimeConstants.VM_LIBRARY_DEFAULT; 158 } 159 else 160 { 161 // try the old default library 162 log.debug("Default library {} not found. Trying old default library: {}", RuntimeConstants.VM_LIBRARY_DEFAULT, DeprecatedRuntimeConstants.OLD_VM_LIBRARY_DEFAULT); 163 if (rsvc.getLoaderNameForResource(RuntimeConstants.OLD_VM_LIBRARY_DEFAULT) != null) 164 { 165 libfiles = RuntimeConstants.OLD_VM_LIBRARY_DEFAULT; 166 } 167 else 168 { 169 log.debug("Old default library {} not found.", DeprecatedRuntimeConstants.OLD_VM_LIBRARY_DEFAULT); 170 } 171 } 172 } 173 174 if(libfiles != null) 175 { 176 macroLibVec = new ArrayList<>(); 177 if (libfiles instanceof Vector) 178 { 179 macroLibVec.addAll((Vector<String>)libfiles); 180 } 181 else if (libfiles instanceof String) 182 { 183 macroLibVec.add((String)libfiles); 184 } 185 186 for (String lib : macroLibVec) 187 { 188 /* 189 * only if it's a non-empty string do we bother 190 */ 191 192 if (StringUtils.isNotEmpty(lib)) 193 { 194 /* 195 * let the VMManager know that the following is coming 196 * from libraries - need to know for auto-load 197 */ 198 199 vmManager.setRegisterFromLib(true); 200 201 log.debug("adding VMs from VM library: {}", lib); 202 203 try 204 { 205 Template template = rsvc.getTemplate(lib); 206 207 /* 208 * save the template. This depends on the assumption 209 * that the Template object won't change - currently 210 * this is how the Resource manager works 211 */ 212 213 Twonk twonk = new Twonk(); 214 twonk.template = template; 215 twonk.modificationTime = template.getLastModified(); 216 libModMap.put(lib, twonk); 217 } 218 catch (Exception e) 219 { 220 String msg = "Velocimacro: Error using VM library: " + lib; 221 log.error(msg, e); 222 throw new VelocityException(msg, e, rsvc.getLogContext().getStackTrace()); 223 } 224 225 log.trace("VM library registration complete."); 226 227 vmManager.setRegisterFromLib(false); 228 } 229 } 230 } 231 232 /* 233 * now, the permissions 234 */ 235 236 237 /* 238 * allowinline: anything after this will be an inline macro, I think 239 * there is the question if a #include is an inline, and I think so 240 * 241 * default = true 242 */ 243 setAddMacroPermission(true); 244 245 if (!rsvc.getBoolean( RuntimeConstants.VM_PERM_ALLOW_INLINE, true)) 246 { 247 setAddMacroPermission(false); 248 249 log.debug("allowInline = false: VMs can NOT be defined inline in templates"); 250 } 251 else 252 { 253 log.debug("allowInline = true: VMs can be defined inline in templates"); 254 } 255 256 /* 257 * allowInlineToReplaceGlobal: allows an inline VM , if allowed at all, 258 * to replace an existing global VM 259 * 260 * default = false 261 */ 262 setReplacementPermission(false); 263 264 if (rsvc.getBoolean( 265 RuntimeConstants.VM_PERM_ALLOW_INLINE_REPLACE_GLOBAL, false)) 266 { 267 setReplacementPermission(true); 268 269 log.debug("allowInlineToOverride = true: VMs " + 270 "defined inline may replace previous VM definitions"); 271 } 272 else 273 { 274 log.debug("allowInlineToOverride = false: VMs " + 275 "defined inline may NOT replace previous VM definitions"); 276 } 277 278 /* 279 * now turn on namespace handling as far as permissions allow in the 280 * manager, and also set it here for gating purposes 281 */ 282 vmManager.setNamespaceUsage(true); 283 284 /* 285 * template-local inline VM mode: default is off 286 */ 287 setTemplateLocalInline(rsvc.getBoolean( 288 RuntimeConstants.VM_PERM_INLINE_LOCAL, false)); 289 290 if (getTemplateLocalInline()) 291 { 292 log.debug("allowInlineLocal = true: VMs " + 293 "defined inline will be local to their defining template only."); 294 } 295 else 296 { 297 log.debug("allowInlineLocal = false: VMs " + 298 "defined inline will be global in scope if allowed."); 299 } 300 301 vmManager.setTemplateLocalInlineVM(getTemplateLocalInline()); 302 303 /* 304 * autoload VM libraries 305 */ 306 setAutoload(rsvc.getBoolean(RuntimeConstants.VM_LIBRARY_AUTORELOAD, false)); 307 308 if (getAutoload()) 309 { 310 log.debug("autoload on: VM system " + 311 "will automatically reload global library macros"); 312 } 313 else 314 { 315 log.debug("autoload off: VM system " + 316 "will not automatically reload global library macros"); 317 } 318 319 log.trace("Velocimacro: initialization complete."); 320 } 321 } 322 323 /** 324 * Adds a macro to the factory. 325 * 326 * @param name Name of the Macro to add. 327 * @param macroBody root node of the parsed macro AST 328 * @param macroArgs Array of macro arguments, containing the 329 * #macro() arguments and default values. the 0th is the name. 330 * @param definingTemplate template containing the macro definition 331 * @return true if Macro was registered successfully. 332 * @since 1.6 333 */ addVelocimacro(String name, Node macroBody, List<Macro.MacroArg> macroArgs, Template definingTemplate)334 public boolean addVelocimacro(String name, Node macroBody, 335 List<Macro.MacroArg> macroArgs, Template definingTemplate) 336 { 337 // Called by RuntimeInstance.addVelocimacro 338 339 /* 340 * maybe we should throw an exception, maybe just tell 341 * the caller like this... 342 * 343 * I hate this: maybe exceptions are in order here... 344 * They definitely would be if this was only called by directly 345 * by users, but Velocity calls this internally. 346 */ 347 if (name == null || macroBody == null || macroArgs == null || 348 definingTemplate == null) 349 { 350 String msg = "VM '"+name+"' addition rejected: "; 351 if (name == null) 352 { 353 msg += "name"; 354 } 355 else if (macroBody == null) 356 { 357 msg += "macroBody"; 358 } 359 else if (macroArgs == null) 360 { 361 msg += "macroArgs"; 362 } 363 else 364 { 365 msg += "sourceTemplate"; 366 } 367 msg += " argument was null"; 368 log.error(msg); 369 throw new NullPointerException(msg); 370 } 371 372 /* 373 * see if the current ruleset allows this addition 374 */ 375 376 if (!canAddVelocimacro(name, definingTemplate)) 377 { 378 return false; 379 } 380 381 synchronized(this) 382 { 383 vmManager.addVM(name, macroBody, macroArgs, definingTemplate, replaceAllowed); 384 } 385 log.debug("added VM {}: source={}", name, definingTemplate); 386 return true; 387 } 388 389 390 /** 391 * determines if a given macro/namespace (name, source) combo is allowed 392 * to be added 393 * 394 * @param name Name of VM to add 395 * @param definingTemplate template containing the source of the macro 396 * @return true if it is allowed to be added, false otherwise 397 */ canAddVelocimacro(String name, Template definingTemplate)398 private synchronized boolean canAddVelocimacro(String name, Template definingTemplate) 399 { 400 /* 401 * short circuit and do it if autoloader is on, and the 402 * template is one of the library templates 403 */ 404 405 if (autoReloadLibrary && (macroLibVec != null)) 406 { 407 if( macroLibVec.contains(definingTemplate.getName()) ) 408 return true; 409 } 410 411 412 /* 413 * maybe the rules should be in manager? I dunno. It's to manage 414 * the namespace issues first, are we allowed to add VMs at all? 415 * This trumps all. 416 */ 417 if (!addNewAllowed) 418 { 419 log.warn("VM addition rejected: {}: inline VelociMacros not allowed.", name); 420 return false; 421 } 422 423 /* 424 * are they local in scope? Then it is ok to add. 425 */ 426 if (!templateLocal) 427 { 428 /* 429 * otherwise, if we have it already in global namespace, and they can't replace 430 * since local templates are not allowed, the global namespace is implied. 431 * remember, we don't know anything about namespace management here, so lets 432 * note do anything fancy like trying to give it the global namespace here 433 * 434 * so if we have it, and we aren't allowed to replace, bail 435 */ 436 if (!replaceAllowed && isVelocimacro(name, definingTemplate)) 437 { 438 /* 439 * Concurrency fix: the log entry was changed to debug scope because it 440 * causes false alarms when several concurrent threads simultaneously (re)parse 441 * some macro 442 */ 443 log.debug("VM addition rejected: {}: inline not allowed to replace existing VM", name); 444 return false; 445 } 446 } 447 448 return true; 449 } 450 451 /** 452 * Tells the world if a given directive string is a Velocimacro 453 * @param vm Name of the Macro. 454 * @param template Source template from which the macro should be loaded. 455 * @return True if the given name is a macro. 456 */ isVelocimacro(String vm, Template template)457 public boolean isVelocimacro(String vm, Template template) 458 { 459 // synchronization removed 460 return(vmManager.get(vm, null, template) != null); 461 } 462 463 /** 464 * actual factory: creates a Directive that will 465 * behave correctly wrt getting the framework to 466 * dig out the correct # of args 467 * @param vmName Name of the Macro. 468 * @param renderingTemplate destination template 469 * @param sourceTemplate Source template from which the macro should be loaded. 470 * @return A directive representing the Macro. 471 */ getVelocimacro(String vmName, Template renderingTemplate, Template sourceTemplate)472 public Directive getVelocimacro(String vmName, Template renderingTemplate, Template sourceTemplate) 473 { 474 VelocimacroProxy vp = null; 475 476 vp = vmManager.get(vmName, renderingTemplate, sourceTemplate); 477 478 /* 479 * if this exists, and autoload is on, we need to check where this VM came from 480 */ 481 482 if (vp != null && autoReloadLibrary ) 483 { 484 synchronized (this) 485 { 486 /* 487 * see if this VM came from a library. Need to pass sourceTemplate in the event 488 * namespaces are set, as it could be masked by local 489 */ 490 491 String lib = vmManager.getLibraryName(vmName, sourceTemplate); 492 493 if (lib != null) 494 { 495 try 496 { 497 /* 498 * get the template from our map 499 */ 500 501 Twonk tw = libModMap.get(lib); 502 503 if (tw != null) 504 { 505 Template template = tw.template; 506 507 /* 508 * now, compare the last modified time of the resource with the last 509 * modified time of the template if the file has changed, then reload. 510 * Otherwise, we should be ok. 511 */ 512 513 long tt = tw.modificationTime; 514 long ft = template.getResourceLoader().getLastModified(template); 515 516 if (ft > tt) 517 { 518 log.debug("auto-reloading VMs from VM library: {}", lib); 519 520 /* 521 * when there are VMs in a library that invoke each other, there are 522 * calls into getVelocimacro() from the init() process of the VM 523 * directive. To stop the infinite loop we save the current time 524 * reported by the resource loader and then be honest when the 525 * reload is complete 526 */ 527 528 tw.modificationTime = ft; 529 530 template = rsvc.getTemplate(lib); 531 532 /* 533 * and now we be honest 534 */ 535 536 tw.template = template; 537 tw.modificationTime = template.getLastModified(); 538 539 /* 540 * note that we don't need to put this twonk 541 * back into the map, as we can just use the 542 * same reference and this block is synchronized 543 */ 544 } 545 } 546 } 547 catch (Exception e) 548 { 549 String msg = "Velocimacro: Error using VM library: " + lib; 550 log.error(msg, e); 551 throw new VelocityException(msg, e, rsvc.getLogContext().getStackTrace()); 552 } 553 554 vp = vmManager.get(vmName, sourceTemplate, renderingTemplate); 555 } 556 } 557 } 558 559 return vp; 560 } 561 562 /** 563 * sets permission to have VMs local in scope to their declaring template note that this is 564 * really taken care of in the VMManager class, but we need it here for gating purposes in addVM 565 * eventually, I will slide this all into the manager, maybe. 566 */ setTemplateLocalInline(boolean b)567 private void setTemplateLocalInline(boolean b) 568 { 569 templateLocal = b; 570 } 571 getTemplateLocalInline()572 private boolean getTemplateLocalInline() 573 { 574 return templateLocal; 575 } 576 577 /** 578 * sets the permission to add new macros 579 */ setAddMacroPermission(final boolean addNewAllowed)580 private boolean setAddMacroPermission(final boolean addNewAllowed) 581 { 582 boolean b = this.addNewAllowed; 583 this.addNewAllowed = addNewAllowed; 584 return b; 585 } 586 587 /** 588 * sets the permission for allowing addMacro() calls to replace existing VM's 589 */ setReplacementPermission(boolean arg)590 private boolean setReplacementPermission(boolean arg) 591 { 592 boolean b = replaceAllowed; 593 replaceAllowed = arg; 594 vmManager.setInlineReplacesGlobal(arg); 595 return b; 596 } 597 598 /** 599 * set the switch for automatic reloading of 600 * global library-based VMs 601 */ setAutoload(boolean b)602 private void setAutoload(boolean b) 603 { 604 autoReloadLibrary = b; 605 } 606 607 /** 608 * get the switch for automatic reloading of 609 * global library-based VMs 610 */ getAutoload()611 private boolean getAutoload() 612 { 613 return autoReloadLibrary; 614 } 615 616 /** 617 * small container class to hold the tuple 618 * of a template and modification time. 619 * We keep the modification time so we can 620 * 'override' it on a reload to prevent 621 * recursive reload due to inter-calling 622 * VMs in a library 623 */ 624 private static class Twonk 625 { 626 /** Template kept in this container. */ 627 public Template template; 628 629 /** modification time of the template. */ 630 public long modificationTime; 631 } 632 } 633 634 635 636 637 638 639 640