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