View Javadoc

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