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.io.BufferedReader;
023: import java.io.StringReader;
024: import java.util.Hashtable;
025: import org.apache.velocity.context.InternalContextAdapter;
026: import org.apache.velocity.runtime.directive.VelocimacroProxy;
027: import org.apache.velocity.runtime.parser.node.SimpleNode;
028:
029: /**
030: * Manages VMs in namespaces. Currently, two namespace modes are
031: * supported:
032: *
033: * <ul>
034: * <li>flat - all allowable VMs are in the global namespace</li>
035: * <li>local - inline VMs are added to it's own template namespace</li>
036: * </ul>
037: *
038: * Thanks to <a href="mailto:JFernandez@viquity.com">Jose Alberto Fernandez</a>
039: * for some ideas incorporated here.
040: *
041: * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
042: * @author <a href="mailto:JFernandez@viquity.com">Jose Alberto Fernandez</a>
043: * @version $Id: VelocimacroManager.java 463298 2006-10-12 16:10:32Z henning $
044: */
045: public class VelocimacroManager {
046: private final RuntimeServices rsvc;
047: private static String GLOBAL_NAMESPACE = "";
048:
049: private boolean registerFromLib = false;
050:
051: /** Hash of namespace hashes. */
052: private final Hashtable namespaceHash = new Hashtable();
053:
054: /** map of names of library tempates/namespaces */
055: private final Hashtable libraryMap = new Hashtable();
056:
057: /*
058: * big switch for namespaces. If true, then properties control
059: * usage. If false, no.
060: */
061: private boolean namespacesOn = true;
062: private boolean inlineLocalMode = false;
063:
064: /**
065: * Adds the global namespace to the hash.
066: */
067: VelocimacroManager(RuntimeServices rsvc) {
068: this .rsvc = rsvc;
069:
070: /*
071: * add the global namespace to the namespace hash. We always have that.
072: */
073:
074: addNamespace(GLOBAL_NAMESPACE);
075: }
076:
077: /**
078: * Adds a VM definition to the cache.
079: * @param vmName Name of the new VelociMacro.
080: * @param macroBody String representation of the macro body.
081: * @param argArray Array of macro parameters, first parameter is the macro name.
082: * @param namespace The namespace/template from which this macro has been loaded.
083: * @return Whether everything went okay.
084: */
085: public boolean addVM(final String vmName, final String macroBody,
086: final String argArray[], final String namespace) {
087: MacroEntry me = new MacroEntry(vmName, macroBody, argArray,
088: namespace);
089:
090: me.setFromLibrary(registerFromLib);
091:
092: /*
093: * the client (VMFactory) will signal to us via
094: * registerFromLib that we are in startup mode registering
095: * new VMs from libraries. Therefore, we want to
096: * addto the library map for subsequent auto reloads
097: */
098:
099: boolean isLib = true;
100:
101: if (registerFromLib) {
102: libraryMap.put(namespace, namespace);
103: } else {
104: /*
105: * now, we first want to check to see if this namespace (template)
106: * is actually a library - if so, we need to use the global namespace
107: * we don't have to do this when registering, as namespaces should
108: * be shut off. If not, the default value is true, so we still go
109: * global
110: */
111:
112: isLib = libraryMap.containsKey(namespace);
113: }
114:
115: if (!isLib && usingNamespaces(namespace)) {
116: /*
117: * first, do we have a namespace hash already for this namespace?
118: * if not, add it to the namespaces, and add the VM
119: */
120:
121: Hashtable local = getNamespace(namespace, true);
122: local.put(vmName, me);
123:
124: return true;
125: } else {
126: /*
127: * otherwise, add to global template. First, check if we
128: * already have it to preserve some of the autoload information
129: */
130:
131: MacroEntry exist = (MacroEntry) getNamespace(
132: GLOBAL_NAMESPACE).get(vmName);
133:
134: if (exist != null) {
135: me.setFromLibrary(exist.getFromLibrary());
136: }
137:
138: /*
139: * now add it
140: */
141:
142: getNamespace(GLOBAL_NAMESPACE).put(vmName, me);
143:
144: return true;
145: }
146: }
147:
148: /**
149: * gets a new living VelocimacroProxy object by the
150: * name / source template duple
151: * @param vmName Name of the VelocityMacro to look up.
152: * @param namespace Namespace in which to look up the macro.
153: * @return A proxy representing the Macro.
154: */
155: public VelocimacroProxy get(final String vmName,
156: final String namespace) {
157:
158: if (usingNamespaces(namespace)) {
159: Hashtable local = getNamespace(namespace, false);
160:
161: /*
162: * if we have macros defined for this template
163: */
164:
165: if (local != null) {
166: MacroEntry me = (MacroEntry) local.get(vmName);
167:
168: if (me != null) {
169: return me.createVelocimacro(namespace);
170: }
171: }
172: }
173:
174: /*
175: * if we didn't return from there, we need to simply see
176: * if it's in the global namespace
177: */
178:
179: MacroEntry me = (MacroEntry) getNamespace(GLOBAL_NAMESPACE)
180: .get(vmName);
181:
182: if (me != null) {
183: return me.createVelocimacro(namespace);
184: }
185:
186: return null;
187: }
188:
189: /**
190: * Removes the VMs and the namespace from the manager.
191: * Used when a template is reloaded to avoid
192: * losing memory.
193: *
194: * @param namespace namespace to dump
195: * @return boolean representing success
196: */
197: public boolean dumpNamespace(final String namespace) {
198: synchronized (this ) {
199: if (usingNamespaces(namespace)) {
200: Hashtable h = (Hashtable) namespaceHash
201: .remove(namespace);
202:
203: if (h == null) {
204: return false;
205: }
206:
207: h.clear();
208:
209: return true;
210: }
211:
212: return false;
213: }
214: }
215:
216: /**
217: * public switch to let external user of manager to control namespace
218: * usage indep of properties. That way, for example, at startup the
219: * library files are loaded into global namespace
220: *
221: * @param namespaceOn True if namespaces should be used.
222: */
223: public void setNamespaceUsage(final boolean namespaceOn) {
224: this .namespacesOn = namespaceOn;
225: }
226:
227: /**
228: * Should macros registered from Libraries be marked special?
229: * @param registerFromLib True if macros from Libs should be marked.
230: */
231: public void setRegisterFromLib(final boolean registerFromLib) {
232: this .registerFromLib = registerFromLib;
233: }
234:
235: /**
236: * Should macros from the same template be inlined?
237: *
238: * @param inlineLocalMode True if macros should be inlined on the same template.
239: */
240: public void setTemplateLocalInlineVM(final boolean inlineLocalMode) {
241: this .inlineLocalMode = inlineLocalMode;
242: }
243:
244: /**
245: * returns the hash for the specified namespace. Will not create a new one
246: * if it doesn't exist
247: *
248: * @param namespace name of the namespace :)
249: * @return namespace Hashtable of VMs or null if doesn't exist
250: */
251: private Hashtable getNamespace(final String namespace) {
252: return getNamespace(namespace, false);
253: }
254:
255: /**
256: * returns the hash for the specified namespace, and if it doesn't exist
257: * will create a new one and add it to the namespaces
258: *
259: * @param namespace name of the namespace :)
260: * @param addIfNew flag to add a new namespace if it doesn't exist
261: * @return namespace Hashtable of VMs or null if doesn't exist
262: */
263: private Hashtable getNamespace(final String namespace,
264: final boolean addIfNew) {
265: Hashtable h = (Hashtable) namespaceHash.get(namespace);
266:
267: if (h == null && addIfNew) {
268: h = addNamespace(namespace);
269: }
270:
271: return h;
272: }
273:
274: /**
275: * adds a namespace to the namespaces
276: *
277: * @param namespace name of namespace to add
278: * @return Hash added to namespaces, ready for use
279: */
280: private Hashtable addNamespace(final String namespace) {
281: Hashtable h = new Hashtable();
282: Object oh;
283:
284: if ((oh = namespaceHash.put(namespace, h)) != null) {
285: /*
286: * There was already an entry on the table, restore it!
287: * This condition should never occur, given the code
288: * and the fact that this method is private.
289: * But just in case, this way of testing for it is much
290: * more efficient than testing before hand using get().
291: */
292: namespaceHash.put(namespace, oh);
293: /*
294: * Should't we be returning the old entry (oh)?
295: * The previous code was just returning null in this case.
296: */
297: return null;
298: }
299:
300: return h;
301: }
302:
303: /**
304: * determines if currently using namespaces.
305: *
306: * @param namespace currently ignored
307: * @return true if using namespaces, false if not
308: */
309: private boolean usingNamespaces(final String namespace) {
310: /*
311: * if the big switch turns of namespaces, then ignore the rules
312: */
313:
314: if (!namespacesOn) {
315: return false;
316: }
317:
318: /*
319: * currently, we only support the local template namespace idea
320: */
321:
322: if (inlineLocalMode) {
323: return true;
324: }
325:
326: return false;
327: }
328:
329: /**
330: * Return the library name for a given macro.
331: * @param vmName Name of the Macro to look up.
332: * @param namespace Namespace to look the macro up.
333: * @return The name of the library which registered this macro in a namespace.
334: */
335: public String getLibraryName(final String vmName,
336: final String namespace) {
337: if (usingNamespaces(namespace)) {
338: Hashtable local = getNamespace(namespace, false);
339:
340: /*
341: * if we have this macro defined in this namespace, then
342: * it is masking the global, library-based one, so
343: * just return null
344: */
345:
346: if (local != null) {
347: MacroEntry me = (MacroEntry) local.get(vmName);
348:
349: if (me != null) {
350: return null;
351: }
352: }
353: }
354:
355: /*
356: * if we didn't return from there, we need to simply see
357: * if it's in the global namespace
358: */
359:
360: MacroEntry me = (MacroEntry) getNamespace(GLOBAL_NAMESPACE)
361: .get(vmName);
362:
363: if (me != null) {
364: return me.getSourceTemplate();
365: }
366:
367: return null;
368: }
369:
370: /**
371: * wrapper class for holding VM information
372: */
373: private class MacroEntry {
374: private final String vmName;
375: private final String[] argArray;
376: private final String macroBody;
377: private final String sourceTemplate;
378:
379: private SimpleNode nodeTree = null;
380: private boolean fromLibrary = false;
381:
382: private MacroEntry(final String vmName, final String macroBody,
383: final String argArray[], final String sourceTemplate) {
384: this .vmName = vmName;
385: this .argArray = argArray;
386: this .macroBody = macroBody;
387: this .sourceTemplate = sourceTemplate;
388: }
389:
390: /**
391: * Has the macro been registered from a library.
392: * @param fromLibrary True if the macro was registered from a Library.
393: */
394: public void setFromLibrary(final boolean fromLibrary) {
395: this .fromLibrary = fromLibrary;
396: }
397:
398: /**
399: * Returns true if the macro was registered from a library.
400: * @return True if the macro was registered from a library.
401: */
402: public boolean getFromLibrary() {
403: return fromLibrary;
404: }
405:
406: /**
407: * Returns the node tree for this macro.
408: * @return The node tree for this macro.
409: */
410: public SimpleNode getNodeTree() {
411: return nodeTree;
412: }
413:
414: /**
415: * Returns the source template name for this macro.
416: * @return The source template name for this macro.
417: */
418: public String getSourceTemplate() {
419: return sourceTemplate;
420: }
421:
422: VelocimacroProxy createVelocimacro(final String namespace) {
423: VelocimacroProxy vp = new VelocimacroProxy();
424: vp.setName(this .vmName);
425: vp.setArgArray(this .argArray);
426: vp.setMacrobody(this .macroBody);
427: vp.setNodeTree(this .nodeTree);
428: vp.setNamespace(namespace);
429: return vp;
430: }
431:
432: void setup(final InternalContextAdapter ica) {
433: /*
434: * if not parsed yet, parse!
435: */
436:
437: if (nodeTree == null) {
438: parseTree(ica);
439: }
440: }
441:
442: void parseTree(final InternalContextAdapter ica) {
443: try {
444: BufferedReader br = new BufferedReader(
445: new StringReader(macroBody));
446:
447: nodeTree = rsvc.parse(br, "VM:" + vmName, true);
448: nodeTree.init(ica, null);
449: } catch (Exception e) {
450: rsvc.getLog().error(
451: "VelocimacroManager.parseTree() failed on VM '"
452: + vmName + "'", e);
453: }
454: }
455: }
456: }
|