• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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