package org.apache.velocity.runtime; /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import org.apache.commons.lang3.StringUtils; import org.apache.velocity.Template; import org.apache.velocity.exception.VelocityException; import org.apache.velocity.runtime.directive.Directive; import org.apache.velocity.runtime.directive.Macro; import org.apache.velocity.runtime.directive.VelocimacroProxy; import org.apache.velocity.runtime.parser.node.Node; import org.slf4j.Logger; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Vector; /** * VelocimacroFactory.java * * manages the set of VMs in a running Velocity engine. * * @author Geir Magnusson Jr. * @version $Id$ */ public class VelocimacroFactory { /** * runtime services for this instance */ private final RuntimeServices rsvc; /** * the log for this instance */ private Logger log = null; /** * VMManager: deal with namespace management * and actually keeps all the VM definitions */ private VelocimacroManager vmManager = null; /** * determines if replacement of global VMs are allowed * controlled by VM_PERM_ALLOW_INLINE_REPLACE_GLOBAL */ private boolean replaceAllowed = false; /** * controls if new VMs can be added. Set by * VM_PERM_ALLOW_INLINE Note the assumption that only * through inline defs can this happen. * additions through autoloaded VMs is allowed */ private boolean addNewAllowed = true; /** * sets if template-local namespace in used */ private boolean templateLocal = false; /** * determines if the libraries are auto-loaded * when they change */ private boolean autoReloadLibrary = false; /** * vector of the library names */ private List macroLibVec = null; /** * map of the library Template objects * used for reload determination */ private Map libModMap; /** * C'tor for the VelociMacro factory. * * @param rsvc Reference to a runtime services object. */ public VelocimacroFactory(final RuntimeServices rsvc) { this.rsvc = rsvc; /* * we always access in a synchronized(), so we * can use an unsynchronized hashmap */ libModMap = new HashMap<>(); vmManager = new VelocimacroManager(rsvc); } /** * initialize the factory - setup all permissions * load all global libraries. */ public void initVelocimacro() { /* * maybe I'm just paranoid... */ synchronized(this) { log = rsvc.getLog("macro"); log.trace("initialization starting."); /* * allow replacements while we add the libraries, if exist */ setReplacementPermission(true); /* * add all library macros to the global namespace */ vmManager.setNamespaceUsage(false); /* * now, if there is a global or local libraries specified, use them. * All we have to do is get the template. The template will be parsed; * VM's are added during the parse phase */ Object libfiles = rsvc.getProperty(RuntimeConstants.VM_LIBRARY); if (libfiles == null) { log.debug("\"{}\" is not set. Trying default library: {}", RuntimeConstants.VM_LIBRARY, RuntimeConstants.VM_LIBRARY_DEFAULT); // try the default library. if (rsvc.getLoaderNameForResource(RuntimeConstants.VM_LIBRARY_DEFAULT) != null) { libfiles = RuntimeConstants.VM_LIBRARY_DEFAULT; } else { // try the old default library log.debug("Default library {} not found. Trying old default library: {}", RuntimeConstants.VM_LIBRARY_DEFAULT, DeprecatedRuntimeConstants.OLD_VM_LIBRARY_DEFAULT); if (rsvc.getLoaderNameForResource(RuntimeConstants.OLD_VM_LIBRARY_DEFAULT) != null) { libfiles = RuntimeConstants.OLD_VM_LIBRARY_DEFAULT; } else { log.debug("Old default library {} not found.", DeprecatedRuntimeConstants.OLD_VM_LIBRARY_DEFAULT); } } } if(libfiles != null) { macroLibVec = new ArrayList<>(); if (libfiles instanceof Vector) { macroLibVec.addAll((Vector)libfiles); } else if (libfiles instanceof String) { macroLibVec.add((String)libfiles); } for (String lib : macroLibVec) { /* * only if it's a non-empty string do we bother */ if (StringUtils.isNotEmpty(lib)) { /* * let the VMManager know that the following is coming * from libraries - need to know for auto-load */ vmManager.setRegisterFromLib(true); log.debug("adding VMs from VM library: {}", lib); try { Template template = rsvc.getTemplate(lib); /* * save the template. This depends on the assumption * that the Template object won't change - currently * this is how the Resource manager works */ Twonk twonk = new Twonk(); twonk.template = template; twonk.modificationTime = template.getLastModified(); libModMap.put(lib, twonk); } catch (Exception e) { String msg = "Velocimacro: Error using VM library: " + lib; log.error(msg, e); throw new VelocityException(msg, e, rsvc.getLogContext().getStackTrace()); } log.trace("VM library registration complete."); vmManager.setRegisterFromLib(false); } } } /* * now, the permissions */ /* * allowinline: anything after this will be an inline macro, I think * there is the question if a #include is an inline, and I think so * * default = true */ setAddMacroPermission(true); if (!rsvc.getBoolean( RuntimeConstants.VM_PERM_ALLOW_INLINE, true)) { setAddMacroPermission(false); log.debug("allowInline = false: VMs can NOT be defined inline in templates"); } else { log.debug("allowInline = true: VMs can be defined inline in templates"); } /* * allowInlineToReplaceGlobal: allows an inline VM , if allowed at all, * to replace an existing global VM * * default = false */ setReplacementPermission(false); if (rsvc.getBoolean( RuntimeConstants.VM_PERM_ALLOW_INLINE_REPLACE_GLOBAL, false)) { setReplacementPermission(true); log.debug("allowInlineToOverride = true: VMs " + "defined inline may replace previous VM definitions"); } else { log.debug("allowInlineToOverride = false: VMs " + "defined inline may NOT replace previous VM definitions"); } /* * now turn on namespace handling as far as permissions allow in the * manager, and also set it here for gating purposes */ vmManager.setNamespaceUsage(true); /* * template-local inline VM mode: default is off */ setTemplateLocalInline(rsvc.getBoolean( RuntimeConstants.VM_PERM_INLINE_LOCAL, false)); if (getTemplateLocalInline()) { log.debug("allowInlineLocal = true: VMs " + "defined inline will be local to their defining template only."); } else { log.debug("allowInlineLocal = false: VMs " + "defined inline will be global in scope if allowed."); } vmManager.setTemplateLocalInlineVM(getTemplateLocalInline()); /* * autoload VM libraries */ setAutoload(rsvc.getBoolean(RuntimeConstants.VM_LIBRARY_AUTORELOAD, false)); if (getAutoload()) { log.debug("autoload on: VM system " + "will automatically reload global library macros"); } else { log.debug("autoload off: VM system " + "will not automatically reload global library macros"); } log.trace("Velocimacro: initialization complete."); } } /** * Adds a macro to the factory. * * @param name Name of the Macro to add. * @param macroBody root node of the parsed macro AST * @param macroArgs Array of macro arguments, containing the * #macro() arguments and default values. the 0th is the name. * @param definingTemplate template containing the macro definition * @return true if Macro was registered successfully. * @since 1.6 */ public boolean addVelocimacro(String name, Node macroBody, List macroArgs, Template definingTemplate) { // Called by RuntimeInstance.addVelocimacro /* * maybe we should throw an exception, maybe just tell * the caller like this... * * I hate this: maybe exceptions are in order here... * They definitely would be if this was only called by directly * by users, but Velocity calls this internally. */ if (name == null || macroBody == null || macroArgs == null || definingTemplate == null) { String msg = "VM '"+name+"' addition rejected: "; if (name == null) { msg += "name"; } else if (macroBody == null) { msg += "macroBody"; } else if (macroArgs == null) { msg += "macroArgs"; } else { msg += "sourceTemplate"; } msg += " argument was null"; log.error(msg); throw new NullPointerException(msg); } /* * see if the current ruleset allows this addition */ if (!canAddVelocimacro(name, definingTemplate)) { return false; } synchronized(this) { vmManager.addVM(name, macroBody, macroArgs, definingTemplate, replaceAllowed); } log.debug("added VM {}: source={}", name, definingTemplate); return true; } /** * determines if a given macro/namespace (name, source) combo is allowed * to be added * * @param name Name of VM to add * @param definingTemplate template containing the source of the macro * @return true if it is allowed to be added, false otherwise */ private synchronized boolean canAddVelocimacro(String name, Template definingTemplate) { /* * short circuit and do it if autoloader is on, and the * template is one of the library templates */ if (autoReloadLibrary && (macroLibVec != null)) { if( macroLibVec.contains(definingTemplate.getName()) ) return true; } /* * maybe the rules should be in manager? I dunno. It's to manage * the namespace issues first, are we allowed to add VMs at all? * This trumps all. */ if (!addNewAllowed) { log.warn("VM addition rejected: {}: inline VelociMacros not allowed.", name); return false; } /* * are they local in scope? Then it is ok to add. */ if (!templateLocal) { /* * otherwise, if we have it already in global namespace, and they can't replace * since local templates are not allowed, the global namespace is implied. * remember, we don't know anything about namespace management here, so lets * note do anything fancy like trying to give it the global namespace here * * so if we have it, and we aren't allowed to replace, bail */ if (!replaceAllowed && isVelocimacro(name, definingTemplate)) { /* * Concurrency fix: the log entry was changed to debug scope because it * causes false alarms when several concurrent threads simultaneously (re)parse * some macro */ log.debug("VM addition rejected: {}: inline not allowed to replace existing VM", name); return false; } } return true; } /** * Tells the world if a given directive string is a Velocimacro * @param vm Name of the Macro. * @param template Source template from which the macro should be loaded. * @return True if the given name is a macro. */ public boolean isVelocimacro(String vm, Template template) { // synchronization removed return(vmManager.get(vm, null, template) != null); } /** * actual factory: creates a Directive that will * behave correctly wrt getting the framework to * dig out the correct # of args * @param vmName Name of the Macro. * @param renderingTemplate destination template * @param sourceTemplate Source template from which the macro should be loaded. * @return A directive representing the Macro. */ public Directive getVelocimacro(String vmName, Template renderingTemplate, Template sourceTemplate) { VelocimacroProxy vp = null; vp = vmManager.get(vmName, renderingTemplate, sourceTemplate); /* * if this exists, and autoload is on, we need to check where this VM came from */ if (vp != null && autoReloadLibrary ) { synchronized (this) { /* * see if this VM came from a library. Need to pass sourceTemplate in the event * namespaces are set, as it could be masked by local */ String lib = vmManager.getLibraryName(vmName, sourceTemplate); if (lib != null) { try { /* * get the template from our map */ Twonk tw = libModMap.get(lib); if (tw != null) { Template template = tw.template; /* * now, compare the last modified time of the resource with the last * modified time of the template if the file has changed, then reload. * Otherwise, we should be ok. */ long tt = tw.modificationTime; long ft = template.getResourceLoader().getLastModified(template); if (ft > tt) { log.debug("auto-reloading VMs from VM library: {}", lib); /* * when there are VMs in a library that invoke each other, there are * calls into getVelocimacro() from the init() process of the VM * directive. To stop the infinite loop we save the current time * reported by the resource loader and then be honest when the * reload is complete */ tw.modificationTime = ft; template = rsvc.getTemplate(lib); /* * and now we be honest */ tw.template = template; tw.modificationTime = template.getLastModified(); /* * note that we don't need to put this twonk * back into the map, as we can just use the * same reference and this block is synchronized */ } } } catch (Exception e) { String msg = "Velocimacro: Error using VM library: " + lib; log.error(msg, e); throw new VelocityException(msg, e, rsvc.getLogContext().getStackTrace()); } vp = vmManager.get(vmName, sourceTemplate, renderingTemplate); } } } return vp; } /** * sets permission to have VMs local in scope to their declaring template note that this is * really taken care of in the VMManager class, but we need it here for gating purposes in addVM * eventually, I will slide this all into the manager, maybe. */ private void setTemplateLocalInline(boolean b) { templateLocal = b; } private boolean getTemplateLocalInline() { return templateLocal; } /** * sets the permission to add new macros */ private boolean setAddMacroPermission(final boolean addNewAllowed) { boolean b = this.addNewAllowed; this.addNewAllowed = addNewAllowed; return b; } /** * sets the permission for allowing addMacro() calls to replace existing VM's */ private boolean setReplacementPermission(boolean arg) { boolean b = replaceAllowed; replaceAllowed = arg; vmManager.setInlineReplacesGlobal(arg); return b; } /** * set the switch for automatic reloading of * global library-based VMs */ private void setAutoload(boolean b) { autoReloadLibrary = b; } /** * get the switch for automatic reloading of * global library-based VMs */ private boolean getAutoload() { return autoReloadLibrary; } /** * small container class to hold the tuple * of a template and modification time. * We keep the modification time so we can * 'override' it on a reload to prevent * recursive reload due to inter-calling * VMs in a library */ private static class Twonk { /** Template kept in this container. */ public Template template; /** modification time of the template. */ public long modificationTime; } }