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