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    }