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 }