1 package org.apache.torque.task;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.ByteArrayInputStream;
23 import java.io.ByteArrayOutputStream;
24 import java.io.File;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.io.InputStreamReader;
28 import java.io.LineNumberReader;
29 import java.io.PrintStream;
30 import java.io.UnsupportedEncodingException;
31 import java.io.Writer;
32 import java.util.ArrayList;
33 import java.util.Date;
34 import java.util.Hashtable;
35 import java.util.Iterator;
36 import java.util.List;
37 import java.util.Map;
38
39 import org.apache.commons.lang.StringUtils;
40 import org.apache.texen.Generator;
41 import org.apache.texen.ant.TexenTask;
42 import org.apache.tools.ant.BuildException;
43 import org.apache.tools.ant.DirectoryScanner;
44 import org.apache.tools.ant.types.FileSet;
45 import org.apache.torque.engine.EngineException;
46 import org.apache.torque.engine.database.model.Database;
47 import org.apache.torque.engine.database.transform.XmlToAppData;
48 import org.apache.velocity.VelocityContext;
49 import org.apache.velocity.app.VelocityEngine;
50 import org.apache.velocity.context.Context;
51 import org.apache.velocity.exception.MethodInvocationException;
52 import org.apache.velocity.exception.ParseErrorException;
53 import org.apache.velocity.exception.ResourceNotFoundException;
54 import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
55 import org.apache.velocity.runtime.resource.loader.FileResourceLoader;
56
57 /***
58 * A base torque task that uses either a single XML schema
59 * representing a data model, or a <fileset> of XML schemas.
60 * We are making the assumption that an XML schema representing
61 * a data model contains tables for a <strong>single</strong>
62 * database.
63 *
64 * @author <a href="mailto:jvanzyl@zenplex.com">Jason van Zyl</a>
65 * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
66 */
67 public class TorqueDataModelTask extends TexenTask
68 {
69 /***
70 * XML that describes the database model, this is transformed
71 * into the application model object.
72 */
73 protected String xmlFile;
74
75 /*** Fileset of XML schemas which represent our data models. */
76 protected List filesets = new ArrayList();
77
78 /*** Data models that we collect. One from each XML schema file. */
79 protected List dataModels = new ArrayList();
80
81 /*** Velocity context which exposes our objects in the templates. */
82 protected Context context;
83
84 /***
85 * Map of data model name to database name.
86 * Should probably stick to the convention of them being the same but
87 * I know right now in a lot of cases they won't be.
88 */
89 protected Hashtable dataModelDbMap;
90
91 /***
92 * Hashtable containing the names of all the databases
93 * in our collection of schemas.
94 */
95 protected Hashtable databaseNames;
96
97
98
99
100
101
102 /***
103 * Name of the properties file that maps an SQL file
104 * to a particular database.
105 */
106 protected String sqldbmap;
107
108 /*** The target database(s) we are generating SQL for. */
109 private String targetDatabase;
110
111 /**</package-summary/html">Target Java package to place the generated files in/ *//package-summary.html">em>* Target Java package to place the generated files in. */
112 private String targetPackage;
113
114
115 /***
116 * Set the sqldbmap.
117 *
118 * @param sqldbmap th db map
119 */
120 public void setSqlDbMap(String sqldbmap)
121 {
122
123 this.sqldbmap = getProject().resolveFile(sqldbmap).toString();
124 }
125
126 /***
127 * Get the sqldbmap.
128 *
129 * @return String sqldbmap.
130 */
131 public String getSqlDbMap()
132 {
133 return sqldbmap;
134 }
135
136 /***
137 * Return the data models that have been processed.
138 *
139 * @return List data models
140 */
141 public List getDataModels()
142 {
143 return dataModels;
144 }
145
146 /***
147 * Return the data model to database name map.
148 *
149 * @return Hashtable data model name to database name map.
150 */
151 public Hashtable getDataModelDbMap()
152 {
153 return dataModelDbMap;
154 }
155
156 /***
157 * Get the xml schema describing the application model.
158 *
159 * @return String xml schema file.
160 */
161 public String getXmlFile()
162 {
163 return xmlFile;
164 }
165
166 /***
167 * Set the xml schema describing the application model.
168 *
169 * @param xmlFile The new XmlFile value
170 */
171 public void setXmlFile(String xmlFile)
172 {
173 this.xmlFile = getProject().resolveFile(xmlFile).toString();
174 }
175
176 /***
177 * Adds a set of xml schema files (nested fileset attribute).
178 *
179 * @param set a Set of xml schema files
180 */
181 public void addFileset(FileSet set)
182 {
183 filesets.add(set);
184 }
185
186 /***
187 * Get the current target database.
188 *
189 * @return String target database(s)
190 */
191 public String getTargetDatabase()
192 {
193 return targetDatabase;
194 }
195
196 /***
197 * Set the current target database. (e.g. mysql, oracle, ..)
198 *
199 * @param v target database(s)
200 */
201 public void setTargetDatabase(String v)
202 {
203 targetDatabase = v;
204 }
205
206 /***
207 * Get the current target package.
208 *
209 * @return return target java package.
210 */
211 public String getTargetPackage()
212 {
213 return targetPackage;
214 }
215
216 /***
217 * Set the current target package. This is where generated java classes will
218 * live.
219 *
220 * @param v target java package.
221 */
222 public void setTargetPackage(String v)
223 {
224 targetPackage = v;
225 }
226
227 /***
228 * Set up the initial context for generating the SQL from the XML schema.
229 *
230 * @return the context
231 * @throws Exception
232 */
233 public Context initControlContext() throws Exception
234 {
235 XmlToAppData xmlParser;
236
237 if (xmlFile == null && filesets.isEmpty())
238 {
239 throw new BuildException("You must specify an XML schema or "
240 + "fileset of XML schemas!");
241 }
242
243 try
244 {
245 if (xmlFile != null)
246 {
247
248
249 xmlParser = new XmlToAppData(getTargetDatabase(),
250 getTargetPackage());
251 Database ad = xmlParser.parseFile(xmlFile);
252 ad.setFileName(grokName(xmlFile));
253 dataModels.add(ad);
254 }
255 else
256 {
257
258 for (int i = 0; i < filesets.size(); i++)
259 {
260 FileSet fs = (FileSet) filesets.get(i);
261 DirectoryScanner ds = fs.getDirectoryScanner(getProject());
262 File srcDir = fs.getDir(getProject());
263
264 String[] dataModelFiles = ds.getIncludedFiles();
265
266
267 for (int j = 0; j < dataModelFiles.length; j++)
268 {
269 File f = new File(srcDir, dataModelFiles[j]);
270 xmlParser = new XmlToAppData(getTargetDatabase(),
271 getTargetPackage());
272 Database ad = xmlParser.parseFile(f.toString());
273 ad.setFileName(grokName(f.toString()));
274 dataModels.add(ad);
275 }
276 }
277 }
278
279 Iterator i = dataModels.iterator();
280 databaseNames = new Hashtable();
281 dataModelDbMap = new Hashtable();
282
283
284
285 while (i.hasNext())
286 {
287 Database database = (Database) i.next();
288 databaseNames.put(database.getName(), database.getName());
289 dataModelDbMap.put(database.getFileName(), database.getName());
290 }
291 }
292 catch (EngineException ee)
293 {
294 throw new BuildException(ee);
295 }
296
297 context = new VelocityContext();
298
299
300
301 context.put("dataModels", dataModels);
302 context.put("databaseNames", databaseNames);
303 context.put("targetDatabase", targetDatabase);
304 context.put("targetPackage", targetPackage);
305
306 return context;
307 }
308
309 /***
310 * Change type of "now" to java.util.Date
311 *
312 * @see org.apache.texen.ant.TexenTask#populateInitialContext(org.apache.velocity.context.Context)
313 */
314 protected void populateInitialContext(Context context) throws Exception
315 {
316 super.populateInitialContext(context);
317 context.put("now", new Date());
318 }
319
320 /***
321 * Gets a name to use for the application's data model.
322 *
323 * @param xmlFile The path to the XML file housing the data model.
324 * @return The name to use for the <code>AppData</code>.
325 */
326 private String grokName(String xmlFile)
327 {
328
329
330
331
332 String name = "data-model";
333 int i = xmlFile.lastIndexOf(System.getProperty("file.separator"));
334 if (i != -1)
335 {
336
337 i++;
338
339 int j = xmlFile.lastIndexOf('.');
340 if (i < j)
341 {
342 name = xmlFile.substring(i, j);
343 }
344 else
345 {
346
347 name = xmlFile.substring(i);
348 }
349 }
350 return name;
351 }
352
353 /***
354 * Override Texen's context properties to map the
355 * torque.xxx properties (including defaults set by the
356 * org/apache/torque/defaults.properties) to just xxx.
357 *
358 * <p>
359 * Also, move xxx.yyy properties to xxxYyy as Velocity
360 * doesn't like the xxx.yyy syntax.
361 * </p>
362 *
363 * @param file the file to read the properties from
364 */
365 public void setContextProperties(String file)
366 {
367 super.setContextProperties(file);
368
369
370 Hashtable env = super.getProject().getProperties();
371 for (Iterator i = env.entrySet().iterator(); i.hasNext();)
372 {
373 Map.Entry entry = (Map.Entry) i.next();
374 String key = (String) entry.getKey();
375 if (key.startsWith("torque."))
376 {
377 String newKey = key.substring("torque.".length());
378 int j = newKey.indexOf(".");
379 while (j != -1)
380 {
381 newKey =
382 newKey.substring(0, j)
383 + StringUtils.capitalize(newKey.substring(j + 1));
384 j = newKey.indexOf(".");
385 }
386
387 contextProperties.setProperty(newKey, entry.getValue());
388 }
389 }
390 }
391 /***
392 * This message fragment (telling users to consult the log or
393 * invoke ant with the -debug flag) is appended to rethrown
394 * exception messages.
395 */
396 private final static String ERR_MSG_FRAGMENT =
397 ". For more information consult the velocity log, or invoke ant " +
398 "with the -debug flag.";
399
400 /***
401 * This method creates an VelocityEngine instance, parses
402 * every template and creates the corresponding output.
403 *
404 * Unfortunately the TextenTask.execute() method makes
405 * everything for us but we just want to set our own
406 * VelocityTemplateLoader.
407 * TODO: change once TEXEN-14 is resolved and out.
408 *
409 * @see org.apache.texen.ant.TexenTask#execute()
410 */
411 public void execute() throws BuildException
412 {
413
414 if (templatePath == null && useClasspath == false)
415 {
416 throw new BuildException(
417 "The template path needs to be defined if you are not using "
418 + "the classpath for locating templates!");
419 }
420
421
422 if (controlTemplate == null)
423 {
424 throw new BuildException(
425 "The control template needs to be defined!");
426 }
427
428
429 if (outputDirectory == null)
430 {
431 throw new BuildException(
432 "The output directory needs to be defined!");
433 }
434
435
436 if (outputFile == null)
437 {
438 throw new BuildException("The output file needs to be defined!");
439 }
440
441 VelocityEngine ve = new VelocityEngine();
442
443 try
444 {
445
446 if (templatePath != null)
447 {
448 log("Using templatePath: " + templatePath, project.MSG_VERBOSE);
449 ve.setProperty("torque" + VelocityEngine.FILE_RESOURCE_LOADER_PATH,
450 templatePath);
451
452
453 ve.addProperty(VelocityEngine.RESOURCE_LOADER, "torquefile");
454 ve.setProperty("torquefile." + VelocityEngine.RESOURCE_LOADER
455 + ".instance",
456 new TorqueFileResourceLoader(this));
457 }
458
459 if (useClasspath)
460 {
461 log("Using classpath");
462
463 ve.addProperty(VelocityEngine.RESOURCE_LOADER, "classpath");
464
465 ve.setProperty("classpath." + VelocityEngine.RESOURCE_LOADER
466 + ".instance", new TorqueClasspathResourceLoader(this));
467
468 ve.setProperty("classpath." + VelocityEngine.RESOURCE_LOADER
469 + ".cache", "false");
470
471 ve.setProperty("classpath." + VelocityEngine.RESOURCE_LOADER
472 + ".modificationCheckInterval", "2");
473 }
474
475 ve.init();
476
477
478 Generator generator = Generator.getInstance();
479 generator.setVelocityEngine(ve);
480 generator.setOutputPath(outputDirectory);
481 generator.setInputEncoding(inputEncoding);
482 generator.setOutputEncoding(outputEncoding);
483
484 if (templatePath != null)
485 {
486 generator.setTemplatePath(templatePath);
487 }
488
489
490
491 File file = new File(outputDirectory);
492 if (!file.exists())
493 {
494 file.mkdirs();
495 }
496
497 String path = outputDirectory + File.separator + outputFile;
498 log("Generating to file " + path, project.MSG_INFO);
499 Writer writer = generator.getWriter(path, outputEncoding);
500
501
502
503
504 Context c = initControlContext();
505
506
507
508
509
510
511 populateInitialContext(c);
512
513
514
515
516 if (contextProperties != null)
517 {
518 Iterator i = contextProperties.getKeys();
519
520 while (i.hasNext())
521 {
522 String property = (String) i.next();
523 String value = contextProperties.getString(property);
524
525
526
527
528 try
529 {
530 c.put(property, new Integer(value));
531 } catch (NumberFormatException nfe)
532 {
533
534
535
536 String booleanString = contextProperties
537 .testBoolean(value);
538
539 if (booleanString != null)
540 {
541 c.put(property, new Boolean(booleanString));
542 } else
543 {
544
545
546
547
548
549
550
551
552
553
554
555
556
557 if (property.endsWith("file.contents"))
558 {
559
560
561
562 value = org.apache.velocity.util.StringUtils
563 .fileContentsToString(project
564 .resolveFile(value)
565 .getCanonicalPath());
566
567 property = property.substring(0, property
568 .indexOf("file.contents") - 1);
569 }
570
571 c.put(property, value);
572 }
573 }
574 }
575 }
576
577 writer.write(generator.parse(controlTemplate, c));
578 writer.flush();
579 writer.close();
580 generator.shutdown();
581 cleanup();
582 }
583 catch (BuildException e)
584 {
585 throw e;
586 }
587 catch (MethodInvocationException e)
588 {
589 throw new BuildException("Exception thrown by '"
590 + e.getReferenceName() + "." + e.getMethodName() + "'"
591 + ERR_MSG_FRAGMENT, e.getWrappedThrowable());
592 }
593 catch (ParseErrorException e)
594 {
595 throw new BuildException(
596 "Velocity syntax error" + ERR_MSG_FRAGMENT, e);
597 }
598 catch (ResourceNotFoundException e)
599 {
600 throw new BuildException(
601 "Resource not found" + ERR_MSG_FRAGMENT,
602 e);
603 }
604 catch (Exception e)
605 {
606 throw new BuildException(
607 "Generation failed" + ERR_MSG_FRAGMENT,
608 e);
609 }
610 }
611
612 /***
613 * This method filters the template and replaces some
614 * unwanted characters. For example it removes leading
615 * spaces in front of velocity commands and replaces
616 * tabs with spaces to prevent bounces in different
617 * code editors with different tab-width-setting.
618 *
619 * @param resource the input stream to filter
620 *
621 * @return the filtered input stream.
622 *
623 * @throws IOException if creating, reading or writing to a stream fails.
624 */
625 protected InputStream filter(InputStream resource) throws IOException
626 {
627 InputStreamReader streamReader;
628 if (inputEncoding != null)
629 {
630 streamReader = new InputStreamReader(resource, inputEncoding);
631 }
632 else
633 {
634 streamReader = new InputStreamReader(resource);
635 }
636 LineNumberReader lineNumberReader = new LineNumberReader(streamReader);
637 String line = null;
638 ByteArrayOutputStream baos = new ByteArrayOutputStream();
639 PrintStream ps = null;
640 if (inputEncoding != null)
641 {
642 ps = new PrintStream(baos, true, inputEncoding);
643 }
644 else
645 {
646 ps = new PrintStream(baos, true);
647 }
648
649 while ((line = lineNumberReader.readLine()) != null)
650 {
651
652 line = line.replaceAll("^//s*#", "#");
653
654 line = line.replaceAll("\t", " ");
655 ps.println(line);
656 }
657 ps.flush();
658 ps.close();
659
660 return new ByteArrayInputStream(baos.toByteArray());
661 }
662
663
664 /***
665 * A custom classpath resource loader which filters tabs and removes spaces
666 * from lines with velocity commands.
667 */
668 public static class TorqueClasspathResourceLoader
669 extends ClasspathResourceLoader
670 {
671 /***
672 * The task in which this resource loader is used.
673 */
674 private TorqueDataModelTask task;
675
676 /***
677 * Constructor.
678 *
679 * @param task the task in which this resource loader is used.
680 */
681 public TorqueClasspathResourceLoader(TorqueDataModelTask task)
682 {
683 this.task = task;
684 }
685
686 /***
687 * @see org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader#getResourceStream(java.lang.String)
688 */
689 public synchronized InputStream getResourceStream(String name)
690 throws ResourceNotFoundException
691 {
692 InputStream source = null;
693 try
694 {
695 source = super.getResourceStream(name);
696 return task.filter(source);
697 }
698 catch (IOException uee)
699 {
700 task.log(uee.getMessage());
701 throw new ResourceNotFoundException(uee.getMessage());
702 }
703 finally
704 {
705 if (source != null)
706 {
707 try
708 {
709 source.close();
710 }
711 catch (IOException e)
712 {
713 task.log(e.getMessage());
714 }
715 }
716 }
717 }
718 }
719
720 /***
721 * A custom file resource loader which filters tabs and removes spaces
722 * from lines with velocity commands.
723 */
724 public static class TorqueFileResourceLoader
725 extends FileResourceLoader
726 {
727 /***
728 * The task in which this resource loader is used.
729 */
730 private TorqueDataModelTask task;
731
732 /***
733 * Constructor.
734 *
735 * @param task the task in which this resource loader is used.
736 */
737 public TorqueFileResourceLoader(TorqueDataModelTask task)
738 {
739 this.task = task;
740 }
741
742 /***
743 * @see org.apache.velocity.runtime.resource.loader.FileResourceLoader#getResourceStream(java.lang.String)
744 */
745 public synchronized InputStream getResourceStream(
746 String name)
747 throws ResourceNotFoundException
748 {
749 InputStream source = null;
750 try
751 {
752 source = super.getResourceStream(name);
753 return task.filter(source);
754 }
755 catch (IOException uee)
756 {
757 task.log(uee.getMessage());
758 throw new ResourceNotFoundException(uee.getMessage());
759 }
760 finally
761 {
762 if (source != null)
763 {
764 try
765 {
766 source.close();
767 }
768 catch (IOException e)
769 {
770 task.log(e.getMessage());
771 }
772 }
773 }
774 }
775 }
776 }
777
778