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.HashMap;
23 import java.util.Map;
24 import java.util.Vector;
25
26 import org.apache.commons.lang.StringUtils;
27 import org.apache.velocity.Template;
28 import org.apache.velocity.runtime.directive.Directive;
29 import org.apache.velocity.runtime.directive.Macro;
30 import org.apache.velocity.runtime.directive.VelocimacroProxy;
31 import org.apache.velocity.runtime.log.LogDisplayWrapper;
32
33 /**
34 * VelocimacroFactory.java
35 *
36 * manages the set of VMs in a running Velocity engine.
37 *
38 * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
39 * @version $Id: VelocimacroFactory.java 469919 2006-11-01 14:35:46Z henning $
40 */
41 public class VelocimacroFactory
42 {
43 /**
44 * runtime services for this instance
45 */
46 private final RuntimeServices rsvc;
47
48 /**
49 * the log for this instance
50 */
51 private final LogDisplayWrapper log;
52
53 /**
54 * VMManager : deal with namespace management
55 * and actually keeps all the VM definitions
56 */
57 private VelocimacroManager vmManager = null;
58
59 /**
60 * determines if replacement of global VMs are allowed
61 * controlled by VM_PERM_ALLOW_INLINE_REPLACE_GLOBAL
62 */
63 private boolean replaceAllowed = false;
64
65 /**
66 * controls if new VMs can be added. Set by
67 * VM_PERM_ALLOW_INLINE Note the assumption that only
68 * through inline defs can this happen.
69 * additions through autoloaded VMs is allowed
70 */
71 private boolean addNewAllowed = true;
72
73 /**
74 * sets if template-local namespace in used
75 */
76 private boolean templateLocal = false;
77
78 /**
79 * determines if the libraries are auto-loaded
80 * when they change
81 */
82 private boolean autoReloadLibrary = false;
83
84 /**
85 * vector of the library names
86 */
87 private Vector macroLibVec = null;
88
89 /**
90 * map of the library Template objects
91 * used for reload determination
92 */
93 private Map libModMap;
94
95 /**
96 * C'tor for the VelociMacro factory.
97 *
98 * @param rsvc Reference to a runtime services object.
99 */
100 public VelocimacroFactory(final RuntimeServices rsvc)
101 {
102 this.rsvc = rsvc;
103 this.log = new LogDisplayWrapper(rsvc.getLog(), "Velocimacro : ",
104 rsvc.getBoolean(RuntimeConstants.VM_MESSAGES_ON, true));
105
106 /*
107 * we always access in a synchronized(), so we
108 * can use an unsynchronized hashmap
109 */
110 libModMap = new HashMap();
111 vmManager = new VelocimacroManager(rsvc);
112 }
113
114 /**
115 * initialize the factory - setup all permissions
116 * load all global libraries.
117 */
118 public void initVelocimacro()
119 {
120 /*
121 * maybe I'm just paranoid...
122 */
123 synchronized(this)
124 {
125 log.trace("initialization starting.");
126
127 /*
128 * allow replacements while we add the libraries, if exist
129 */
130 setReplacementPermission(true);
131
132 /*
133 * add all library macros to the global namespace
134 */
135
136 vmManager.setNamespaceUsage(false);
137
138 /*
139 * now, if there is a global or local libraries specified, use them.
140 * All we have to do is get the template. The template will be parsed;
141 * VM's are added during the parse phase
142 */
143
144 Object libfiles = rsvc.getProperty(RuntimeConstants.VM_LIBRARY);
145
146 if (libfiles == null)
147 {
148 log.debug("\"" + RuntimeConstants.VM_LIBRARY +
149 "\" is not set. Trying default library: " +
150 RuntimeConstants.VM_LIBRARY_DEFAULT);
151
152 // try the default library.
153 if (rsvc.getLoaderNameForResource(RuntimeConstants.VM_LIBRARY_DEFAULT) != null)
154 {
155 libfiles = RuntimeConstants.VM_LIBRARY_DEFAULT;
156 }
157 else
158 {
159 log.debug("Default library not found.");
160 }
161 }
162
163 if(libfiles != null)
164 {
165 if (libfiles instanceof Vector)
166 {
167 macroLibVec = (Vector) libfiles;
168 }
169 else if (libfiles instanceof String)
170 {
171 macroLibVec = new Vector();
172 macroLibVec.addElement(libfiles);
173 }
174
175 for(int i = 0; i < macroLibVec.size(); i++)
176 {
177 String lib = (String) macroLibVec.elementAt(i);
178
179 /*
180 * only if it's a non-empty string do we bother
181 */
182
183 if (StringUtils.isNotEmpty(lib))
184 {
185 /*
186 * let the VMManager know that the following is coming
187 * from libraries - need to know for auto-load
188 */
189
190 vmManager.setRegisterFromLib(true);
191
192 log.debug("adding VMs from VM library : " + lib);
193
194 try
195 {
196 Template template = rsvc.getTemplate(lib);
197
198 /*
199 * save the template. This depends on the assumption
200 * that the Template object won't change - currently
201 * this is how the Resource manager works
202 */
203
204 Twonk twonk = new Twonk();
205 twonk.template = template;
206 twonk.modificationTime = template.getLastModified();
207 libModMap.put(lib, twonk);
208 }
209 catch (Exception e)
210 {
211 log.error(true, "Velocimacro : Error using VM library : " + lib, e);
212 }
213
214 log.trace("VM library registration complete.");
215
216 vmManager.setRegisterFromLib(false);
217 }
218 }
219 }
220
221 /*
222 * now, the permissions
223 */
224
225
226 /*
227 * allowinline : anything after this will be an inline macro, I think
228 * there is the question if a #include is an inline, and I think so
229 *
230 * default = true
231 */
232 setAddMacroPermission(true);
233
234 if (!rsvc.getBoolean( RuntimeConstants.VM_PERM_ALLOW_INLINE, true))
235 {
236 setAddMacroPermission(false);
237
238 log.info("allowInline = false : VMs can NOT be defined inline in templates");
239 }
240 else
241 {
242 log.debug("allowInline = true : VMs can be defined inline in templates");
243 }
244
245 /*
246 * allowInlineToReplaceGlobal : allows an inline VM , if allowed at all,
247 * to replace an existing global VM
248 *
249 * default = false
250 */
251 setReplacementPermission(false);
252
253 if (rsvc.getBoolean(
254 RuntimeConstants.VM_PERM_ALLOW_INLINE_REPLACE_GLOBAL, false))
255 {
256 setReplacementPermission(true);
257
258 log.info("allowInlineToOverride = true : VMs " +
259 "defined inline may replace previous VM definitions");
260 }
261 else
262 {
263 log.debug("allowInlineToOverride = false : VMs " +
264 "defined inline may NOT replace previous VM definitions");
265 }
266
267 /*
268 * now turn on namespace handling as far as permissions allow in the
269 * manager, and also set it here for gating purposes
270 */
271 vmManager.setNamespaceUsage(true);
272
273 /*
274 * template-local inline VM mode : default is off
275 */
276 setTemplateLocalInline(rsvc.getBoolean(
277 RuntimeConstants.VM_PERM_INLINE_LOCAL, false));
278
279 if (getTemplateLocalInline())
280 {
281 log.info("allowInlineLocal = true : VMs " +
282 "defined inline will be local to their defining template only.");
283 }
284 else
285 {
286 log.debug("allowInlineLocal = false : VMs " +
287 "defined inline will be global in scope if allowed.");
288 }
289
290 vmManager.setTemplateLocalInlineVM(getTemplateLocalInline());
291
292 /*
293 * autoload VM libraries
294 */
295 setAutoload(rsvc.getBoolean(RuntimeConstants.VM_LIBRARY_AUTORELOAD, false));
296
297 if (getAutoload())
298 {
299 log.info("autoload on : VM system " +
300 "will automatically reload global library macros");
301 }
302 else
303 {
304 log.debug("autoload off : VM system " +
305 "will not automatically reload global library macros");
306 }
307
308 log.trace("Velocimacro : initialization complete.");
309 }
310 }
311
312 /**
313 * adds a macro to the factory.
314 *
315 * @param name Name of the Macro to add.
316 * @param macroBody String representation of the macro.
317 * @param argArray Macro arguments. First element is the macro name.
318 * @param sourceTemplate Source template from which the macro gets registered.
319 * @return True if Macro was registered successfully.
320 */
321 public boolean addVelocimacro(String name, String macroBody,
322 String argArray[], String sourceTemplate)
323 {
324 /*
325 * maybe we should throw an exception, maybe just tell
326 * the caller like this...
327 *
328 * I hate this : maybe exceptions are in order here...
329 */
330 if (name == null || macroBody == null || argArray == null ||
331 sourceTemplate == null)
332 {
333 log.warn("VM addition rejected : programmer error : arg null");
334 return false;
335 }
336
337 /*
338 * see if the current ruleset allows this addition
339 */
340
341 if (!canAddVelocimacro(name, sourceTemplate))
342 {
343 return false;
344 }
345
346 /*
347 * seems like all is good. Lets do it.
348 */
349 synchronized(this)
350 {
351 vmManager.addVM(name, macroBody, argArray, sourceTemplate);
352 }
353
354 /*
355 * Report addition of the new Velocimacro.
356 */
357 StringBuffer msg = new StringBuffer("added ");
358 Macro.macroToString(msg, argArray);
359 msg.append(" : source = ").append(sourceTemplate);
360 log.info(msg.toString());
361
362 return true;
363 }
364
365 /**
366 * determines if a given macro/namespace (name, source) combo is allowed
367 * to be added
368 *
369 * @param name Name of VM to add
370 * @param sourceTemplate Source template that contains the defintion of the VM
371 * @return true if it is allowed to be added, false otherwise
372 */
373 private synchronized boolean canAddVelocimacro(String name, String sourceTemplate)
374 {
375 /*
376 * short circuit and do it if autoloader is on, and the
377 * template is one of the library templates
378 */
379
380 if (getAutoload() && (macroLibVec != null))
381 {
382 /*
383 * see if this is a library template
384 */
385
386 for(int i = 0; i < macroLibVec.size(); i++)
387 {
388 String lib = (String) macroLibVec.elementAt(i);
389
390 if (lib.equals(sourceTemplate))
391 {
392 return true;
393 }
394 }
395 }
396
397
398 /*
399 * maybe the rules should be in manager? I dunno. It's to manage
400 * the namespace issues first, are we allowed to add VMs at all?
401 * This trumps all.
402 */
403 if (!addNewAllowed)
404 {
405 log.warn("VM addition rejected : "+name+" : inline VMs not allowed.");
406 return false;
407 }
408
409 /*
410 * are they local in scope? Then it is ok to add.
411 */
412 if (!templateLocal)
413 {
414 /*
415 * otherwise, if we have it already in global namespace, and they can't replace
416 * since local templates are not allowed, the global namespace is implied.
417 * remember, we don't know anything about namespace managment here, so lets
418 * note do anything fancy like trying to give it the global namespace here
419 *
420 * so if we have it, and we aren't allowed to replace, bail
421 */
422 if (isVelocimacro(name, sourceTemplate) && !replaceAllowed)
423 {
424 log.warn("VM addition rejected : "+name+" : inline not allowed to replace existing VM");
425 return false;
426 }
427 }
428
429 return true;
430 }
431
432 /**
433 * Tells the world if a given directive string is a Velocimacro
434 * @param vm Name of the Macro.
435 * @param sourceTemplate Source template from which the macro should be loaded.
436 * @return True if the given name is a macro.
437 */
438 public boolean isVelocimacro(String vm , String sourceTemplate)
439 {
440 synchronized(this)
441 {
442 /*
443 * first we check the locals to see if we have
444 * a local definition for this template
445 */
446 if (vmManager.get(vm, sourceTemplate) != null)
447 return true;
448 }
449 return false;
450 }
451
452 /**
453 * actual factory : creates a Directive that will
454 * behave correctly wrt getting the framework to
455 * dig out the correct # of args
456 * @param vmName Name of the Macro.
457 * @param sourceTemplate Source template from which the macro should be loaded.
458 * @return A directive representing the Macro.
459 */
460 public Directive getVelocimacro(String vmName, String sourceTemplate)
461 {
462 VelocimacroProxy vp = null;
463
464 synchronized(this)
465 {
466 /*
467 * don't ask - do
468 */
469
470 vp = vmManager.get(vmName, sourceTemplate);
471
472 /*
473 * if this exists, and autoload is on, we need to check
474 * where this VM came from
475 */
476
477 if (vp != null && getAutoload())
478 {
479 /*
480 * see if this VM came from a library. Need to pass sourceTemplate
481 * in the event namespaces are set, as it could be masked by local
482 */
483
484 String lib = vmManager.getLibraryName(vmName, sourceTemplate);
485
486 if (lib != null)
487 {
488 try
489 {
490 /*
491 * get the template from our map
492 */
493
494 Twonk tw = (Twonk) libModMap.get(lib);
495
496 if (tw != null)
497 {
498 Template template = tw.template;
499
500 /*
501 * now, compare the last modified time of the resource
502 * with the last modified time of the template
503 * if the file has changed, then reload. Otherwise, we should
504 * be ok.
505 */
506
507 long tt = tw.modificationTime;
508 long ft = template.getResourceLoader().getLastModified(template);
509
510 if (ft > tt)
511 {
512 log.debug("auto-reloading VMs from VM library : "+lib);
513
514 /*
515 * when there are VMs in a library that invoke each other,
516 * there are calls into getVelocimacro() from the init()
517 * process of the VM directive. To stop the infinite loop
518 * we save the current time reported by the resource loader
519 * and then be honest when the reload is complete
520 */
521
522 tw.modificationTime = ft;
523
524 template = rsvc.getTemplate(lib);
525
526 /*
527 * and now we be honest
528 */
529
530 tw.template = template;
531 tw.modificationTime = template.getLastModified();
532
533 /*
534 * note that we don't need to put this twonk back
535 * into the map, as we can just use the same reference
536 * and this block is synchronized
537 */
538 }
539 }
540 }
541 catch (Exception e)
542 {
543 log.error(true, "Velocimacro : Error using VM library : "+lib, e);
544 }
545
546 /*
547 * and get again
548 */
549
550 vp = vmManager.get(vmName, sourceTemplate);
551 }
552 }
553 }
554
555 return vp;
556 }
557
558 /**
559 * tells the vmManager to dump the specified namespace
560 * @param namespace Namespace to dump.
561 * @return True if namespace has been dumped successfully.
562 */
563 public boolean dumpVMNamespace(String namespace)
564 {
565 return vmManager.dumpNamespace(namespace);
566 }
567
568 /**
569 * sets permission to have VMs local in scope to their declaring template
570 * note that this is really taken care of in the VMManager class, but
571 * we need it here for gating purposes in addVM
572 * eventually, I will slide this all into the manager, maybe.
573 */
574 private void setTemplateLocalInline(boolean b)
575 {
576 templateLocal = b;
577 }
578
579 private boolean getTemplateLocalInline()
580 {
581 return templateLocal;
582 }
583
584 /**
585 * sets the permission to add new macros
586 */
587 private boolean setAddMacroPermission(final boolean addNewAllowed)
588 {
589 boolean b = this.addNewAllowed;
590 this.addNewAllowed = addNewAllowed;
591 return b;
592 }
593
594 /**
595 * sets the permission for allowing addMacro() calls to
596 * replace existing VM's
597 */
598 private boolean setReplacementPermission(boolean arg)
599 {
600 boolean b = replaceAllowed;
601 replaceAllowed = arg;
602 return b;
603 }
604
605 /**
606 * set the switch for automatic reloading of
607 * global library-based VMs
608 */
609 private void setAutoload(boolean b)
610 {
611 autoReloadLibrary = b;
612 }
613
614 /**
615 * get the switch for automatic reloading of
616 * global library-based VMs
617 */
618 private boolean getAutoload()
619 {
620 return autoReloadLibrary;
621 }
622
623 /**
624 * small container class to hold the tuple
625 * of a template and modification time.
626 * We keep the modification time so we can
627 * 'override' it on a reload to prevent
628 * recursive reload due to inter-calling
629 * VMs in a library
630 */
631 private static class Twonk
632 {
633 /** Template kept in this container. */
634 public Template template;
635
636 /** modification time of the template. */
637 public long modificationTime;
638 }
639 }
640
641
642
643
644
645
646