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 }