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.Collections;
23  import java.util.HashSet;
24  import java.util.Map;
25  import java.util.Set;
26  
27  import org.apache.velocity.exception.VelocityException;
28  import org.apache.velocity.runtime.directive.VelocimacroProxy;
29  import org.apache.velocity.runtime.parser.node.Node;
30  import org.apache.velocity.runtime.parser.node.SimpleNode;
31  import org.apache.velocity.util.MapFactory;
32  
33  /**
34   * Manages VMs in namespaces.  Currently, two namespace modes are
35   * supported:
36   *
37   * <ul>
38   * <li>flat - all allowable VMs are in the global namespace</li>
39   * <li>local - inline VMs are added to it's own template namespace</li>
40   * </ul>
41   *
42   * Thanks to <a href="mailto:JFernandez@viquity.com">Jose Alberto Fernandez</a>
43   * for some ideas incorporated here.
44   *
45   * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
46   * @author <a href="mailto:JFernandez@viquity.com">Jose Alberto Fernandez</a>
47   * @version $Id: VelocimacroManager.java 992115 2010-09-02 21:00:10Z nbubna $
48   */
49  public class VelocimacroManager
50  {
51      private static String GLOBAL_NAMESPACE = "";
52  
53      private boolean registerFromLib = false;
54  
55      /** Hash of namespace hashes. */
56      private final Map namespaceHash = MapFactory.create(17, 0.5f, 20, false);
57  
58      /** reference to global namespace hash */
59      private final Map globalNamespace;
60  
61      /** set of names of library tempates/namespaces */
62      private final Set libraries = Collections.synchronizedSet(new HashSet());
63      
64      private RuntimeServices rsvc = null;
65  
66      /*
67       * big switch for namespaces.  If true, then properties control
68       * usage. If false, no.
69       */
70      private boolean namespacesOn = true;
71      private boolean inlineLocalMode = false;
72      private boolean inlineReplacesGlobal = false;
73  
74      /**
75       * Adds the global namespace to the hash.
76       */
77      VelocimacroManager(RuntimeServices rsvc)
78      {
79          /*
80           *  add the global namespace to the namespace hash. We always have that.
81           */
82  
83          globalNamespace = addNamespace(GLOBAL_NAMESPACE);
84          this.rsvc = rsvc;
85      }
86  
87      /**
88       * Adds a VM definition to the cache.
89       * 
90       * Called by VelocimacroFactory.addVelociMacro (after parsing and discovery in Macro directive)
91       * 
92       * @param vmName Name of the new VelociMacro.
93       * @param macroBody String representation of the macro body.
94       * @param argArray Array of macro parameters, first parameter is the macro name.
95       * @param namespace The namespace/template from which this macro has been loaded.
96       * @return Whether everything went okay.
97       */
98      public boolean addVM(final String vmName, final Node macroBody, final String argArray[],
99                           final String namespace, boolean canReplaceGlobalMacro)
100     {
101         if (macroBody == null)
102         {
103             // happens only if someone uses this class without the Macro directive
104             // and provides a null value as an argument
105             throw new VelocityException("Null AST for "+vmName+" in "+namespace);
106         }
107 
108         MacroEntry me = new MacroEntry(vmName, macroBody, argArray, namespace, rsvc);
109 
110         me.setFromLibrary(registerFromLib);
111         
112         /*
113          *  the client (VMFactory) will signal to us via
114          *  registerFromLib that we are in startup mode registering
115          *  new VMs from libraries.  Therefore, we want to
116          *  addto the library map for subsequent auto reloads
117          */
118 
119         boolean isLib = true;
120 
121         MacroEntry exist = (MacroEntry) globalNamespace.get(vmName);
122         
123         if (registerFromLib)
124         {
125            libraries.add(namespace);
126         }
127         else
128         {
129             /*
130              *  now, we first want to check to see if this namespace (template)
131              *  is actually a library - if so, we need to use the global namespace
132              *  we don't have to do this when registering, as namespaces should
133              *  be shut off. If not, the default value is true, so we still go
134              *  global
135              */
136 
137             isLib = libraries.contains(namespace);
138         }
139 
140         if ( !isLib && usingNamespaces(namespace) )
141         {
142             /*
143              *  first, do we have a namespace hash already for this namespace?
144              *  if not, add it to the namespaces, and add the VM
145              */
146 
147             Map local = getNamespace(namespace, true);
148             local.put(vmName, me);
149             
150             return true;
151         }
152         else
153         {
154             /*
155              *  otherwise, add to global template.  First, check if we
156              *  already have it to preserve some of the autoload information
157              */
158 
159 
160             if (exist != null)
161             {
162                 me.setFromLibrary(exist.getFromLibrary());
163             }
164 
165             /*
166              *  now add it
167              */
168 
169             globalNamespace.put(vmName, me);
170 
171             return true;
172         }
173     }
174     
175     /**
176      * Gets a VelocimacroProxy object by the name / source template duple.
177      * 
178      * @param vmName Name of the VelocityMacro to look up.
179      * @param namespace Namespace in which to look up the macro.
180      * @return A proxy representing the Macro.
181      */
182      public VelocimacroProxy get(final String vmName, final String namespace)
183      {
184         return(get(vmName, namespace, null));
185      }
186 
187      /**
188       * Gets a VelocimacroProxy object by the name / source template duple.
189       * 
190       * @param vmName Name of the VelocityMacro to look up.
191       * @param namespace Namespace in which to look up the macro.
192       * @param renderingTemplate Name of the template we are currently rendering.
193       * @return A proxy representing the Macro.
194       * @since 1.6
195       */
196      public VelocimacroProxy get(final String vmName, final String namespace, final String renderingTemplate)
197      {
198         if( inlineReplacesGlobal && renderingTemplate != null )
199         {
200             /*
201              * if VM_PERM_ALLOW_INLINE_REPLACE_GLOBAL is true (local macros can
202              * override global macros) and we know which template we are rendering at the
203              * moment, check if local namespace contains a macro we are looking for
204              * if so, return it instead of the global one
205              */
206             Map local = getNamespace(renderingTemplate, false);
207             if (local != null)
208             {
209                 MacroEntry me = (MacroEntry) local.get(vmName);
210 
211                 if (me != null)
212                 {
213                     return me.getProxy(namespace);
214                 }
215             }
216         }
217         
218         if (usingNamespaces(namespace))
219         {
220             Map local = getNamespace(namespace, false);
221 
222             /*
223              *  if we have macros defined for this template
224              */
225 
226             if (local != null)
227             {
228                 MacroEntry me = (MacroEntry) local.get(vmName);
229                 
230                 if (me != null)
231                 {
232                     return me.getProxy(namespace);
233                 }
234             }
235         }
236 
237         /*
238          * if we didn't return from there, we need to simply see
239          * if it's in the global namespace
240          */
241 
242         MacroEntry me = (MacroEntry) globalNamespace.get(vmName);
243 
244         if (me != null)
245         {
246             return me.getProxy(namespace);
247         }
248 
249         return null;
250     }
251 
252     /**
253      * Removes the VMs and the namespace from the manager.
254      * Used when a template is reloaded to avoid
255      * losing memory.
256      *
257      * @param namespace namespace to dump
258      * @return boolean representing success
259      */
260     public boolean dumpNamespace(final String namespace)
261     {
262         if (usingNamespaces(namespace))
263         {
264             synchronized(this)
265             {
266                 Map h = (Map) namespaceHash.remove(namespace);
267 
268                 if (h == null)
269                 {
270                     return false;
271                 }
272 
273                 h.clear();
274 
275                 return true;
276             }
277         }
278         return false;
279     }
280 
281     /**
282      *  public switch to let external user of manager to control namespace
283      *  usage indep of properties.  That way, for example, at startup the
284      *  library files are loaded into global namespace
285      *
286      * @param namespaceOn True if namespaces should be used.
287      */
288     public void setNamespaceUsage(final boolean namespaceOn)
289     {
290         this.namespacesOn = namespaceOn;
291     }
292 
293     /**
294      * Should macros registered from Libraries be marked special?
295      * @param registerFromLib True if macros from Libs should be marked.
296      */
297     public void setRegisterFromLib(final boolean registerFromLib)
298     {
299         this.registerFromLib = registerFromLib;
300     }
301 
302     /**
303      * Should macros from the same template be inlined?
304      *
305      * @param inlineLocalMode True if macros should be inlined on the same template.
306      */
307     public void setTemplateLocalInlineVM(final boolean inlineLocalMode)
308     {
309         this.inlineLocalMode = inlineLocalMode;
310     }
311 
312     /**
313      *  returns the hash for the specified namespace, and if it doesn't exist
314      *  will create a new one and add it to the namespaces
315      *
316      *  @param namespace  name of the namespace :)
317      *  @param addIfNew  flag to add a new namespace if it doesn't exist
318      *  @return namespace Map of VMs or null if doesn't exist
319      */
320     private Map getNamespace(final String namespace, final boolean addIfNew)
321     {
322         Map h = (Map) namespaceHash.get(namespace);
323 
324         if (h == null && addIfNew)
325         {
326             h = addNamespace(namespace);
327         }
328 
329         return h;
330     }
331 
332     /**
333      *   adds a namespace to the namespaces
334      *
335      *  @param namespace name of namespace to add
336      *  @return Hash added to namespaces, ready for use
337      */
338     private Map addNamespace(final String namespace)
339     {
340         Map h = MapFactory.create(17, 0.5f, 20, false);
341         Object oh;
342 
343         if ((oh = namespaceHash.put(namespace, h)) != null)
344         {
345           /*
346            * There was already an entry on the table, restore it!
347            * This condition should never occur, given the code
348            * and the fact that this method is private.
349            * But just in case, this way of testing for it is much
350            * more efficient than testing before hand using get().
351            */
352           namespaceHash.put(namespace, oh);
353           /*
354            * Should't we be returning the old entry (oh)?
355            * The previous code was just returning null in this case.
356            */
357           return null;
358         }
359 
360         return h;
361     }
362 
363     /**
364      *  determines if currently using namespaces.
365      *
366      *  @param namespace currently ignored
367      *  @return true if using namespaces, false if not
368      */
369     private boolean usingNamespaces(final String namespace)
370     {
371         /*
372          *  if the big switch turns of namespaces, then ignore the rules
373          */
374 
375         if (!namespacesOn)
376         {
377             return false;
378         }
379 
380         /*
381          *  currently, we only support the local template namespace idea
382          */
383 
384         if (inlineLocalMode)
385         {
386             return true;
387         }
388 
389         return false;
390     }
391 
392     /**
393      * Return the library name for a given macro.
394      * @param vmName Name of the Macro to look up.
395      * @param namespace Namespace to look the macro up.
396      * @return The name of the library which registered this macro in a namespace.
397      */
398     public String getLibraryName(final String vmName, final String namespace)
399     {
400         if (usingNamespaces(namespace))
401         {
402             Map local = getNamespace(namespace, false);
403 
404             /*
405              *  if we have this macro defined in this namespace, then
406              *  it is masking the global, library-based one, so
407              *  just return null
408              */
409 
410             if ( local != null)
411             {
412                 MacroEntry me = (MacroEntry) local.get(vmName);
413 
414                 if (me != null)
415                 {
416                     return null;
417                 }
418             }
419         }
420 
421         /*
422          * if we didn't return from there, we need to simply see
423          * if it's in the global namespace
424          */
425 
426         MacroEntry me = (MacroEntry) globalNamespace.get(vmName);
427 
428         if (me != null)
429         {
430             return me.getSourceTemplate();
431         }
432 
433         return null;
434     }
435     
436     /**
437      * @since 1.6
438      */
439     public void setInlineReplacesGlobal(boolean is)
440     {
441         inlineReplacesGlobal = is;
442     }
443 
444 
445     /**
446      *  wrapper class for holding VM information
447      */
448     private static class MacroEntry
449     {
450         private final String vmName;
451         private final String[] argArray;
452         private final String sourceTemplate;
453         private SimpleNode nodeTree = null;
454         private boolean fromLibrary = false;
455         private VelocimacroProxy vp;
456 
457         private MacroEntry(final String vmName, final Node macro,
458                    final String argArray[], final String sourceTemplate,
459                    RuntimeServices rsvc)
460         {
461             this.vmName = vmName;
462             this.argArray = argArray;
463             this.nodeTree = (SimpleNode)macro;
464             this.sourceTemplate = sourceTemplate;
465 
466             vp = new VelocimacroProxy();
467             vp.setName(this.vmName);
468             vp.setArgArray(this.argArray);
469             vp.setNodeTree(this.nodeTree);
470             vp.setLocation(macro.getLine(), macro.getColumn(), macro.getTemplateName());
471             vp.init(rsvc);
472         }
473         
474         /**
475          * Has the macro been registered from a library.
476          * @param fromLibrary True if the macro was registered from a Library.
477          */
478         public void setFromLibrary(final boolean fromLibrary)
479         {
480             this.fromLibrary = fromLibrary;
481         }
482 
483         /**
484          * Returns true if the macro was registered from a library.
485          * @return True if the macro was registered from a library.
486          */
487         public boolean getFromLibrary()
488         {
489             return fromLibrary;
490         }
491 
492         /**
493          * Returns the node tree for this macro.
494          * @return The node tree for this macro.
495          */
496         public SimpleNode getNodeTree()
497         {
498             return nodeTree;
499         }
500 
501         /**
502          * Returns the source template name for this macro.
503          * @return The source template name for this macro.
504          */
505         public String getSourceTemplate()
506         {
507             return sourceTemplate;
508         }
509 
510         VelocimacroProxy getProxy(final String namespace)
511         {
512             /*
513              * FIXME: namespace data is omitted, this probably 
514              * breaks some error reporting?
515              */ 
516             return vp;
517         }
518     }
519 }
520 
521