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    }