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 718442 2008-11-18 00:01:17Z 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         return(true);
466     }
467     
468     
469     /**
470      *  determines if a given macro/namespace (name, source) combo is allowed
471      *  to be added
472      *
473      *  @param name Name of VM to add
474      *  @param sourceTemplate Source template that contains the defintion of the VM
475      *  @return true if it is allowed to be added, false otherwise
476      */
477     private synchronized boolean canAddVelocimacro(String name, String sourceTemplate)
478     {
479         /*
480          *  short circuit and do it if autoloader is on, and the
481          *  template is one of the library templates
482          */
483          
484         if (autoReloadLibrary && (macroLibVec != null))
485         {
486             if( macroLibVec.contains(sourceTemplate) )
487                 return true;
488         }
489 
490 
491         /*
492          * maybe the rules should be in manager?  I dunno. It's to manage
493          * the namespace issues first, are we allowed to add VMs at all?
494          * This trumps all.
495          */
496         if (!addNewAllowed)
497         {
498             log.warn("VM addition rejected : "+name+" : inline VMs not allowed.");
499             return false;
500         }
501 
502         /*
503          *  are they local in scope?  Then it is ok to add.
504          */
505         if (!templateLocal)
506         {
507             /*
508              * otherwise, if we have it already in global namespace, and they can't replace
509              * since local templates are not allowed, the global namespace is implied.
510              *  remember, we don't know anything about namespace managment here, so lets
511              *  note do anything fancy like trying to give it the global namespace here
512              *
513              *  so if we have it, and we aren't allowed to replace, bail
514              */
515             if (!replaceAllowed && isVelocimacro(name, sourceTemplate))
516             {
517                 /*
518                  * Concurrency fix: the log entry was changed to debug scope because it
519                  * causes false alarms when several concurrent threads simultaneously (re)parse
520                  * some macro
521                  */ 
522                 if (log.isDebugEnabled())
523                     log.debug("VM addition rejected : "+name+" : inline not allowed to replace existing VM");
524                 return false;
525             }
526         }
527 
528         return true;
529     }
530 
531     /**
532      * Tells the world if a given directive string is a Velocimacro
533      * @param vm Name of the Macro.
534      * @param sourceTemplate Source template from which the macro should be loaded.
535      * @return True if the given name is a macro.
536      */
537     public boolean isVelocimacro(String vm, String sourceTemplate)
538     {
539         // synchronization removed
540         return(vmManager.get(vm, sourceTemplate) != null);
541     }
542 
543     /**
544      *  actual factory : creates a Directive that will
545      *  behave correctly wrt getting the framework to
546      *  dig out the correct # of args
547      * @param vmName Name of the Macro.
548      * @param sourceTemplate Source template from which the macro should be loaded.
549      * @return A directive representing the Macro.
550      */
551      public Directive getVelocimacro(String vmName, String sourceTemplate)
552      {
553         return(getVelocimacro(vmName, sourceTemplate, null));
554      }
555 
556      /**
557       * @since 1.6
558       */
559      public Directive getVelocimacro(String vmName, String sourceTemplate, String renderingTemplate)
560      {
561         VelocimacroProxy vp = null;
562 
563         vp = vmManager.get(vmName, sourceTemplate, renderingTemplate);
564 
565         /*
566          * if this exists, and autoload is on, we need to check where this VM came from
567          */
568 
569         if (vp != null && autoReloadLibrary )
570         {
571             synchronized (this)
572             {
573                 /*
574                  * see if this VM came from a library. Need to pass sourceTemplate in the event
575                  * namespaces are set, as it could be masked by local
576                  */
577 
578                 String lib = vmManager.getLibraryName(vmName, sourceTemplate);
579 
580                 if (lib != null)
581                 {
582                     try
583                     {
584                         /*
585                          * get the template from our map
586                          */
587 
588                         Twonk tw = (Twonk) libModMap.get(lib);
589 
590                         if (tw != null)
591                         {
592                             Template template = tw.template;
593 
594                             /*
595                              * now, compare the last modified time of the resource with the last
596                              * modified time of the template if the file has changed, then reload.
597                              * Otherwise, we should be ok.
598                              */
599 
600                             long tt = tw.modificationTime;
601                             long ft = template.getResourceLoader().getLastModified(template);
602 
603                             if (ft > tt)
604                             {
605                                 log.debug("auto-reloading VMs from VM library : " + lib);
606 
607                                 /*
608                                  * when there are VMs in a library that invoke each other, there are
609                                  * calls into getVelocimacro() from the init() process of the VM
610                                  * directive. To stop the infinite loop we save the current time
611                                  * reported by the resource loader and then be honest when the
612                                  * reload is complete
613                                  */
614 
615                                 tw.modificationTime = ft;
616 
617                                 template = rsvc.getTemplate(lib);
618 
619                                 /*
620                                  * and now we be honest
621                                  */
622 
623                                 tw.template = template;
624                                 tw.modificationTime = template.getLastModified();
625 
626                                 /*
627                                  * note that we don't need to put this twonk
628                                  * back into the map, as we can just use the
629                                  * same reference and this block is synchronized
630                                  */
631                             }
632                         }
633                     }
634                     catch (Exception e)
635                     {
636                         String msg = "Velocimacro : Error using VM library : " + lib;
637                         log.error(true, msg, e);
638                         throw new VelocityException(msg, e);
639                     }
640 
641                     vp = vmManager.get(vmName, sourceTemplate, renderingTemplate);
642                 }
643             }
644         }
645 
646         return vp;
647     }
648 
649     /**
650      * tells the vmManager to dump the specified namespace
651      * 
652      * @param namespace Namespace to dump.
653      * @return True if namespace has been dumped successfully.
654      */
655     public boolean dumpVMNamespace(String namespace)
656     {
657         return vmManager.dumpNamespace(namespace);
658     }
659 
660     /**
661      * sets permission to have VMs local in scope to their declaring template note that this is
662      * really taken care of in the VMManager class, but we need it here for gating purposes in addVM
663      * eventually, I will slide this all into the manager, maybe.
664      */
665     private void setTemplateLocalInline(boolean b)
666     {
667         templateLocal = b;
668     }
669 
670     private boolean getTemplateLocalInline()
671     {
672         return templateLocal;
673     }
674 
675     /**
676      * sets the permission to add new macros
677      */
678     private boolean setAddMacroPermission(final boolean addNewAllowed)
679     {
680         boolean b = this.addNewAllowed;
681         this.addNewAllowed = addNewAllowed;
682         return b;
683     }
684 
685     /**
686      * sets the permission for allowing addMacro() calls to replace existing VM's
687      */
688     private boolean setReplacementPermission(boolean arg)
689     {
690         boolean b = replaceAllowed;
691         replaceAllowed = arg;
692         vmManager.setInlineReplacesGlobal(arg);
693         return b;
694     }
695 
696     /**
697      *  set the switch for automatic reloading of
698      *  global library-based VMs
699      */
700     private void setAutoload(boolean b)
701     {
702         autoReloadLibrary = b;
703     }
704 
705     /**
706      *  get the switch for automatic reloading of
707      *  global library-based VMs
708      */
709     private boolean getAutoload()
710     {
711         return autoReloadLibrary;
712     }
713 
714     /**
715      * small container class to hold the tuple
716      * of a template and modification time.
717      * We keep the modification time so we can
718      * 'override' it on a reload to prevent
719      * recursive reload due to inter-calling
720      * VMs in a library
721      */
722     private static class Twonk
723     {
724         /** Template kept in this container. */
725         public Template template;
726 
727         /** modification time of the template. */
728         public long modificationTime;
729     }
730 }
731 
732 
733 
734 
735 
736 
737