expiremental support for the velocity template engine
[mir.git] / source / mir / generator / VelocityGenerator.java
1 /*\r
2  * Copyright (C) 2001, 2002 The Mir-coders group\r
3  *\r
4  * This file is part of Mir.\r
5  *\r
6  * Mir is free software; you can redistribute it and/or modify\r
7  * it under the terms of the GNU General Public License as published by\r
8  * the Free Software Foundation; either version 2 of the License, or\r
9  * (at your option) any later version.\r
10  *\r
11  * Mir is distributed in the hope that it will be useful,\r
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
14  * GNU General Public License for more details.\r
15  *\r
16  * You should have received a copy of the GNU General Public License\r
17  * along with Mir; if not, write to the Free Software\r
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\r
19  *\r
20  * In addition, as a special exception, The Mir-coders gives permission to link\r
21  * the code of this program with  any library licensed under the Apache Software License,\r
22  * The Sun (tm) Java Advanced Imaging library (JAI), The Sun JIMI library\r
23  * (or with modified versions of the above that use the same license as the above),\r
24  * and distribute linked combinations including the two.  You must obey the\r
25  * GNU General Public License in all respects for all of the code used other than\r
26  * the above mentioned libraries.  If you modify this file, you may extend this\r
27  * exception to your version of the file, but you are not obligated to do so.\r
28  * If you do not wish to do so, delete this exception statement from your version.\r
29  */\r
30 package mir.generator;\r
31 \r
32 import java.io.PrintWriter;\r
33 import java.io.StringWriter;\r
34 import java.util.AbstractList;\r
35 import java.util.Date;\r
36 import java.util.HashMap;\r
37 import java.util.Iterator;\r
38 import java.util.List;\r
39 import java.util.Map;\r
40 import java.util.Vector;\r
41 \r
42 import org.apache.commons.beanutils.MethodUtils;\r
43 import org.apache.commons.beanutils.PropertyUtils;\r
44 import org.apache.velocity.Template;\r
45 import org.apache.velocity.VelocityContext;\r
46 import org.apache.velocity.app.VelocityEngine;\r
47 import org.apache.velocity.context.Context;\r
48 import org.apache.velocity.exception.ParseErrorException;\r
49 import org.apache.velocity.exception.ResourceNotFoundException;\r
50 import org.apache.velocity.runtime.RuntimeServices;\r
51 import org.apache.velocity.runtime.log.LogSystem;\r
52 import mir.log.LoggerWrapper;\r
53 import mir.util.GeneratorFormatAdapters;\r
54 import mir.util.RewindableIterator;\r
55 \r
56 public class VelocityGenerator implements Generator {\r
57   private String templateIdentifier;\r
58   private VelocityGeneratorLibrary library;\r
59   private static LoggerWrapper logger = new LoggerWrapper("Generator.velocity");\r
60 \r
61   public VelocityGenerator(String aTemplate, VelocityGeneratorLibrary aLibrary) {\r
62     templateIdentifier = aTemplate;\r
63     library = aLibrary;\r
64   }\r
65 \r
66   public void generate(Object anOutputWriter, Map aValues, LoggerWrapper aLogger) throws GeneratorExc, GeneratorFailure {\r
67     Template template;\r
68     Context context = makeMapAdapter(aValues);\r
69     StringWriter stringWriter = new StringWriter();\r
70 \r
71     try {\r
72       template = library.engine.getTemplate(templateIdentifier);\r
73       if (template == null) {\r
74         throw new GeneratorExc("VelocityGeneratorLibrary: Can't find template " + templateIdentifier);\r
75       }\r
76       template.merge(context, stringWriter);\r
77 \r
78       ( (PrintWriter) anOutputWriter).print(stringWriter.toString());\r
79     }\r
80     catch (ResourceNotFoundException t) {\r
81       throw new GeneratorExc("VelocityGeneratorLibrary: Can't find template " + templateIdentifier);\r
82     }\r
83     catch (ParseErrorException t) {\r
84       ( (PrintWriter) anOutputWriter).print(t.toString());\r
85     }\r
86     catch (Throwable t) {\r
87       throw new GeneratorFailure(t);\r
88     }\r
89 \r
90   }\r
91 \r
92   private static class ContextAdapter implements Context {\r
93     public boolean containsKey(java.lang.Object key) {\r
94       return false;\r
95     }\r
96 \r
97     public Object get(java.lang.String key) {\r
98       return null;\r
99     }\r
100 \r
101     public Object[] getKeys() {\r
102       return new Object[] {};\r
103     }\r
104 \r
105     public Object put(java.lang.String key, java.lang.Object value) {\r
106       return null;\r
107     }\r
108 \r
109     public Object remove(java.lang.Object key) {\r
110       return null;\r
111     }\r
112   }\r
113 \r
114   private static Context makeMapAdapter(Map aMap)  {\r
115     return new MapAdapter(aMap);\r
116   }\r
117 \r
118   private static List makeIteratorAdapter(Iterator anIterator) {\r
119     return new IteratorAdapter(anIterator);\r
120   }\r
121 \r
122   private static List makeListAdapter(List aList) {\r
123     return new ListAdapter(aList);\r
124   }\r
125 \r
126 \r
127   private static Object makeFunctionAdapter(Generator.GeneratorFunction aFunction) {\r
128     return new FunctionAdapter(aFunction);\r
129   }\r
130 \r
131   private static Object makeBeanAdapter(Object anObject)  {\r
132     return new BeanAdapter(anObject);\r
133   }\r
134 \r
135   private interface VelocityAdapter {\r
136     public Object getOriginal();\r
137   }\r
138 \r
139   public static Object unmakeAdapter(Object anObject) {\r
140     if (anObject instanceof VelocityAdapter) {\r
141       return ((VelocityAdapter) anObject).getOriginal();\r
142     }\r
143     else\r
144       return anObject;\r
145   }\r
146 \r
147   public static Object makeAdapter(Object anObject) {\r
148     if (anObject == null)\r
149       return null;\r
150 \r
151     if (anObject instanceof Context)\r
152       return anObject;\r
153 \r
154     else if (anObject instanceof Generator.GeneratorFunction)\r
155       return makeFunctionAdapter((Generator.GeneratorFunction) anObject);\r
156     else if (anObject instanceof Integer)\r
157       return anObject;\r
158     else if (anObject instanceof Boolean)\r
159       return anObject;\r
160     else if (anObject instanceof String)\r
161       return anObject;\r
162     else if (anObject instanceof Map)\r
163       return makeMapAdapter((Map) anObject);\r
164     else if (anObject instanceof Iterator)\r
165       return makeIteratorAdapter((Iterator) anObject);\r
166     else if (anObject instanceof List)\r
167       return makeListAdapter(((List) anObject));\r
168     else if (anObject instanceof Number)\r
169       return makeAdapter(new GeneratorFormatAdapters.NumberFormatAdapter((Number) anObject));\r
170     else if (anObject instanceof Date)\r
171       return makeAdapter(new GeneratorFormatAdapters.DateFormatAdapter((Date) anObject));\r
172     else\r
173       return makeBeanAdapter(anObject);\r
174   }\r
175 \r
176   public static class FunctionAdapter implements VelocityAdapter {\r
177     private GeneratorFunction function;\r
178 \r
179     public Object getOriginal() {\r
180       return function;\r
181     }\r
182 \r
183     private FunctionAdapter(GeneratorFunction aFunction) {\r
184       function = aFunction;\r
185     }\r
186 \r
187     public Object call(Object aParameters[]) throws GeneratorExc {\r
188       List parameters = new Vector();\r
189 \r
190       for (int i = 0; i<aParameters.length; i++) {\r
191         parameters.add(unmakeAdapter(aParameters[i]));\r
192       }\r
193 \r
194       return makeAdapter(function.perform(parameters));\r
195     }\r
196 \r
197     public Object call() throws GeneratorExc {\r
198       return makeAdapter(function.perform(new Vector()));\r
199     }\r
200 \r
201     public Object call(Object anObject) throws GeneratorExc {\r
202       return call(new Object[] { anObject });\r
203     }\r
204 \r
205     public Object call(Object anObject1, Object anObject2) throws GeneratorExc {\r
206       return call(new Object[] { anObject1, anObject2 });\r
207     }\r
208 \r
209     public Object call(Object anObject1, Object anObject2, Object anObject3) throws GeneratorExc {\r
210       return call(new Object[] { anObject1, anObject2, anObject3 });\r
211     }\r
212 \r
213     public Object call(Object anObject1, Object anObject2, Object anObject3, Object anObject4) throws GeneratorExc {\r
214       return call(new Object[] { anObject1, anObject2, anObject3, anObject4 });\r
215     }\r
216   }\r
217 \r
218 \r
219   private static class MapAdapter implements Context, VelocityAdapter  {\r
220     private Map map;\r
221     private Map valuesCache;\r
222 \r
223     private MapAdapter(Map aMap) {\r
224       map = aMap;\r
225       valuesCache = new HashMap();\r
226     }\r
227 \r
228     public Object getOriginal() {\r
229       return map;\r
230     }\r
231 \r
232     public boolean containsKey(Object aKey) {\r
233       return map.containsKey(aKey);\r
234     }\r
235 \r
236     public Object get(String aKey) {\r
237       try {\r
238         if (!valuesCache.containsKey(aKey)) {\r
239           Object value = map.get(aKey);\r
240 \r
241           if (value == null && !map.containsKey(aKey)) {\r
242             return "no key "+aKey+" available";\r
243           }\r
244           else\r
245             valuesCache.put(aKey, makeAdapter(value));\r
246         }\r
247 \r
248         return valuesCache.get(aKey);\r
249       }\r
250       catch (Throwable t) {\r
251         throw new GeneratorFailure(t);\r
252       }\r
253     }\r
254 \r
255     public Object[] getKeys() {\r
256       return new Object[] {};\r
257     }\r
258 \r
259     public Object put(String aKey, Object aValue) {\r
260       valuesCache.remove(aKey);\r
261       map.put(aKey, unmakeAdapter(aValue));\r
262 \r
263       return aValue;\r
264     }\r
265 \r
266     public Object remove(java.lang.Object key) {\r
267       return null;\r
268     }\r
269   }\r
270 \r
271   private static class IteratorAdapter extends AbstractList implements VelocityAdapter  {\r
272     private Iterator iterator;\r
273     private List valuesCache;\r
274     private int position;\r
275 \r
276     private IteratorAdapter(Iterator anIterator) {\r
277       iterator = anIterator;\r
278 \r
279       valuesCache = new Vector();\r
280       position=0;\r
281 \r
282 \r
283       if (iterator instanceof RewindableIterator) {\r
284         ((RewindableIterator) iterator).rewind();\r
285       }\r
286     }\r
287 \r
288     private void getUntil(int anIndex) {\r
289       while ((anIndex==-1 || valuesCache.size()<=anIndex) && iterator.hasNext())\r
290       {\r
291         valuesCache.add(makeAdapter(iterator.next()));\r
292       }\r
293     };\r
294 \r
295     public Object getOriginal() {\r
296       return iterator;\r
297     }\r
298 \r
299     public Object get(int anIndex) {\r
300       Object result;\r
301 \r
302       getUntil(anIndex);\r
303 \r
304       if (anIndex<valuesCache.size())\r
305       {\r
306         result = valuesCache.get(anIndex);\r
307 \r
308         return result;\r
309       }\r
310       else\r
311         throw new RuntimeException( "Iterator out of bounds" );\r
312     }\r
313 \r
314     public int size() {\r
315       getUntil(-1);\r
316       return valuesCache.size();\r
317     }\r
318 \r
319   }\r
320 \r
321   private static class ListAdapter extends AbstractList implements VelocityAdapter  {\r
322     private List list;\r
323     private List valuesCache;\r
324     private int position;\r
325 \r
326     private ListAdapter(List aList) {\r
327       list = aList;\r
328 \r
329       valuesCache = new Vector();\r
330       position=0;\r
331     }\r
332 \r
333     private void getUntil(int anIndex) {\r
334       while ((anIndex==-1 || valuesCache.size()<=anIndex) && valuesCache.size()<list.size())\r
335       {\r
336         valuesCache.add(makeAdapter(list.get(valuesCache.size())));\r
337       }\r
338     };\r
339 \r
340     public Object getOriginal() {\r
341       return list;\r
342     }\r
343 \r
344     public Object get(int anIndex) {\r
345       Object result;\r
346 \r
347       getUntil(anIndex);\r
348 \r
349       if (anIndex<valuesCache.size())\r
350       {\r
351         result = valuesCache.get(anIndex);\r
352 \r
353         return result;\r
354       }\r
355       else\r
356         throw new RuntimeException( "Iterator out of bounds" );\r
357     }\r
358 \r
359     public int size() {\r
360       return list.size();\r
361     }\r
362 \r
363   }\r
364 \r
365 /*\r
366   private static class FunctionAdapter implements TemplateMethodModel {\r
367     private Generator.GeneratorFunction function;\r
368 \r
369     public FunctionAdapter(Generator.GeneratorFunction aFunction) {\r
370       function = aFunction;\r
371     }\r
372 \r
373     public TemplateModel exec(List anArguments) throws TemplateModelException {\r
374       try {\r
375         return makeAdapter(function.perform(anArguments));\r
376       }\r
377       catch (Throwable t) {\r
378         throw new TemplateModelException(t.getMessage());\r
379       }\r
380     }\r
381 \r
382     public boolean isEmpty() {\r
383       return false;\r
384     }\r
385 \r
386   }\r
387 \r
388 */\r
389 \r
390   private static class BeanAdapter implements Context, VelocityAdapter {\r
391     private Object object;\r
392 \r
393     public BeanAdapter(Object anObject) {\r
394       object = anObject;\r
395     }\r
396 \r
397     public boolean containsKey(Object key) {\r
398       return true;\r
399     }\r
400 \r
401     public Object getOriginal() {\r
402       return object;\r
403     }\r
404 \r
405     public Object get(String aKey) {\r
406       try {\r
407         if (PropertyUtils.isReadable(object, aKey))\r
408           return makeAdapter(PropertyUtils.getSimpleProperty(object, aKey));\r
409         else\r
410           return makeAdapter(MethodUtils.invokeExactMethod(object, "get", aKey));\r
411       }\r
412       catch (Throwable t) {\r
413         throw new GeneratorFailure(t);\r
414       }\r
415     }\r
416 \r
417     public Object[] getKeys() {\r
418       return new Object[] {};\r
419     }\r
420 \r
421     public Object put(String aKey, Object aValue) {\r
422       try {\r
423         if (PropertyUtils.isWriteable(object, aKey))\r
424           PropertyUtils.setSimpleProperty(object, aKey, unmakeAdapter(aValue));\r
425         else\r
426           MethodUtils.invokeExactMethod(object, "set", new Object[] {aKey, unmakeAdapter(aValue)});\r
427 \r
428         return this;\r
429       }\r
430       catch (Throwable t) {\r
431         throw new GeneratorFailure(t);\r
432       }\r
433     }\r
434 \r
435     public Object remove(Object aKey) {\r
436       throw new RuntimeException("BeanAdapter.remove not supported");\r
437     }\r
438   }\r
439 \r
440   private static class VelocityLoggerWrapper implements LogSystem {\r
441     private LoggerWrapper logger;\r
442 \r
443     public VelocityLoggerWrapper(LoggerWrapper aLogger) {\r
444       logger = aLogger;\r
445     }\r
446 \r
447     public void init(RuntimeServices aRuntimeServices) {\r
448     }\r
449 \r
450     public void logVelocityMessage(int aLevel, String aMessage) {\r
451       switch (aLevel) {\r
452         case DEBUG_ID:\r
453           logger.debug(aMessage);\r
454           break;\r
455         case ERROR_ID:\r
456           logger.error(aMessage);\r
457           break;\r
458         case INFO_ID:\r
459           logger.info(aMessage);\r
460           break;\r
461         default:\r
462           logger.warn(aMessage);\r
463           break;\r
464       }\r
465     }\r
466   }\r
467 \r
468   public static class VelocityGeneratorLibrary implements GeneratorLibrary {\r
469     private VelocityEngine engine;\r
470 \r
471     public VelocityGeneratorLibrary(String aTemplateRoot) throws GeneratorExc, GeneratorFailure {\r
472       try {\r
473         engine = new VelocityEngine();\r
474         try {\r
475           engine.setProperty(VelocityEngine.RESOURCE_LOADER, "file");\r
476         }\r
477         catch (Throwable t) {\r
478           logger.error(VelocityEngine.RESOURCE_LOADER);\r
479         }\r
480 \r
481         try {\r
482           engine.setProperty("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.FileResourceLoader");\r
483         }\r
484         catch (Throwable t) {\r
485           logger.error("file.resource.loader.class");\r
486         }\r
487 \r
488         try {\r
489           engine.setProperty("file.resource.loader.path", aTemplateRoot);\r
490         }\r
491         catch (Throwable t) {\r
492           logger.error("file.resource.loader.path");\r
493 \r
494         }\r
495         try {\r
496           engine.setProperty("file.resource.loader.cache", "true");\r
497         }\r
498         catch (Throwable t) {\r
499           logger.error("file.resource.loader.cache");\r
500 \r
501         }\r
502         try {\r
503           engine.setProperty("file.resource.loader.modificationCheckInterval", "10");\r
504         }\r
505         catch (Throwable t) {\r
506           logger.error("file.resource.loader.modificationCheckInterval");\r
507 \r
508         }\r
509 \r
510         try {\r
511           engine.setProperty(VelocityEngine.RUNTIME_LOG_LOGSYSTEM, new VelocityLoggerWrapper(logger));\r
512         }\r
513         catch (Throwable t) {\r
514           logger.error(VelocityEngine.RUNTIME_LOG_LOGSYSTEM);\r
515 \r
516         }\r
517 /*        try {\r
518           engine.setProperty(VelocityEngine.RUNTIME_LOG_LOGSYSTEM_CLASS, null);\r
519         }\r
520         catch (Throwable t) {\r
521           logger.error(VelocityEngine.RUNTIME_LOG_LOGSYSTEM_CLASS);\r
522 \r
523         }\r
524 */\r
525 \r
526         engine.init();\r
527       }\r
528       catch (Throwable t) {\r
529         t.printStackTrace(logger.asPrintWriter(logger.ERROR_MESSAGE));\r
530 \r
531         logger.error("Failed to set up a VelocityGeneratorLibrary: " + t.toString());\r
532         throw new GeneratorFailure(t);\r
533       }\r
534     }\r
535 \r
536     public Generator makeGenerator(String anIdentifier) throws GeneratorExc, GeneratorFailure {\r
537       return new VelocityGenerator(anIdentifier, this);\r
538     }\r
539   }\r
540 \r
541   public static class VelocityGeneratorLibraryFactory implements GeneratorLibraryFactory {\r
542     private String basePath;\r
543 \r
544     public VelocityGeneratorLibraryFactory(String aBasePath) {\r
545       basePath = aBasePath;\r
546     }\r
547 \r
548     public GeneratorLibrary makeLibrary(String anInitializationString) throws GeneratorExc, GeneratorFailure {\r
549       return new VelocityGeneratorLibrary(basePath+anInitializationString);\r
550     };\r
551   }\r
552 }\r