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 }