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