1 package org.andromda.templateengines.velocity;
2
3 import java.io.File;
4 import java.io.IOException;
5 import java.io.InputStream;
6 import java.io.StringWriter;
7 import java.io.Writer;
8 import java.net.URL;
9 import java.util.ArrayList;
10 import java.util.Collection;
11 import java.util.HashMap;
12 import java.util.Iterator;
13 import java.util.List;
14 import java.util.Map;
15 import java.util.Properties;
16
17 import org.andromda.core.common.AndroMDALogger;
18 import org.andromda.core.common.Constants;
19 import org.andromda.core.common.ExceptionUtils;
20 import org.andromda.core.common.Merger;
21 import org.andromda.core.common.ResourceUtils;
22 import org.andromda.core.common.ResourceWriter;
23 import org.andromda.core.templateengine.TemplateEngine;
24 import org.andromda.core.templateengine.TemplateEngineException;
25 import org.apache.commons.collections.ExtendedProperties;
26 import org.apache.commons.lang.StringUtils;
27 import org.apache.log4j.FileAppender;
28 import org.apache.log4j.Logger;
29 import org.apache.log4j.PatternLayout;
30 import org.apache.velocity.Template;
31 import org.apache.velocity.VelocityContext;
32 import org.apache.velocity.app.VelocityEngine;
33 import org.apache.velocity.runtime.RuntimeServices;
34 import org.apache.velocity.runtime.log.LogSystem;
35 import org.apache.velocity.runtime.resource.loader.FileResourceLoader;
36
37
38 /***
39 * The TemplateEngine implementation for VelocityTemplateEngine template processor.
40 *
41 * @author <a href="http://www.mbohlen.de">Matthias Bohlen </a>
42 * @author Chad Brandon
43 * @see http://jakarta.apache.org/velocity/
44 */
45 public class VelocityTemplateEngine
46 implements TemplateEngine
47 {
48 protected static Logger logger = null;
49
50 /***
51 * The directory we look in to find velocity properties.
52 */
53 private static final String PROPERTIES_DIR = "META-INF/";
54
55 /***
56 * The suffix for the the velocity properties.
57 */
58 private static final String PROPERTIES_SUFFIX = "-velocity.properties";
59
60 /***
61 * The location to which temporary templates are written
62 */
63 private static final String TEMPORARY_TEMPLATE_LOCATION = Constants.TEMPORARY_DIRECTORY + "velocity/merged";
64
65 /***
66 * The location of external templates
67 */
68 private String mergeLocation;
69
70 /***
71 * Stores additional properties specified within the plugin within the file META-INF/'plugin
72 * name'-velocity.properties
73 */
74 private Properties properties = null;
75
76 /***
77 * The current namespace this template engine is running within.
78 */
79 private String namespace;
80
81 /***
82 * the VelocityEngine instance to use
83 */
84 private VelocityEngine velocityEngine;
85 private VelocityContext velocityContext;
86 private final List macroLibraries = new ArrayList();
87
88 /***
89 * Stores a collection of templates that have already been
90 * discovered by the velocity engine
91 */
92 private final Map discoveredTemplates = new HashMap();
93
94 /***
95 * Stores the merged template files that are deleted at shutdown.
96 */
97 private final Collection mergedTemplateFiles = new ArrayList();
98
99 /***
100 * @see org.andromda.core.templateengine.TemplateEngine#init(java.lang.String)
101 */
102 public void initialize(final String namespace)
103 throws Exception
104 {
105 this.namespace = namespace;
106 this.initLogger(namespace);
107
108 ExtendedProperties engineProperties = new ExtendedProperties();
109
110
111
112
113
114
115 engineProperties.setProperty(VelocityEngine.RESOURCE_LOADER, "file,classpath");
116
117 engineProperties.setProperty(
118 "file." + VelocityEngine.RESOURCE_LOADER + ".class",
119 FileResourceLoader.class.getName());
120
121 engineProperties.setProperty(
122 "classpath." + VelocityEngine.RESOURCE_LOADER + ".class",
123 ClasspathResourceLoader.class.getName());
124
125
126
127 engineProperties.setProperty(
128 VelocityEngine.RUNTIME_LOG_LOGSYSTEM,
129 new VelocityLoggingReceiver());
130
131
132 for (final Iterator iterator = getMacroLibraries().iterator(); iterator.hasNext();)
133 {
134 engineProperties.addProperty(
135 VelocityEngine.VM_LIBRARY,
136 iterator.next());
137 }
138
139 this.velocityEngine = new VelocityEngine();
140 this.velocityEngine.setExtendedProperties(engineProperties);
141
142 if (this.mergeLocation != null)
143 {
144
145 velocityEngine.addProperty(VelocityEngine.FILE_RESOURCE_LOADER_PATH, this.mergeLocation);
146 }
147
148
149
150 if (Merger.instance().requiresMerge(this.namespace))
151 {
152 velocityEngine.addProperty(
153 VelocityEngine.FILE_RESOURCE_LOADER_PATH,
154 this.getMergedTemplatesLocation());
155 }
156
157 this.addProperties(namespace);
158 this.velocityEngine.init();
159 }
160
161 /***
162 * Adds any properties found within META-INF/'plugin name'-velocity.properties
163 */
164 private void addProperties(String pluginName)
165 throws IOException
166 {
167
168 this.properties = null;
169
170
171
172 URL propertiesUri =
173 ResourceUtils.getResource(PROPERTIES_DIR + StringUtils.trimToEmpty(pluginName) + PROPERTIES_SUFFIX);
174
175 if (propertiesUri != null)
176 {
177 if (logger.isDebugEnabled())
178 {
179 logger.debug("loading properties from --> '" + propertiesUri + "'");
180 }
181
182 this.properties = new Properties();
183 this.properties.load(propertiesUri.openStream());
184
185 for (final Iterator iterator = this.properties.keySet().iterator(); iterator.hasNext();)
186 {
187 final String property = (String)iterator.next();
188 final String value = this.properties.getProperty(property);
189 if (logger.isDebugEnabled())
190 {
191 logger.debug("setting property '" + property + "' with --> '" + value + "'");
192 }
193 this.velocityEngine.setProperty(property, value);
194 }
195 }
196 }
197
198 /***
199 * @see org.andromda.core.templateengine.TemplateEngine#processTemplate(java.lang.String, java.util.Map,
200 * java.io.StringWriter)
201 */
202 public void processTemplate(
203 final String templateFile,
204 final Map templateObjects,
205 final Writer output)
206 throws Exception
207 {
208 final String methodName = "VelocityTemplateEngine.processTemplate";
209
210 if (logger.isDebugEnabled())
211 {
212 logger.debug(
213 "performing " + methodName + " with templateFile '" + templateFile + "' and templateObjects '" +
214 templateObjects + "'");
215 }
216 ExceptionUtils.checkEmpty("templateFile", templateFile);
217 ExceptionUtils.checkNull("output", output);
218 this.velocityContext = new VelocityContext();
219 this.loadVelocityContext(templateObjects);
220
221 Template template = (Template)this.discoveredTemplates.get(templateFile);
222 if (template == null)
223 {
224 template = this.velocityEngine.getTemplate(templateFile);
225
226
227 final Merger merger = Merger.instance();
228 if (merger.requiresMerge(this.namespace))
229 {
230 final String mergedTemplateLocation = this.getMergedTemplateLocation(templateFile);
231 final InputStream resource = template.getResourceLoader().getResourceStream(templateFile);
232 ResourceWriter.instance().writeStringToFile(
233 merger.getMergedString(resource, this.namespace),
234 mergedTemplateLocation);
235 template = this.velocityEngine.getTemplate(templateFile);
236 this.mergedTemplateFiles.add(new File(mergedTemplateLocation));
237 }
238 this.discoveredTemplates.put(templateFile, template);
239 }
240 template.merge(velocityContext, output);
241 }
242
243 /***
244 * Loads the internal {@link #velocityContext} from the
245 * given Map of template objects.
246 *
247 * @param a Map containing objects to add to the template context.
248 */
249 private final void loadVelocityContext(final Map templateObjects)
250 {
251 if (templateObjects != null && !templateObjects.isEmpty())
252 {
253
254 if (templateObjects != null)
255 {
256 for (final Iterator namesIterator = templateObjects.keySet().iterator(); namesIterator.hasNext();)
257 {
258 final String name = (String)namesIterator.next();
259 final Object value = templateObjects.get(name);
260 this.velocityContext.put(name, value);
261 }
262 }
263 }
264 }
265
266 /***
267 * Gets location to which the given <code>templateName</code>
268 * has its merged output written.
269 * @param templatePath the relative path to the template.
270 * @return the complete merged template location.
271 */
272 private String getMergedTemplateLocation(String templatePath)
273 {
274 return this.getMergedTemplatesLocation() + "/" + templatePath;
275 }
276
277 /***
278 * Gets the location to which merge templates are written. These
279 * must be written in order to replace the unmerged ones when Velocity
280 * performs its template search.
281 *
282 * @return the merged templates location.
283 */
284 private String getMergedTemplatesLocation()
285 {
286 return TEMPORARY_TEMPLATE_LOCATION + "/" + this.namespace;
287 }
288
289 /***
290 * The log tag used for evaluation (this can be any abitrary name).
291 */
292 private static final String LOG_TAG = "logtag";
293
294 /***
295 * @see org.andromda.core.templateengine.TemplateEngine#getEvaluatedExpression(java.lang.String, java.util.Map)
296 */
297 public String getEvaluatedExpression(final String expression, final Map templateObjects)
298 {
299 String evaluatedExpression = null;
300 if (StringUtils.isNotEmpty(expression) && templateObjects != null && !templateObjects.isEmpty())
301 {
302 if (this.velocityContext == null)
303 {
304 this.velocityContext = new VelocityContext();
305 this.loadVelocityContext(templateObjects);
306 }
307 try
308 {
309 final StringWriter writer = new StringWriter();
310 this.velocityEngine.evaluate(this.velocityContext, writer, LOG_TAG, expression);
311 evaluatedExpression = writer.toString();
312 }
313 catch (final Throwable throwable)
314 {
315 throw new TemplateEngineException(throwable);
316 }
317 }
318 return evaluatedExpression;
319 }
320
321 /***
322 * @see org.andromda.core.templateengine.TemplateEngine#getMacroLibraries()
323 */
324 public List getMacroLibraries()
325 {
326 return this.macroLibraries;
327 }
328
329 /***
330 * @see org.andromda.core.templateengine.TemplateEngine#addMacroLibrary(java.lang.String)
331 */
332 public void addMacroLibrary(String libraryName)
333 {
334 this.macroLibraries.add(libraryName);
335 }
336
337 /***
338 * @see org.andromda.core.templateengine.TemplateEngine#setMergeLocation(java.lang.String)
339 */
340 public void setMergeLocation(String mergeLocation)
341 {
342 this.mergeLocation = mergeLocation;
343 }
344
345 /***
346 * @see org.andromda.core.templateengine.TemplateEngine#shutdown()
347 */
348 public void shutdown()
349 {
350 this.deleteMergedTemplatesLocation();
351 this.discoveredTemplates.clear();
352 this.velocityEngine = null;
353 }
354
355 /***
356 * Deletes the merged templates location (these
357 * are the templates that were created just for merging
358 * purposes and so therefore are no longer needed after
359 * the engine is shutdown).
360 */
361 private final void deleteMergedTemplatesLocation()
362 {
363 File directory = new File(TEMPORARY_TEMPLATE_LOCATION);
364 if (directory.getParentFile().isDirectory())
365 {
366 directory = directory.getParentFile();
367 ResourceUtils.deleteDirectory(directory);
368 directory.delete();
369 }
370 }
371
372 /***
373 * Opens a log file for this namespace.
374 *
375 * @throws IOException if the file cannot be opened
376 */
377 private final void initLogger(final String pluginName)
378 throws IOException
379 {
380 logger = AndroMDALogger.getNamespaceLogger(pluginName);
381 logger.setAdditivity(false);
382
383 final FileAppender appender =
384 new FileAppender(
385 new PatternLayout("%-5p %d - %m%n"),
386 AndroMDALogger.getNamespaceLogFileName(pluginName),
387 true);
388 logger.addAppender(appender);
389 }
390
391 /***
392 * <p/>
393 * This class receives log messages from VelocityTemplateEngine and forwards them to the concrete logger that is
394 * configured for this cartridge. </p>
395 * <p/>
396 * This avoids creation of one large VelocityTemplateEngine log file where errors are difficult to find and track.
397 * </p>
398 * <p/>
399 * Error messages can now be traced to plugin activities. </p>
400 */
401 private static class VelocityLoggingReceiver
402 implements LogSystem
403 {
404 /***
405 * @see org.apache.velocity.runtime.log.LogSystem#init(org.apache.velocity.runtime.RuntimeServices)
406 */
407 public void init(RuntimeServices services)
408 throws Exception
409 {
410 }
411
412 /***
413 * @see org.apache.velocity.runtime.log.LogSystem#logVelocityMessage(int, java.lang.String)
414 */
415 public void logVelocityMessage(
416 int level,
417 String message)
418 {
419 switch (level)
420 {
421 case LogSystem.WARN_ID:
422 logger.info(message);
423 break;
424 case LogSystem.INFO_ID:
425 logger.info(message);
426 break;
427 case LogSystem.DEBUG_ID:
428 logger.debug(message);
429 break;
430 case LogSystem.ERROR_ID:
431 logger.info(message);
432 break;
433 default:
434 logger.debug(message);
435 break;
436 }
437 }
438 }
439 }