1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47 package pl.kernelpanic.dbmonster;
48
49 import java.io.File;
50 import java.io.FileOutputStream;
51 import java.io.IOException;
52 import java.io.OutputStream;
53 import java.io.PrintWriter;
54 import java.sql.Connection;
55 import java.sql.DatabaseMetaData;
56 import java.sql.ResultSet;
57 import java.sql.SQLException;
58 import java.sql.Types;
59 import java.util.HashMap;
60 import java.util.Iterator;
61 import java.util.Map;
62 import java.util.Properties;
63 import java.util.Vector;
64
65 import org.apache.commons.logging.Log;
66 import org.apache.commons.logging.LogFactory;
67
68 import pl.kernelpanic.dbmonster.connection.ConnectionProvider;
69 import pl.kernelpanic.dbmonster.connection.DBCPConnectionProvider;
70 import pl.kernelpanic.dbmonster.connection.Transaction;
71 import pl.kernelpanic.dbmonster.generator.BooleanGenerator;
72 import pl.kernelpanic.dbmonster.generator.DataGenerator;
73 import pl.kernelpanic.dbmonster.generator.DateTimeGenerator;
74 import pl.kernelpanic.dbmonster.generator.ForeignKeyGenerator;
75 import pl.kernelpanic.dbmonster.generator.KeyGenerator;
76 import pl.kernelpanic.dbmonster.generator.MaxKeyGenerator;
77 import pl.kernelpanic.dbmonster.generator.NullGenerator;
78 import pl.kernelpanic.dbmonster.generator.NumberGenerator;
79 import pl.kernelpanic.dbmonster.generator.StringGenerator;
80 import pl.kernelpanic.dbmonster.generator.StringKeyGenerator;
81 import pl.kernelpanic.dbmonster.schema.Column;
82 import pl.kernelpanic.dbmonster.schema.Key;
83 import pl.kernelpanic.dbmonster.schema.Schema;
84 import pl.kernelpanic.dbmonster.schema.SchemaException;
85 import pl.kernelpanic.dbmonster.schema.SchemaUtil;
86 import pl.kernelpanic.dbmonster.schema.Table;
87 import pl.kernelpanic.dbmonster.sql.ExtendedTypes;
88
89 /***
90 * Schema Grabber.
91 *
92 * @author Piotr Maj <piotr.maj@kernelpanic.pl>
93 *
94 * @version $Id: SchemaGrabber.html,v 1.1 2007/06/21 08:38:14 sbahloul Exp $
95 */
96 public class SchemaGrabber {
97
98 /***
99 * Logger.
100 */
101 private Log log = LogFactory.getLog(SchemaGrabber.class);
102
103 /***
104 * Connection provider.
105 */
106 private ConnectionProvider connectionProvider = null;
107
108 /***
109 * Specifies the tables we want to grab. If set to <code>null</code>
110 * all tables will be grabbed.
111 */
112 private Vector tables = null;
113
114 /***
115 * Output stream.
116 */
117 private OutputStream output = System.out;
118
119 /***
120 * Properties.
121 */
122 private Properties properties = new Properties();
123
124 /***
125 * Database schema used in grabbing table. Useful for Oracle,
126 * and other databases that support schema.
127 */
128 private String dbSchema = null;
129
130 /***
131 * Number of rows to generate.
132 */
133 private int numRows = 1000;
134
135 /***
136 * Constructs new SchemaGrabber.
137 */
138 public SchemaGrabber() {
139 }
140
141 /***
142 * Starts SchemaGrabber
143 *
144 * @param args command line arguments
145 *
146 * @throws Exception on errors
147 */
148 public static void main(final String[] args) throws Exception {
149 SchemaGrabber sg = new SchemaGrabber();
150 ConnectionProvider cp = new DBCPConnectionProvider();
151 sg.setConnectionProvider(cp);
152 Schema schema = sg.grabSchema();
153 OutputStream os = sg.getOutput();
154 SchemaUtil.serializeSchema(new PrintWriter(os), schema);
155 }
156
157 /***
158 * Starts schema grabber.
159 *
160 * @throws Exception on errors
161 */
162 public final void doTheJob() throws Exception {
163 Schema schema = grabSchema();
164 SchemaUtil.serializeSchema(new PrintWriter(output), schema);
165 }
166
167 /***
168 * Returns the output stream where the serialized schema should
169 * be pushed.
170 *
171 * @return output stream
172 */
173 public final OutputStream getOutput() {
174 return output;
175 }
176
177 /***
178 * Grabs the schema.
179 *
180 * @return Schema schema
181 *
182 * @throws Exception on errors
183 */
184 public final Schema grabSchema() throws Exception {
185
186
187 dbSchema = properties.getProperty("dbmonster.jdbc.schema", null);
188 if (dbSchema != null && "".equals(dbSchema)) {
189 dbSchema = null;
190 }
191 String rows = properties.getProperty("dbmonster.rows", "1000");
192 try {
193 numRows = Integer.valueOf(rows).intValue();
194 } catch (Exception e) {
195 }
196 Schema schema = new Schema();
197 schema.setName("Change me!");
198 if (tables == null) {
199 tables = getTableNames();
200 }
201 Iterator it = tables.iterator();
202 int count = tables.size();
203 int current = 1;
204 log.info("Grabbing schema from database. "
205 + count + " tables to grab.");
206 while (it.hasNext()) {
207 String tableName = (String) it.next();
208 Table t = grabTable(tableName);
209 schema.addTable(t);
210 log.info("Grabbing table " + tableName + ". "
211 + ((current * 100) / count) + "% done.");
212 ++current;
213 }
214 log.info("Grabbing schema from database complete.");
215 return schema;
216 }
217
218 /***
219 * Grabs the table.
220 *
221 * @param name table name
222 *
223 * @return table
224 *
225 * @throws SQLException on SQL errors
226 * @throws SchemaException on schema errors
227 */
228 public final Table grabTable(final String name)
229 throws SQLException, SchemaException {
230 ResultSet rs;
231
232 Table t = new Table();
233 String tableName = name;
234 if (dbSchema != null) {
235 tableName = dbSchema + "." + tableName;
236 }
237 t.setName(tableName);
238 t.setRows(numRows);
239 Transaction tx = null;
240 try {
241 tx = new Transaction(connectionProvider);
242 Connection conn = tx.begin();
243 DatabaseMetaData md = conn.getMetaData();
244
245
246 Vector keyColumns = new Vector();
247 rs = md.getPrimaryKeys(null, dbSchema, name);
248 while (rs.next()) {
249 String columnName = rs.getString("COLUMN_NAME");
250 keyColumns.add(columnName);
251 }
252 KeyGenerator generator = suggestKeyGenerator(name, keyColumns);
253 if (generator != null) {
254 Key key = new Key();
255 key.setGenerator(generator);
256 t.setKey(key);
257 }
258 else {
259
260 keyColumns.clear();
261 }
262
263
264 HashMap foreignKeys = new HashMap();
265 rs = md.getImportedKeys(null, null, name);
266 while (rs.next()) {
267 String fkColumn = rs.getString("FKCOLUMN_NAME");
268 String pkColumn = rs.getString("PKCOLUMN_NAME");
269 String pkTable = rs.getString("PKTABLE_NAME");
270
271
272
273
274
275
276 foreignKeys.put(fkColumn, new PairBean(pkTable, pkColumn));
277 }
278
279
280 rs = md.getColumns(null, dbSchema, name, "%");
281 while (rs.next()) {
282 String columnName = rs.getString("COLUMN_NAME");
283 if (keyColumns.contains(columnName)) {
284 continue;
285 }
286 Column c = new Column();
287 c.setName(columnName);
288 c.setGenerator(suggestGenerator(name, columnName, foreignKeys));
289 t.addColumn(c);
290 }
291
292 } catch (SQLException e) {
293 tx.abort();
294 log.fatal(e.getMessage());
295 throw e;
296 } finally {
297 if (tx != null) {
298 tx.close();
299 }
300 }
301 return t;
302 }
303
304 /***
305 * Returns names of the tables in the database.
306 *
307 * @return vector of strings
308 *
309 * @throws SQLException on errors
310 */
311 public final Vector getTableNames() throws SQLException {
312 Vector v = new Vector();
313 Transaction tx = null;
314 try {
315 tx = new Transaction(connectionProvider);
316 Connection conn = tx.begin();
317 DatabaseMetaData md = conn.getMetaData();
318 ResultSet rs =
319 md.getTables(null, dbSchema, "%", new String[] {"TABLE"});
320 while (rs.next()) {
321 String name = rs.getString("TABLE_NAME");
322 v.add(name);
323 }
324 tx.commit();
325 } catch (SQLException e) {
326 log.fatal(e.getMessage());
327 tx.abort();
328 throw e;
329 } finally {
330 if (tx != null) {
331 tx.close();
332 }
333 }
334 return v;
335 }
336
337 /***
338 * Returns a connection provider used by the grabber.
339 *
340 * @return connection provider instance
341 */
342 public final ConnectionProvider getConnectionProvider() {
343 return connectionProvider;
344 }
345
346 /***
347 * Sets the connection provider.
348 *
349 * @param provider connection provider
350 */
351 public final void setConnectionProvider(
352 final ConnectionProvider provider) {
353 connectionProvider = provider;
354 }
355
356 /***
357 * Returns the logger.
358 *
359 * @return logger
360 */
361 public final Log getLog() {
362 return log;
363 }
364
365 /***
366 * Sets the logger
367 *
368 * @param logger new logger
369 */
370 public final void setLog(final Log logger) {
371 log = logger;
372 }
373
374 /***
375 * Adds a table names to the list of grabbed tables.
376 *
377 * @param name name of the table
378 *
379 * @throws SQLException if table cannot be found
380 */
381 public final void addTable(final String name) throws SQLException {
382 if (tables == null) {
383 tables = new Vector();
384 }
385 String tableName = name;
386 Transaction tx = null;
387 try {
388 tx = new Transaction(connectionProvider);
389 Connection conn = tx.begin();
390 DatabaseMetaData md = conn.getMetaData();
391 if (md.storesLowerCaseIdentifiers()) {
392 tableName = name.toLowerCase();
393 } else if (md.storesUpperCaseIdentifiers()) {
394 tableName = name.toUpperCase();
395 }
396 ResultSet rs =
397 md.getTables(null, dbSchema, tableName, new String[] {"TABLE"});
398 if (!rs.next()) {
399 throw new SQLException("No such table <" + name + ">.");
400 }
401 tx.commit();
402 tables.add(tableName);
403 } catch (SQLException e) {
404 log.fatal(e.getMessage());
405 tx.abort();
406 throw e;
407 } finally {
408 if (tx != null) {
409 tx.close();
410 }
411 }
412 }
413
414 /***
415 * Sets the output file name.
416 *
417 * @param file file name
418 *
419 * @throws IOException on I/O errors
420 */
421 public final void setOutputFile(final String file) throws IOException {
422 File f = new File(file);
423 output = new FileOutputStream(f);
424 }
425
426 /***
427 * Sets the properties for the SchemaGrabber
428 * @param p
429 */
430 public final void setProperties(final Properties p) {
431 properties = p;
432 }
433
434 /***
435 * Suggests which key generator should be used. In current implementation
436 * it only check if the key consists of only one table and if so it uses
437 * MaxKeyGenerator (it the type of the column allow it).
438 *
439 * @param tableName the name ot the table
440 * @param columns columns
441 *
442 * @return key generator or <code>null</code>
443 *
444 * @throws SQLException on errors
445 */
446 private final KeyGenerator suggestKeyGenerator(
447 final String tableName,
448 final Vector columns) throws SQLException {
449
450 KeyGenerator generator = null;
451
452 if (columns.size() != 1) {
453 return generator;
454 }
455 String columnName = (String) columns.get(0);
456 Transaction tx = null;
457 try {
458 tx = new Transaction(connectionProvider);
459 Connection conn = tx.begin();
460 DatabaseMetaData md = conn.getMetaData();
461 ResultSet rs = md.getColumns(null, null, tableName, columnName);
462 rs.next();
463 int dataType = rs.getInt("DATA_TYPE");
464 if (isIntegerType(dataType)) {
465 generator = new MaxKeyGenerator();
466 ((MaxKeyGenerator) generator).setColumnName(columnName);
467 } else if (isTextType(dataType)) {
468 generator = new StringKeyGenerator();
469 ((StringKeyGenerator) generator).setStartValue("0");
470 ((StringKeyGenerator) generator).setColumnName(columnName);
471 } else {
472 if (log.isWarnEnabled()) {
473 log.warn("Datatype " + dataType + " for " + columnName
474 + " is unknown, no Key Generator in schema.xml");
475 }
476 }
477 tx.commit();
478 } catch (SQLException e) {
479 tx.abort();
480 log.fatal(e.getMessage());
481 throw e;
482 } finally {
483 if (tx != null) {
484 tx.close();
485 }
486 }
487 return generator;
488 }
489
490 /***
491 * Suggests the data generator for a column.
492 *
493 * @param tableName table name
494 * @param columnName column name
495 * @param foreignKeys infomation about foreing keys in this table
496 *
497 * @return data generator. Never <code>null</code>.
498 *
499 * @throws SQLException on SQL errors
500 */
501 private final DataGenerator suggestGenerator(
502 final String tableName,
503 final String columnName,
504 final Map foreignKeys) throws SQLException {
505
506 DataGenerator generator = new NullGenerator();
507 if (foreignKeys.containsKey(columnName)) {
508 generator = new ForeignKeyGenerator();
509 PairBean bean = (PairBean) foreignKeys.get(columnName);
510 ((ForeignKeyGenerator) generator).setTableName(bean.getKey());
511 ((ForeignKeyGenerator) generator).setColumnName(bean.getValue());
512 return generator;
513 }
514 Transaction tx = null;
515 try {
516 tx = new Transaction(connectionProvider);
517 Connection conn = tx.begin();
518 DatabaseMetaData md = conn.getMetaData();
519 ResultSet rs = md.getColumns(null, null, tableName, columnName);
520 rs.next();
521 int dataType = rs.getInt("DATA_TYPE");
522 String isNullable = rs.getString("IS_NULLABLE");
523 int nulls = 0;
524 if (isNullable == null) {
525 isNullable = "NO";
526 }
527 if ("YES".equals(isNullable)) {
528 nulls = 10;
529 }
530 int columnSize = rs.getInt("COLUMN_SIZE");
531 int decimalDigits = rs.getInt("DECIMAL_DIGITS");
532 if (isIntegerType(dataType)) {
533 generator = new NumberGenerator();
534 NumberGenerator numGen = (NumberGenerator) generator;
535 numGen.setNulls(nulls);
536 if (dataType == Types.BIGINT) {
537 numGen.setReturnedType("long");
538 } else if (dataType == Types.INTEGER) {
539 numGen.setReturnedType("integer");
540 } else if (dataType == Types.SMALLINT
541 || dataType == Types.TINYINT) {
542 numGen.setReturnedType("short");
543 } else if (dataType == Types.DECIMAL || dataType == Types.NUMERIC) {
544 if (decimalDigits > 0) {
545 numGen.setReturnedType("float");
546 numGen.setScale(decimalDigits);
547 } else {
548 numGen.setReturnedType("integer");
549 }
550 }
551 } else if (isBooleanType(dataType)) {
552 generator = new BooleanGenerator();
553 ((BooleanGenerator) generator).setNulls(nulls);
554 } else if (isTextType(dataType)) {
555 generator = new StringGenerator();
556 ((StringGenerator) generator).setNulls(nulls);
557 if (columnSize > 0) {
558 ((StringGenerator) generator).setMaxLength(columnSize);
559 }
560 } else if (isTimeType(dataType)) {
561 generator = new DateTimeGenerator();
562 ((DateTimeGenerator) generator).setNulls(nulls);
563 if (dataType == Types.DATE) {
564 ((DateTimeGenerator) generator).setReturnedType("date");
565 } else if (dataType == Types.TIME) {
566 ((DateTimeGenerator) generator).setReturnedType("time");
567 } else if (dataType == Types.TIMESTAMP) {
568 ((DateTimeGenerator) generator)
569 .setReturnedType("timestamp");
570 }
571 } else {
572 if (log.isWarnEnabled()) {
573 log.warn("Unknown datatype. No generator created.");
574 }
575 }
576 tx.commit();
577 } finally {
578 if (tx != null) {
579 tx.close();
580 }
581 }
582 return generator;
583 }
584
585 /***
586 * Checks if the type is one of numeric type.
587 *
588 * @param type type to check
589 *
590 * @return <code>true</code> if type accepts numbers
591 */
592 private final boolean isIntegerType(final int type) {
593 int tempType;
594
595 tempType = ExtendedTypes.getInstance().
596 getStandardAlias(properties.getProperty(
597 "dbmonster.jdbc.driver"), type);
598
599 return (tempType == Types.BIGINT)
600 || (tempType == Types.INTEGER)
601 || (tempType == Types.SMALLINT)
602 || (tempType == Types.TINYINT)
603 || (tempType == Types.DECIMAL)
604 || (tempType == Types.NUMERIC);
605 }
606
607 /***
608 * Checks if the type is one of text or character type.
609 *
610 * @param type type to check
611 *
612 * @return <code>true</code> if type accepts strings
613 */
614 private final boolean isTextType(final int type) {
615 int tempType;
616
617 tempType = ExtendedTypes.getInstance().
618 getStandardAlias(properties.getProperty(
619 "dbmonster.jdbc.driver"), type);
620
621 return (tempType == Types.CHAR)
622 || (tempType == Types.LONGVARCHAR)
623 || (tempType == Types.VARCHAR);
624 }
625 /***
626 * Checks if the type is one of text or date/time type.
627 *
628 * @param type type to check
629 *
630 * @return <code>true</code> if type accepts dates
631 */
632 private final boolean isTimeType(final int type) {
633 int tempType;
634
635 tempType = ExtendedTypes.getInstance().
636 getStandardAlias(properties.getProperty(
637 "dbmonster.jdbc.driver"), type);
638
639 return (tempType == Types.DATE)
640 || (tempType == Types.TIME)
641 || (tempType == Types.TIMESTAMP);
642 }
643
644 /***
645 * Checks if the type is boolean
646 *
647 * @param type type to check
648 *
649 * @return <code>true</code> if type is boolean
650 */
651 private final boolean isBooleanType(final int type) {
652 int tempType;
653
654 tempType = ExtendedTypes.getInstance().
655 getStandardAlias(properties.getProperty(
656 "dbmonster.jdbc.driver"), type);
657
658 return (tempType == Types.BIT)
659 || (tempType == Types.BOOLEAN);
660 }
661 }
662
663 /***
664 * Simple maps a key to a value.
665 */
666 final class PairBean {
667
668 /***
669 * Key.
670 */
671 private String key = null;
672
673 /***
674 * Value.
675 */
676 private String value = null;
677
678 /***
679 * Constructs new PairBean
680 *
681 * @param k key
682 * @param v value
683 */
684 public PairBean(final String k, final String v) {
685 key = k;
686 value = v;
687 }
688
689 /***
690 * Returns the key.
691 *
692 * @return key
693 */
694 public final String getKey() {
695 return key;
696 }
697
698 /***
699 * Sets the key.
700 *
701 * @param k key
702 */
703 public final void setKey(final String k) {
704 key = k;
705 }
706
707 /***
708 * Returns the value.
709 *
710 * @return value
711 */
712 public final String getValue() {
713 return value;
714 }
715
716 /***
717 * Sets the value
718 *
719 * @param v value to set
720 */
721 public final void setValue(final String v) {
722 value = v;
723 }
724 }