View Javadoc

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