1 package org.apache.torque.task;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
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
179 Class.forName(dbDriver);
180 log("DB driver sucessfuly instantiated");
181
182 Connection con = null;
183 try
184 {
185
186 con = DriverManager.getConnection(dbUrl, dbUser, dbPassword);
187 log("DB connection established");
188
189
190 DatabaseMetaData dbMetaData = con.getMetaData();
191
192
193 List tableList = getTableNames(dbMetaData);
194
195 databaseNode = doc.createElement("database");
196 databaseNode.setAttribute("name", dbUser);
197
198
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
219 String curTable = (String) tableList.get(i);
220
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
231
232
233 List columns = getColumns(dbMetaData, curTable);
234 List primKeys = getPrimaryKeys(dbMetaData, curTable);
235 Collection forgnKeys = getForeignKeys(dbMetaData, curTable);
236
237
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
255
256
257
258
259
260
261
262
263
264
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
306
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
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
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
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;
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);
514 ref[1] = foreignKeys.getString(4);
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 }