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