1 package org.apache.torque.task;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
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
183 Class.forName(dbDriver);
184 log("DB driver sucessfuly instantiated");
185
186 Connection con = null;
187 try
188 {
189
190 con = DriverManager.getConnection(dbUrl, dbUser, dbPassword);
191 log("DB connection established");
192
193
194 DatabaseMetaData dbMetaData = con.getMetaData();
195
196
197 List tableList = getTableNames(dbMetaData);
198
199 databaseNode = doc.createElement("database");
200 databaseNode.setAttribute("name", dbUser);
201
202
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
223 String curTable = (String) tableList.get(i);
224
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
235
236
237 List columns = getColumns(dbMetaData, curTable);
238 List primKeys = getPrimaryKeys(dbMetaData, curTable);
239 Collection forgnKeys = getForeignKeys(dbMetaData, curTable);
240
241
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
259
260
261
262
263
264
265
266
267
268
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
310
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
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
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
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;
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);
518 ref[1] = foreignKeys.getString(4);
519 refs.add(ref);
520 }
521 }
522 catch (SQLException e)
523 {
524
525
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 }