001: package org.apache.velocity.runtime;
002:
003: /*
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021:
022: import java.util.HashMap;
023: import java.util.Map;
024: import java.util.Vector;
025:
026: import org.apache.commons.lang.StringUtils;
027: import org.apache.velocity.Template;
028: import org.apache.velocity.runtime.directive.Directive;
029: import org.apache.velocity.runtime.directive.Macro;
030: import org.apache.velocity.runtime.directive.VelocimacroProxy;
031: import org.apache.velocity.runtime.log.LogDisplayWrapper;
032:
033: /**
034: * VelocimacroFactory.java
035: *
036: * manages the set of VMs in a running Velocity engine.
037: *
038: * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
039: * @version $Id: VelocimacroFactory.java 469919 2006-11-01 14:35:46Z henning $
040: */
041: public class VelocimacroFactory {
042: /**
043: * runtime services for this instance
044: */
045: private final RuntimeServices rsvc;
046:
047: /**
048: * the log for this instance
049: */
050: private final LogDisplayWrapper log;
051:
052: /**
053: * VMManager : deal with namespace management
054: * and actually keeps all the VM definitions
055: */
056: private VelocimacroManager vmManager = null;
057:
058: /**
059: * determines if replacement of global VMs are allowed
060: * controlled by VM_PERM_ALLOW_INLINE_REPLACE_GLOBAL
061: */
062: private boolean replaceAllowed = false;
063:
064: /**
065: * controls if new VMs can be added. Set by
066: * VM_PERM_ALLOW_INLINE Note the assumption that only
067: * through inline defs can this happen.
068: * additions through autoloaded VMs is allowed
069: */
070: private boolean addNewAllowed = true;
071:
072: /**
073: * sets if template-local namespace in used
074: */
075: private boolean templateLocal = false;
076:
077: /**
078: * determines if the libraries are auto-loaded
079: * when they change
080: */
081: private boolean autoReloadLibrary = false;
082:
083: /**
084: * vector of the library names
085: */
086: private Vector macroLibVec = null;
087:
088: /**
089: * map of the library Template objects
090: * used for reload determination
091: */
092: private Map libModMap;
093:
094: /**
095: * C'tor for the VelociMacro factory.
096: *
097: * @param rsvc Reference to a runtime services object.
098: */
099: public VelocimacroFactory(final RuntimeServices rsvc) {
100: this .rsvc = rsvc;
101: this .log = new LogDisplayWrapper(rsvc.getLog(),
102: "Velocimacro : ", rsvc.getBoolean(
103: RuntimeConstants.VM_MESSAGES_ON, true));
104:
105: /*
106: * we always access in a synchronized(), so we
107: * can use an unsynchronized hashmap
108: */
109: libModMap = new HashMap();
110: vmManager = new VelocimacroManager(rsvc);
111: }
112:
113: /**
114: * initialize the factory - setup all permissions
115: * load all global libraries.
116: */
117: public void initVelocimacro() {
118: /*
119: * maybe I'm just paranoid...
120: */
121: synchronized (this ) {
122: log.trace("initialization starting.");
123:
124: /*
125: * allow replacements while we add the libraries, if exist
126: */
127: setReplacementPermission(true);
128:
129: /*
130: * add all library macros to the global namespace
131: */
132:
133: vmManager.setNamespaceUsage(false);
134:
135: /*
136: * now, if there is a global or local libraries specified, use them.
137: * All we have to do is get the template. The template will be parsed;
138: * VM's are added during the parse phase
139: */
140:
141: Object libfiles = rsvc
142: .getProperty(RuntimeConstants.VM_LIBRARY);
143:
144: if (libfiles == null) {
145: log.debug("\"" + RuntimeConstants.VM_LIBRARY
146: + "\" is not set. Trying default library: "
147: + RuntimeConstants.VM_LIBRARY_DEFAULT);
148:
149: // try the default library.
150: if (rsvc
151: .getLoaderNameForResource(RuntimeConstants.VM_LIBRARY_DEFAULT) != null) {
152: libfiles = RuntimeConstants.VM_LIBRARY_DEFAULT;
153: } else {
154: log.debug("Default library not found.");
155: }
156: }
157:
158: if (libfiles != null) {
159: if (libfiles instanceof Vector) {
160: macroLibVec = (Vector) libfiles;
161: } else if (libfiles instanceof String) {
162: macroLibVec = new Vector();
163: macroLibVec.addElement(libfiles);
164: }
165:
166: for (int i = 0; i < macroLibVec.size(); i++) {
167: String lib = (String) macroLibVec.elementAt(i);
168:
169: /*
170: * only if it's a non-empty string do we bother
171: */
172:
173: if (StringUtils.isNotEmpty(lib)) {
174: /*
175: * let the VMManager know that the following is coming
176: * from libraries - need to know for auto-load
177: */
178:
179: vmManager.setRegisterFromLib(true);
180:
181: log
182: .debug("adding VMs from VM library : "
183: + lib);
184:
185: try {
186: Template template = rsvc.getTemplate(lib);
187:
188: /*
189: * save the template. This depends on the assumption
190: * that the Template object won't change - currently
191: * this is how the Resource manager works
192: */
193:
194: Twonk twonk = new Twonk();
195: twonk.template = template;
196: twonk.modificationTime = template
197: .getLastModified();
198: libModMap.put(lib, twonk);
199: } catch (Exception e) {
200: log.error(true,
201: "Velocimacro : Error using VM library : "
202: + lib, e);
203: }
204:
205: log.trace("VM library registration complete.");
206:
207: vmManager.setRegisterFromLib(false);
208: }
209: }
210: }
211:
212: /*
213: * now, the permissions
214: */
215:
216: /*
217: * allowinline : anything after this will be an inline macro, I think
218: * there is the question if a #include is an inline, and I think so
219: *
220: * default = true
221: */
222: setAddMacroPermission(true);
223:
224: if (!rsvc.getBoolean(RuntimeConstants.VM_PERM_ALLOW_INLINE,
225: true)) {
226: setAddMacroPermission(false);
227:
228: log
229: .info("allowInline = false : VMs can NOT be defined inline in templates");
230: } else {
231: log
232: .debug("allowInline = true : VMs can be defined inline in templates");
233: }
234:
235: /*
236: * allowInlineToReplaceGlobal : allows an inline VM , if allowed at all,
237: * to replace an existing global VM
238: *
239: * default = false
240: */
241: setReplacementPermission(false);
242:
243: if (rsvc
244: .getBoolean(
245: RuntimeConstants.VM_PERM_ALLOW_INLINE_REPLACE_GLOBAL,
246: false)) {
247: setReplacementPermission(true);
248:
249: log
250: .info("allowInlineToOverride = true : VMs "
251: + "defined inline may replace previous VM definitions");
252: } else {
253: log
254: .debug("allowInlineToOverride = false : VMs "
255: + "defined inline may NOT replace previous VM definitions");
256: }
257:
258: /*
259: * now turn on namespace handling as far as permissions allow in the
260: * manager, and also set it here for gating purposes
261: */
262: vmManager.setNamespaceUsage(true);
263:
264: /*
265: * template-local inline VM mode : default is off
266: */
267: setTemplateLocalInline(rsvc.getBoolean(
268: RuntimeConstants.VM_PERM_INLINE_LOCAL, false));
269:
270: if (getTemplateLocalInline()) {
271: log
272: .info("allowInlineLocal = true : VMs "
273: + "defined inline will be local to their defining template only.");
274: } else {
275: log
276: .debug("allowInlineLocal = false : VMs "
277: + "defined inline will be global in scope if allowed.");
278: }
279:
280: vmManager
281: .setTemplateLocalInlineVM(getTemplateLocalInline());
282:
283: /*
284: * autoload VM libraries
285: */
286: setAutoload(rsvc.getBoolean(
287: RuntimeConstants.VM_LIBRARY_AUTORELOAD, false));
288:
289: if (getAutoload()) {
290: log
291: .info("autoload on : VM system "
292: + "will automatically reload global library macros");
293: } else {
294: log
295: .debug("autoload off : VM system "
296: + "will not automatically reload global library macros");
297: }
298:
299: log.trace("Velocimacro : initialization complete.");
300: }
301: }
302:
303: /**
304: * adds a macro to the factory.
305: *
306: * @param name Name of the Macro to add.
307: * @param macroBody String representation of the macro.
308: * @param argArray Macro arguments. First element is the macro name.
309: * @param sourceTemplate Source template from which the macro gets registered.
310: * @return True if Macro was registered successfully.
311: */
312: public boolean addVelocimacro(String name, String macroBody,
313: String argArray[], String sourceTemplate) {
314: /*
315: * maybe we should throw an exception, maybe just tell
316: * the caller like this...
317: *
318: * I hate this : maybe exceptions are in order here...
319: */
320: if (name == null || macroBody == null || argArray == null
321: || sourceTemplate == null) {
322: log
323: .warn("VM addition rejected : programmer error : arg null");
324: return false;
325: }
326:
327: /*
328: * see if the current ruleset allows this addition
329: */
330:
331: if (!canAddVelocimacro(name, sourceTemplate)) {
332: return false;
333: }
334:
335: /*
336: * seems like all is good. Lets do it.
337: */
338: synchronized (this ) {
339: vmManager.addVM(name, macroBody, argArray, sourceTemplate);
340: }
341:
342: /*
343: * Report addition of the new Velocimacro.
344: */
345: StringBuffer msg = new StringBuffer("added ");
346: Macro.macroToString(msg, argArray);
347: msg.append(" : source = ").append(sourceTemplate);
348: log.info(msg.toString());
349:
350: return true;
351: }
352:
353: /**
354: * determines if a given macro/namespace (name, source) combo is allowed
355: * to be added
356: *
357: * @param name Name of VM to add
358: * @param sourceTemplate Source template that contains the defintion of the VM
359: * @return true if it is allowed to be added, false otherwise
360: */
361: private synchronized boolean canAddVelocimacro(String name,
362: String sourceTemplate) {
363: /*
364: * short circuit and do it if autoloader is on, and the
365: * template is one of the library templates
366: */
367:
368: if (getAutoload() && (macroLibVec != null)) {
369: /*
370: * see if this is a library template
371: */
372:
373: for (int i = 0; i < macroLibVec.size(); i++) {
374: String lib = (String) macroLibVec.elementAt(i);
375:
376: if (lib.equals(sourceTemplate)) {
377: return true;
378: }
379: }
380: }
381:
382: /*
383: * maybe the rules should be in manager? I dunno. It's to manage
384: * the namespace issues first, are we allowed to add VMs at all?
385: * This trumps all.
386: */
387: if (!addNewAllowed) {
388: log.warn("VM addition rejected : " + name
389: + " : inline VMs not allowed.");
390: return false;
391: }
392:
393: /*
394: * are they local in scope? Then it is ok to add.
395: */
396: if (!templateLocal) {
397: /*
398: * otherwise, if we have it already in global namespace, and they can't replace
399: * since local templates are not allowed, the global namespace is implied.
400: * remember, we don't know anything about namespace managment here, so lets
401: * note do anything fancy like trying to give it the global namespace here
402: *
403: * so if we have it, and we aren't allowed to replace, bail
404: */
405: if (isVelocimacro(name, sourceTemplate) && !replaceAllowed) {
406: log
407: .warn("VM addition rejected : "
408: + name
409: + " : inline not allowed to replace existing VM");
410: return false;
411: }
412: }
413:
414: return true;
415: }
416:
417: /**
418: * Tells the world if a given directive string is a Velocimacro
419: * @param vm Name of the Macro.
420: * @param sourceTemplate Source template from which the macro should be loaded.
421: * @return True if the given name is a macro.
422: */
423: public boolean isVelocimacro(String vm, String sourceTemplate) {
424: synchronized (this ) {
425: /*
426: * first we check the locals to see if we have
427: * a local definition for this template
428: */
429: if (vmManager.get(vm, sourceTemplate) != null)
430: return true;
431: }
432: return false;
433: }
434:
435: /**
436: * actual factory : creates a Directive that will
437: * behave correctly wrt getting the framework to
438: * dig out the correct # of args
439: * @param vmName Name of the Macro.
440: * @param sourceTemplate Source template from which the macro should be loaded.
441: * @return A directive representing the Macro.
442: */
443: public Directive getVelocimacro(String vmName, String sourceTemplate) {
444: VelocimacroProxy vp = null;
445:
446: synchronized (this ) {
447: /*
448: * don't ask - do
449: */
450:
451: vp = vmManager.get(vmName, sourceTemplate);
452:
453: /*
454: * if this exists, and autoload is on, we need to check
455: * where this VM came from
456: */
457:
458: if (vp != null && getAutoload()) {
459: /*
460: * see if this VM came from a library. Need to pass sourceTemplate
461: * in the event namespaces are set, as it could be masked by local
462: */
463:
464: String lib = vmManager.getLibraryName(vmName,
465: sourceTemplate);
466:
467: if (lib != null) {
468: try {
469: /*
470: * get the template from our map
471: */
472:
473: Twonk tw = (Twonk) libModMap.get(lib);
474:
475: if (tw != null) {
476: Template template = tw.template;
477:
478: /*
479: * now, compare the last modified time of the resource
480: * with the last modified time of the template
481: * if the file has changed, then reload. Otherwise, we should
482: * be ok.
483: */
484:
485: long tt = tw.modificationTime;
486: long ft = template.getResourceLoader()
487: .getLastModified(template);
488:
489: if (ft > tt) {
490: log
491: .debug("auto-reloading VMs from VM library : "
492: + lib);
493:
494: /*
495: * when there are VMs in a library that invoke each other,
496: * there are calls into getVelocimacro() from the init()
497: * process of the VM directive. To stop the infinite loop
498: * we save the current time reported by the resource loader
499: * and then be honest when the reload is complete
500: */
501:
502: tw.modificationTime = ft;
503:
504: template = rsvc.getTemplate(lib);
505:
506: /*
507: * and now we be honest
508: */
509:
510: tw.template = template;
511: tw.modificationTime = template
512: .getLastModified();
513:
514: /*
515: * note that we don't need to put this twonk back
516: * into the map, as we can just use the same reference
517: * and this block is synchronized
518: */
519: }
520: }
521: } catch (Exception e) {
522: log.error(true,
523: "Velocimacro : Error using VM library : "
524: + lib, e);
525: }
526:
527: /*
528: * and get again
529: */
530:
531: vp = vmManager.get(vmName, sourceTemplate);
532: }
533: }
534: }
535:
536: return vp;
537: }
538:
539: /**
540: * tells the vmManager to dump the specified namespace
541: * @param namespace Namespace to dump.
542: * @return True if namespace has been dumped successfully.
543: */
544: public boolean dumpVMNamespace(String namespace) {
545: return vmManager.dumpNamespace(namespace);
546: }
547:
548: /**
549: * sets permission to have VMs local in scope to their declaring template
550: * note that this is really taken care of in the VMManager class, but
551: * we need it here for gating purposes in addVM
552: * eventually, I will slide this all into the manager, maybe.
553: */
554: private void setTemplateLocalInline(boolean b) {
555: templateLocal = b;
556: }
557:
558: private boolean getTemplateLocalInline() {
559: return templateLocal;
560: }
561:
562: /**
563: * sets the permission to add new macros
564: */
565: private boolean setAddMacroPermission(final boolean addNewAllowed) {
566: boolean b = this .addNewAllowed;
567: this .addNewAllowed = addNewAllowed;
568: return b;
569: }
570:
571: /**
572: * sets the permission for allowing addMacro() calls to
573: * replace existing VM's
574: */
575: private boolean setReplacementPermission(boolean arg) {
576: boolean b = replaceAllowed;
577: replaceAllowed = arg;
578: return b;
579: }
580:
581: /**
582: * set the switch for automatic reloading of
583: * global library-based VMs
584: */
585: private void setAutoload(boolean b) {
586: autoReloadLibrary = b;
587: }
588:
589: /**
590: * get the switch for automatic reloading of
591: * global library-based VMs
592: */
593: private boolean getAutoload() {
594: return autoReloadLibrary;
595: }
596:
597: /**
598: * small container class to hold the tuple
599: * of a template and modification time.
600: * We keep the modification time so we can
601: * 'override' it on a reload to prevent
602: * recursive reload due to inter-calling
603: * VMs in a library
604: */
605: private static class Twonk {
606: /** Template kept in this container. */
607: public Template template;
608:
609: /** modification time of the template. */
610: public long modificationTime;
611: }
612: }
|