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