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