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