001 package org.apache.torque.task;
002
003 /*
004 * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
005 * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
007 * License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
012 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
013 * specific language governing permissions and limitations under the License.
014 */
015
016 import java.io.File;
017 import java.util.ArrayList;
018 import java.util.Date;
019 import java.util.Hashtable;
020 import java.util.Iterator;
021 import java.util.List;
022 import java.util.Map;
023
024 import org.apache.commons.lang.StringUtils;
025 import org.apache.texen.ant.TexenTask;
026 import org.apache.tools.ant.BuildException;
027 import org.apache.tools.ant.DirectoryScanner;
028 import org.apache.tools.ant.types.FileSet;
029 import org.apache.torque.engine.EngineException;
030 import org.apache.torque.engine.database.model.Database;
031 import org.apache.torque.engine.database.model.Table;
032 import org.apache.velocity.VelocityContext;
033 import org.apache.velocity.context.Context;
034 import org.kuali.core.db.torque.DatabaseParser;
035 import org.kuali.core.db.torque.KualiXmlToAppData;
036 import org.kuali.core.db.torque.StringFilter;
037 import org.kuali.core.db.torque.pojo.KualiDatabase;
038 import org.kuali.core.db.torque.pojo.Sequence;
039 import org.kuali.core.db.torque.pojo.View;
040
041 /**
042 * A base torque task that uses either a single XML schema representing a data model, or a <fileset> of XML
043 * schemas. We are making the assumption that an XML schema representing a data model contains tables for a
044 * <strong>single</strong> database.
045 *
046 * @author <a href="mailto:jvanzyl@zenplex.com">Jason van Zyl</a>
047 * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
048 */
049 public class TorqueDataModelTask extends TexenTask {
050 List<String> tblIncludes;
051 List<String> tblExcludes;
052 List<String> vIncludes;
053 List<String> sIncludes;
054 List<String> vExcludes;
055 List<String> sExcludes;
056
057 /**
058 * XML that describes the database model, this is transformed into the application model object.
059 */
060 protected String xmlFile;
061
062 /** Fileset of XML schemas which represent our data models. */
063 protected List<FileSet> filesets = new ArrayList<FileSet>();
064
065 /** Data models that we collect. One from each XML schema file. */
066 protected List<Database> dataModels = new ArrayList<Database>();
067
068 /** Velocity context which exposes our objects in the templates. */
069 protected Context context;
070
071 /**
072 * Map of data model name to database name. Should probably stick to the convention of them being the same but I
073 * know right now in a lot of cases they won't be.
074 */
075 protected Hashtable<String, String> dataModelDbMap;
076
077 /**
078 * Hashtable containing the names of all the databases in our collection of schemas.
079 */
080 protected Hashtable<String, String> databaseNames;
081
082 // !! This is probably a crappy idea having the sql file -> db map
083 // here. I can't remember why I put it here at the moment ...
084 // maybe I was going to map something else. It can probably
085 // move into the SQL task.
086
087 /**
088 * Name of the properties file that maps an SQL file to a particular database.
089 */
090 protected String sqldbmap;
091
092 /** The target database(s) we are generating SQL for. */
093 private String targetDatabase;
094
095 /** Target Java package to place the generated files in. */
096 private String targetPackage;
097
098 /**
099 * Set the sqldbmap.
100 *
101 * @param sqldbmap
102 * th db map
103 */
104 public void setSqlDbMap(String sqldbmap) {
105 // !! Make all these references files not strings.
106 this.sqldbmap = getProject().resolveFile(sqldbmap).toString();
107 }
108
109 /**
110 * Get the sqldbmap.
111 *
112 * @return String sqldbmap.
113 */
114 public String getSqlDbMap() {
115 return sqldbmap;
116 }
117
118 /**
119 * Return the data models that have been processed.
120 *
121 * @return List data models
122 */
123 public List<Database> getDataModels() {
124 return dataModels;
125 }
126
127 /**
128 * Return the data model to database name map.
129 *
130 * @return Hashtable data model name to database name map.
131 */
132 public Hashtable<String, String> getDataModelDbMap() {
133 return dataModelDbMap;
134 }
135
136 /**
137 * Get the xml schema describing the application model.
138 *
139 * @return String xml schema file.
140 */
141 public String getXmlFile() {
142 return xmlFile;
143 }
144
145 /**
146 * Set the xml schema describing the application model.
147 *
148 * @param xmlFile
149 * The new XmlFile value
150 */
151 public void setXmlFile(String xmlFile) {
152 this.xmlFile = xmlFile;
153 }
154
155 /**
156 * Adds a set of xml schema files (nested fileset attribute).
157 *
158 * @param set
159 * a Set of xml schema files
160 */
161 public void addFileset(FileSet set) {
162 filesets.add(set);
163 }
164
165 /**
166 * Get the current target database.
167 *
168 * @return String target database(s)
169 */
170 public String getTargetDatabase() {
171 return targetDatabase;
172 }
173
174 /**
175 * Set the current target database. (e.g. mysql, oracle, ..)
176 */
177 public void setTargetDatabase(String targetDatabase) {
178 this.targetDatabase = targetDatabase;
179 }
180
181 /**
182 * Get the current target package.
183 *
184 * @return return target java package.
185 */
186 public String getTargetPackage() {
187 return targetPackage;
188 }
189
190 /**
191 * Set the current target package. This is where generated java classes will live.
192 */
193 public void setTargetPackage(String targetPackage) {
194 this.targetPackage = targetPackage;
195 }
196
197 /**
198 * Return a SAX parser that implements the DatabaseParser interface
199 */
200 protected DatabaseParser getDatabaseParser() {
201 return new KualiXmlToAppData(getTargetDatabase(), getTargetPackage());
202 }
203
204 /**
205 * Parse a schema XML File into a Database object
206 */
207 protected Database getDataModel(File file) throws EngineException {
208 // Get a handle to a parser
209 DatabaseParser databaseParser = getDatabaseParser();
210
211 // Parse the file into a database
212 Database database = databaseParser.parseResource(file.toString());
213
214 KualiDatabase kdb = (KualiDatabase) database;
215
216 // Filter out tables as needed
217 filterTables(database);
218
219 // Filter out views as needed
220 filterViews(kdb);
221
222 // Filter out sequences as needed
223 filterSequences(kdb);
224
225 // Extract the filename
226 database.setFileName(grokName(file.toString()));
227
228 // return the database
229 return database;
230 }
231
232 protected List<Table> getTables(Database database) {
233 List<Table> tables = new ArrayList<Table>();
234 for (Object object : database.getTables()) {
235 Table table = (Table) object;
236 tables.add(table);
237 }
238 return tables;
239 }
240
241 protected void filterSequences(KualiDatabase database) {
242 StringFilter filter = new StringFilter(sIncludes, sExcludes);
243 Iterator<Sequence> itr = database.getSequences().iterator();
244 while (itr.hasNext()) {
245 Sequence sequence = itr.next();
246 String name = sequence.getName();
247 boolean remove = !filter.isInclude(name) || filter.isExclude(name);
248 if (remove) {
249 System.out.println("[INFO] Filtering out sequence " + name);
250 itr.remove();
251 }
252 }
253 }
254
255 protected void filterViews(KualiDatabase database) {
256 StringFilter filter = new StringFilter(vIncludes, vExcludes);
257 Iterator<View> itr = database.getViews().iterator();
258 while (itr.hasNext()) {
259 View view = itr.next();
260 String name = view.getName();
261 boolean remove = !filter.isInclude(name) || filter.isExclude(name);
262 if (remove) {
263 System.out.println("[INFO] Filtering out view " + name);
264 itr.remove();
265 }
266 }
267 }
268
269 protected void filterTables(Database database) {
270 StringFilter filter = new StringFilter(tblIncludes, tblExcludes);
271 List<Table> tables = getTables(database);
272 for (Table table : tables) {
273 String name = table.getName();
274 boolean remove = !filter.isInclude(name) || filter.isExclude(name);
275 if (remove) {
276 System.out.println("[INFO] Filtering out table " + name);
277 database.removeTable(table);
278 }
279 }
280 }
281
282 /**
283 * Get the list of schema XML files from our filesets
284 */
285 protected List<File> getDataModelFiles() {
286 // Allocate some storage
287 List<File> dataModelFiles = new ArrayList<File>();
288
289 // Iterate through the filesets
290 for (int i = 0; i < getFilesets().size(); i++) {
291 // Extract a fileset
292 FileSet fs = getFilesets().get(i);
293
294 // Create a directory scanner
295 DirectoryScanner ds = fs.getDirectoryScanner(getProject());
296
297 // Figure out the directory to scan
298 File srcDir = fs.getDir(getProject());
299
300 // Scan the directory
301 String[] dataModelFilesArray = ds.getIncludedFiles();
302
303 // Add each file in the directory to our list
304 for (int j = 0; j < dataModelFilesArray.length; j++) {
305 File file = new File(srcDir, dataModelFilesArray[j]);
306 dataModelFiles.add(file);
307 }
308 }
309
310 // Return the list of schema.xml files
311 return dataModelFiles;
312 }
313
314 /**
315 * Parse schema XML files into Database objects
316 */
317 protected List<Database> getPopulatedDataModels() throws EngineException {
318 // Allocate some storage
319 List<Database> databases = new ArrayList<Database>();
320
321 // Only one file to parse
322 if (getXmlFile() != null) {
323 // Parse the file into a database object
324 Database database = getDataModel(new File(getXmlFile()));
325 // Add it to our list
326 databases.add(database);
327 // we are done
328 return databases;
329 }
330
331 // Get the list of schema XML files to parse from our filesets
332 List<File> dataModelFiles = getDataModelFiles();
333 // Iterate through the list, parsing each schema.xml file into a database object
334 for (File dataModelFile : dataModelFiles) {
335 // Parse a schema.xml file into a database object
336 Database database = getDataModel(dataModelFile);
337 // Add the database object to our list
338 databases.add(database);
339 }
340 // Return the list of database objects
341 return databases;
342 }
343
344 /**
345 * Set up the initial context for generating SQL
346 *
347 * @return the context
348 * @throws Exception
349 */
350 @Override
351 public Context initControlContext() throws Exception {
352 if (xmlFile == null && filesets.isEmpty()) {
353 throw new BuildException("You must specify an XML schema or fileset of XML schemas!");
354 }
355
356 try {
357 dataModels = getPopulatedDataModels();
358 } catch (EngineException ee) {
359 throw new BuildException(ee);
360 }
361
362 Iterator<Database> i = dataModels.iterator();
363 databaseNames = new Hashtable<String, String>();
364 dataModelDbMap = new Hashtable<String, String>();
365
366 // Different datamodels may state the same database
367 // names, we just want the unique names of databases.
368 while (i.hasNext()) {
369 Database database = i.next();
370 databaseNames.put(database.getName(), database.getName());
371 dataModelDbMap.put(database.getFileName(), database.getName());
372 }
373
374 context = new VelocityContext();
375
376 // Place our set of data models into the context along
377 // with the names of the databases as a convenience for now.
378 context.put("dataModels", dataModels);
379 context.put("databaseNames", databaseNames);
380 context.put("targetDatabase", targetDatabase);
381 context.put("targetPackage", targetPackage);
382
383 return context;
384 }
385
386 /**
387 * Change type of "now" to java.util.Date
388 *
389 * @see org.apache.velocity.texen.ant.TexenTask#populateInitialContext(org.apache.velocity.context.Context)
390 */
391 @Override
392 protected void populateInitialContext(Context context) throws Exception {
393 super.populateInitialContext(context);
394 context.put("now", new Date());
395 }
396
397 /**
398 * Gets a name to use for the application's data model.
399 *
400 * @param xmlFile
401 * The path to the XML file housing the data model.
402 * @return The name to use for the <code>AppData</code>.
403 */
404 private String grokName(String xmlFile) {
405 // This can't be set from the file name as it is an unreliable
406 // method of naming the descriptor. Not everyone uses the same
407 // method as I do in the TDK. jvz.
408
409 String name = "data-model";
410 int i = xmlFile.lastIndexOf(System.getProperty("file.separator"));
411 if (i != -1) {
412 // Creep forward to the start of the file name.
413 i++;
414
415 int j = xmlFile.lastIndexOf('.');
416 if (i < j) {
417 name = xmlFile.substring(i, j);
418 } else {
419 // Weirdo
420 name = xmlFile.substring(i);
421 }
422 }
423 return name;
424 }
425
426 /**
427 * Override Texen's context properties to map the torque.xxx properties (including defaults set by the
428 * org/apache/torque/defaults.properties) to just xxx.
429 *
430 * <p>
431 * Also, move xxx.yyy properties to xxxYyy as Velocity doesn't like the xxx.yyy syntax.
432 * </p>
433 *
434 * @param file
435 * the file to read the properties from
436 */
437 @Override
438 public void setContextProperties(String file) {
439 super.setContextProperties(file);
440
441 // Map the torque.xxx elements from the env to the contextProperties
442 Hashtable<?, ?> env = super.getProject().getProperties();
443 for (Iterator<?> i = env.entrySet().iterator(); i.hasNext();) {
444 Map.Entry<?, ?> entry = (Map.Entry<?, ?>) i.next();
445 String key = (String) entry.getKey();
446 if (key.startsWith("torque.")) {
447 String newKey = key.substring("torque.".length());
448 int j = newKey.indexOf(".");
449 while (j != -1) {
450 newKey = newKey.substring(0, j) + StringUtils.capitalize(newKey.substring(j + 1));
451 j = newKey.indexOf(".");
452 }
453
454 contextProperties.setProperty(newKey, entry.getValue());
455 }
456 }
457 }
458
459 public List<FileSet> getFilesets() {
460 return filesets;
461 }
462
463 public void setFilesets(List<FileSet> filesets) {
464 this.filesets = filesets;
465 }
466
467 public List<String> getTblIncludes() {
468 return tblIncludes;
469 }
470
471 public void setTblIncludes(List<String> includes) {
472 this.tblIncludes = includes;
473 }
474
475 public List<String> getTblExcludes() {
476 return tblExcludes;
477 }
478
479 public void setTblExcludes(List<String> excludes) {
480 this.tblExcludes = excludes;
481 }
482
483 public List<String> getvIncludes() {
484 return vIncludes;
485 }
486
487 public void setvIncludes(List<String> vIncludes) {
488 this.vIncludes = vIncludes;
489 }
490
491 public List<String> getsIncludes() {
492 return sIncludes;
493 }
494
495 public void setsIncludes(List<String> sIncludes) {
496 this.sIncludes = sIncludes;
497 }
498
499 public List<String> getvExcludes() {
500 return vExcludes;
501 }
502
503 public void setvExcludes(List<String> vExcludes) {
504 this.vExcludes = vExcludes;
505 }
506
507 public List<String> getsExcludes() {
508 return sExcludes;
509 }
510
511 public void setsExcludes(List<String> sExcludes) {
512 this.sExcludes = sExcludes;
513 }
514 }