View Javadoc

1   package org.apache.torque.task;
2   
3   /*
4    * Copyright 2001-2005 The Apache Software Foundation.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License")
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  import java.io.FileOutputStream;
20  import java.io.PrintWriter;
21  import java.sql.Connection;
22  import java.sql.DatabaseMetaData;
23  import java.sql.DriverManager;
24  import java.sql.ResultSet;
25  import java.sql.SQLException;
26  import java.sql.Types;
27  import java.util.ArrayList;
28  import java.util.Collection;
29  import java.util.Hashtable;
30  import java.util.Iterator;
31  import java.util.List;
32  
33  import org.apache.tools.ant.BuildException;
34  import org.apache.tools.ant.Task;
35  import org.apache.torque.engine.database.model.TypeMap;
36  import org.apache.torque.engine.database.transform.DTDResolver;
37  import org.apache.xerces.dom.DocumentImpl;
38  import org.apache.xerces.dom.DocumentTypeImpl;
39  import org.apache.xml.serialize.Method;
40  import org.apache.xml.serialize.OutputFormat;
41  import org.apache.xml.serialize.XMLSerializer;
42  import org.w3c.dom.Element;
43  
44  /***
45   * This class generates an XML schema of an existing database from
46   * JDBC metadata.
47   *
48   * @author <a href="mailto:jvanzyl@periapt.com">Jason van Zyl</a>
49   * @author <a href="mailto:fedor.karpelevitch@barra.com">Fedor Karpelevitch</a>
50   * @version $Id: TorqueJDBCTransformTask.java 331000 2005-11-05 13:59:18Z tfischer $
51   */
52  public class TorqueJDBCTransformTask extends Task
53  {
54      /*** Name of XML database schema produced. */
55      protected String xmlSchema;
56  
57      /*** JDBC URL. */
58      protected String dbUrl;
59  
60      /*** JDBC driver. */
61      protected String dbDriver;
62  
63      /*** JDBC user name. */
64      protected String dbUser;
65  
66      /*** JDBC password. */
67      protected String dbPassword;
68  
69      /*** DB schema to use. */
70      protected String dbSchema;
71  
72      /*** DOM document produced. */
73      protected DocumentImpl doc;
74  
75      /*** The document root element. */
76      protected Element databaseNode;
77  
78      /*** Hashtable of columns that have primary keys. */
79      protected Hashtable primaryKeys;
80  
81      /*** Hashtable to track what table a column belongs to. */
82      protected Hashtable columnTableMap;
83  
84      protected boolean sameJavaName;
85  
86      private XMLSerializer xmlSerializer;
87  
88      public String getDbSchema()
89      {
90          return dbSchema;
91      }
92  
93      public void setDbSchema(String dbSchema)
94      {
95          this.dbSchema = dbSchema;
96      }
97  
98      public void setDbUrl(String v)
99      {
100         dbUrl = v;
101     }
102 
103     public void setDbDriver(String v)
104     {
105         dbDriver = v;
106     }
107 
108     public void setDbUser(String v)
109     {
110         dbUser = v;
111     }
112 
113     public void setDbPassword(String v)
114     {
115         dbPassword = v;
116     }
117 
118     public void setOutputFile (String v)
119     {
120         xmlSchema = v;
121     }
122 
123     public void setSameJavaName(boolean v)
124     {
125         this.sameJavaName = v;
126     }
127 
128     public boolean isSameJavaName()
129     {
130         return this.sameJavaName;
131     }
132 
133     /***
134      * Default constructor.
135      *
136      * @throws BuildException
137      */
138     public void execute() throws BuildException
139     {
140         log("Torque - JDBCToXMLSchema starting");
141         log("Your DB settings are:");
142         log("driver : " + dbDriver);
143         log("URL : " + dbUrl);
144         log("user : " + dbUser);
145         // log("password : " + dbPassword);
146         log("schema : " + dbSchema);
147 
148         DocumentTypeImpl docType = new DocumentTypeImpl(null, "database", null,
149                 DTDResolver.WEB_SITE_DTD);
150         doc = new DocumentImpl(docType);
151         doc.appendChild(doc.createComment(
152                 " Autogenerated by JDBCToXMLSchema! "));
153 
154         try
155         {
156             generateXML();
157             log(xmlSchema);
158             xmlSerializer = new XMLSerializer(
159                     new PrintWriter(
160                     new FileOutputStream(xmlSchema)),
161                     new OutputFormat(Method.XML, null, true));
162             xmlSerializer.serialize(doc);
163         }
164         catch (Exception e)
165         {
166             throw new BuildException(e);
167         }
168         log("Torque - JDBCToXMLSchema finished");
169     }
170 
171     /***
172      * Generates an XML database schema from JDBC metadata.
173      *
174      * @throws Exception a generic exception.
175      */
176     public void generateXML() throws Exception
177     {
178         // Load the Interbase Driver.
179         Class.forName(dbDriver);
180         log("DB driver sucessfuly instantiated");
181 
182         Connection con = null;
183         try
184         {
185             // Attempt to connect to a database.
186             con = DriverManager.getConnection(dbUrl, dbUser, dbPassword);
187             log("DB connection established");
188 
189             // Get the database Metadata.
190             DatabaseMetaData dbMetaData = con.getMetaData();
191 
192             // The database map.
193             List tableList = getTableNames(dbMetaData);
194 
195             databaseNode = doc.createElement("database");
196             databaseNode.setAttribute("name", dbUser);
197 
198             // Build a database-wide column -> table map.
199             columnTableMap = new Hashtable();
200 
201             log("Building column/table map...");
202             for (int i = 0; i < tableList.size(); i++)
203             {
204                 String curTable = (String) tableList.get(i);
205                 List columns = getColumns(dbMetaData, curTable);
206 
207                 for (int j = 0; j < columns.size(); j++)
208                 {
209                     List col = (List) columns.get(j);
210                     String name = (String) col.get(0);
211 
212                     columnTableMap.put(name, curTable);
213                 }
214             }
215 
216             for (int i = 0; i < tableList.size(); i++)
217             {
218                 // Add Table.
219                 String curTable = (String) tableList.get(i);
220                 // dbMap.addTable(curTable);
221                 log("Processing table: " + curTable);
222 
223                 Element table = doc.createElement("table");
224                 table.setAttribute("name", curTable);
225                 if (isSameJavaName())
226                 {
227                     table.setAttribute("javaName", curTable);
228                 }
229 
230                 // Add Columns.
231                 // TableMap tblMap = dbMap.getTable(curTable);
232 
233                 List columns = getColumns(dbMetaData, curTable);
234                 List primKeys = getPrimaryKeys(dbMetaData, curTable);
235                 Collection forgnKeys = getForeignKeys(dbMetaData, curTable);
236 
237                 // Set the primary keys.
238                 primaryKeys = new Hashtable();
239 
240                 for (int k = 0; k < primKeys.size(); k++)
241                 {
242                     String curPrimaryKey = (String) primKeys.get(k);
243                     primaryKeys.put(curPrimaryKey, curPrimaryKey);
244                 }
245 
246                 for (int j = 0; j < columns.size(); j++)
247                 {
248                     List col = (List) columns.get(j);
249                     String name = (String) col.get(0);
250                     Integer type = ((Integer) col.get(1));
251                     int size = ((Integer) col.get(2)).intValue();
252                     int scale = ((Integer) col.get(5)).intValue();
253 
254                     // From DatabaseMetaData.java
255                     //
256                     // Indicates column might not allow NULL values.  Huh?
257                     // Might? Boy, that's a definitive answer.
258                     /* int columnNoNulls = 0; */
259 
260                     // Indicates column definitely allows NULL values.
261                     /* int columnNullable = 1; */
262 
263                     // Indicates NULLABILITY of column is unknown.
264                     /* int columnNullableUnknown = 2; */
265 
266                     Integer nullType = (Integer) col.get(3);
267                     String defValue = (String) col.get(4);
268 
269                     Element column = doc.createElement("column");
270                     column.setAttribute("name", name);
271                     if (isSameJavaName())
272                     {
273                         column.setAttribute("javaName", name);
274                     }
275 
276                     column.setAttribute("type", TypeMap.getTorqueType(type).getName());
277 
278                     if (size > 0 && (type.intValue() == Types.CHAR
279                             || type.intValue() == Types.VARCHAR
280                             || type.intValue() == Types.LONGVARCHAR
281                             || type.intValue() == Types.DECIMAL
282                             || type.intValue() == Types.NUMERIC))
283                     {
284                         column.setAttribute("size", String.valueOf(size));
285                     }
286 
287                     if (scale > 0 && (type.intValue() == Types.DECIMAL
288                             || type.intValue() == Types.NUMERIC))
289                     {
290                         column.setAttribute("scale", String.valueOf(scale));
291                     }
292 
293                     if (nullType.intValue() == 0)
294                     {
295                         column.setAttribute("required", "true");
296                     }
297 
298                     if (primaryKeys.containsKey(name))
299                     {
300                         column.setAttribute("primaryKey", "true");
301                     }
302 
303                     if (defValue != null)
304                     {
305                         // trim out parens & quotes out of def value.
306                         // makes sense for MSSQL. not sure about others.
307                         if (defValue.startsWith("(") && defValue.endsWith(")"))
308                         {
309                             defValue = defValue.substring(1, defValue.length() - 1);
310                         }
311 
312                         if (defValue.startsWith("'") && defValue.endsWith("'"))
313                         {
314                             defValue = defValue.substring(1, defValue.length() - 1);
315                         }
316 
317                         column.setAttribute("default", defValue);
318                     }
319                     table.appendChild(column);
320                 }
321 
322                 // Foreign keys for this table.
323                 for (Iterator l = forgnKeys.iterator(); l.hasNext();)
324                 {
325                     Object[] forKey = (Object[]) l.next();
326                     String foreignKeyTable = (String) forKey[0];
327                     List refs = (List) forKey[1];
328                     Element fk = doc.createElement("foreign-key");
329                     fk.setAttribute("foreignTable", foreignKeyTable);
330                     for (int m = 0; m < refs.size(); m++)
331                     {
332                         Element ref = doc.createElement("reference");
333                         String[] refData = (String[]) refs.get(m);
334                         ref.setAttribute("local", refData[0]);
335                         ref.setAttribute("foreign", refData[1]);
336                         fk.appendChild(ref);
337                     }
338                     table.appendChild(fk);
339                 }
340                 databaseNode.appendChild(table);
341             }
342             doc.appendChild(databaseNode);
343         }
344         finally {
345             if (con != null)
346             {
347                 con.close();
348                 con = null;
349             }
350         }
351     }
352 
353     /***
354      * Get all the table names in the current database that are not
355      * system tables.
356      *
357      * @param dbMeta JDBC database metadata.
358      * @return The list of all the tables in a database.
359      * @throws SQLException
360      */
361     public List getTableNames(DatabaseMetaData dbMeta)
362         throws SQLException
363     {
364         log("Getting table list...");
365         List tables = new ArrayList();
366         ResultSet tableNames = null;
367         // these are the entity types we want from the database
368         String[] types = {"TABLE", "VIEW"};
369         try
370         {
371             tableNames = dbMeta.getTables(null, dbSchema, "%", types);
372             while (tableNames.next())
373             {
374                 String name = tableNames.getString(3);
375                 String type = tableNames.getString(4);
376                 tables.add(name);
377             }
378         }
379         finally
380         {
381             if (tableNames != null)
382             {
383                 tableNames.close();
384             }
385         }
386         return tables;
387     }
388 
389     /***
390      * Retrieves all the column names and types for a given table from
391      * JDBC metadata.  It returns a List of Lists.  Each element
392      * of the returned List is a List with:
393      *
394      * element 0 => a String object for the column name.
395      * element 1 => an Integer object for the column type.
396      * element 2 => size of the column.
397      * element 3 => null type.
398      *
399      * @param dbMeta JDBC metadata.
400      * @param tableName Table from which to retrieve column information.
401      * @return The list of columns in <code>tableName</code>.
402      * @throws SQLException
403      */
404     public List getColumns(DatabaseMetaData dbMeta, String tableName)
405             throws SQLException
406     {
407         List columns = new ArrayList();
408         ResultSet columnSet = null;
409         try
410         {
411             columnSet = dbMeta.getColumns(null, dbSchema, tableName, null);
412             while (columnSet.next())
413             {
414                 String name = columnSet.getString(4);
415                 Integer sqlType = new Integer(columnSet.getString(5));
416                 Integer size = new Integer(columnSet.getInt(7));
417                 Integer decimalDigits = new Integer(columnSet.getInt(9));
418                 Integer nullType = new Integer(columnSet.getInt(11));
419                 String defValue = columnSet.getString(13);
420 
421                 List col = new ArrayList(6);
422                 col.add(name);
423                 col.add(sqlType);
424                 col.add(size);
425                 col.add(nullType);
426                 col.add(defValue);
427                 col.add(decimalDigits);
428                 columns.add(col);
429             }
430         }
431         finally
432         {
433             if (columnSet != null)
434             {
435                 columnSet.close();
436             }
437         }
438         return columns;
439     }
440 
441     /***
442      * Retrieves a list of the columns composing the primary key for a given
443      * table.
444      *
445      * @param dbMeta JDBC metadata.
446      * @param tableName Table from which to retrieve PK information.
447      * @return A list of the primary key parts for <code>tableName</code>.
448      * @throws SQLException
449      */
450     public List getPrimaryKeys(DatabaseMetaData dbMeta, String tableName)
451             throws SQLException
452     {
453         List pk = new ArrayList();
454         ResultSet parts = null;
455         try
456         {
457             parts = dbMeta.getPrimaryKeys(null, dbSchema, tableName);
458             while (parts.next())
459             {
460                 pk.add(parts.getString(4));
461             }
462         }
463         finally
464         {
465             if (parts != null)
466             {
467                 parts.close();
468             }
469         }
470         return pk;
471     }
472 
473     /***
474      * Retrieves a list of foreign key columns for a given table.
475      *
476      * @param dbMeta JDBC metadata.
477      * @param tableName Table from which to retrieve FK information.
478      * @return A list of foreign keys in <code>tableName</code>.
479      * @throws SQLException
480      */
481     public Collection getForeignKeys(DatabaseMetaData dbMeta, String tableName)
482         throws SQLException
483     {
484         Hashtable fks = new Hashtable();
485         ResultSet foreignKeys = null;
486         try
487         {
488             foreignKeys = dbMeta.getImportedKeys(null, dbSchema, tableName);
489             while (foreignKeys.next())
490             {
491                 String refTableName = foreignKeys.getString(3);
492                 String fkName = foreignKeys.getString(12);
493                 // if FK has no name - make it up (use tablename instead)
494                 if (fkName == null)
495                 {
496                     fkName = refTableName;
497                 }
498                 Object[] fk = (Object[]) fks.get(fkName);
499                 List refs;
500                 if (fk == null)
501                 {
502                     fk = new Object[2];
503                     fk[0] = refTableName; //referenced table name
504                     refs = new ArrayList();
505                     fk[1] = refs;
506                     fks.put(fkName, fk);
507                 }
508                 else
509                 {
510                     refs = (ArrayList) fk[1];
511                 }
512                 String[] ref = new String[2];
513                 ref[0] = foreignKeys.getString(8); //local column
514                 ref[1] = foreignKeys.getString(4); //foreign column
515                 refs.add(ref);
516             }
517         }
518         finally
519         {
520             if (foreignKeys != null)
521             {
522                 foreignKeys.close();
523             }
524         }
525         return fks.values();
526     }
527 }