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