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