1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.mybatis.generator.internal.db;
17
18 import static org.mybatis.generator.internal.util.JavaBeansUtil.getCamelCaseString;
19 import static org.mybatis.generator.internal.util.JavaBeansUtil.getValidPropertyName;
20 import static org.mybatis.generator.internal.util.StringUtility.composeFullyQualifiedTableName;
21 import static org.mybatis.generator.internal.util.StringUtility.isTrue;
22 import static org.mybatis.generator.internal.util.StringUtility.stringContainsSQLWildcard;
23 import static org.mybatis.generator.internal.util.StringUtility.stringContainsSpace;
24 import static org.mybatis.generator.internal.util.StringUtility.stringHasValue;
25 import static org.mybatis.generator.internal.util.messages.Messages.getString;
26
27 import java.sql.DatabaseMetaData;
28 import java.sql.ResultSet;
29 import java.sql.SQLException;
30 import java.util.ArrayList;
31 import java.util.HashMap;
32 import java.util.Iterator;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.StringTokenizer;
36 import java.util.TreeMap;
37 import java.util.regex.Matcher;
38 import java.util.regex.Pattern;
39
40 import org.mybatis.generator.api.FullyQualifiedTable;
41 import org.mybatis.generator.api.IntrospectedColumn;
42 import org.mybatis.generator.api.IntrospectedTable;
43 import org.mybatis.generator.api.JavaTypeResolver;
44 import org.mybatis.generator.api.dom.java.FullyQualifiedJavaType;
45 import org.mybatis.generator.api.dom.java.JavaReservedWords;
46 import org.mybatis.generator.config.ColumnOverride;
47 import org.mybatis.generator.config.Context;
48 import org.mybatis.generator.config.GeneratedKey;
49 import org.mybatis.generator.config.PropertyRegistry;
50 import org.mybatis.generator.config.TableConfiguration;
51 import org.mybatis.generator.internal.ObjectFactory;
52 import org.mybatis.generator.logging.Log;
53 import org.mybatis.generator.logging.LogFactory;
54
55
56
57
58
59 public class DatabaseIntrospector {
60
61 private DatabaseMetaData databaseMetaData;
62 private JavaTypeResolver javaTypeResolver;
63 private List<String> warnings;
64 private Context context;
65 private Log logger;
66
67 public DatabaseIntrospector(Context context,
68 DatabaseMetaData databaseMetaData,
69 JavaTypeResolver javaTypeResolver, List<String> warnings) {
70 super();
71 this.context = context;
72 this.databaseMetaData = databaseMetaData;
73 this.javaTypeResolver = javaTypeResolver;
74 this.warnings = warnings;
75 logger = LogFactory.getLog(getClass());
76 }
77
78 private void calculatePrimaryKey(FullyQualifiedTable table,
79 IntrospectedTable introspectedTable) {
80 ResultSet rs = null;
81
82 try {
83 rs = databaseMetaData.getPrimaryKeys(
84 table.getIntrospectedCatalog(), table
85 .getIntrospectedSchema(), table
86 .getIntrospectedTableName());
87 } catch (SQLException e) {
88 closeResultSet(rs);
89 warnings.add(getString("Warning.15"));
90 return;
91 }
92
93 try {
94
95 Map<Short, String> keyColumns = new TreeMap<Short, String>();
96 while (rs.next()) {
97 String columnName = rs.getString("COLUMN_NAME");
98 short keySeq = rs.getShort("KEY_SEQ");
99 keyColumns.put(keySeq, columnName);
100 }
101
102 for (String columnName : keyColumns.values()) {
103 introspectedTable.addPrimaryKeyColumn(columnName);
104 }
105 } catch (SQLException e) {
106
107 } finally {
108 closeResultSet(rs);
109 }
110 }
111
112 private void closeResultSet(ResultSet rs) {
113 if (rs != null) {
114 try {
115 rs.close();
116 } catch (SQLException e) {
117
118 ;
119 }
120 }
121 }
122
123 private void reportIntrospectionWarnings(
124 IntrospectedTable introspectedTable,
125 TableConfiguration tableConfiguration, FullyQualifiedTable table) {
126
127
128 for (ColumnOverride columnOverride : tableConfiguration
129 .getColumnOverrides()) {
130 if (introspectedTable.getColumn(columnOverride.getColumnName()) == null) {
131 warnings.add(getString("Warning.3",
132 columnOverride.getColumnName(), table.toString()));
133 }
134 }
135
136
137
138 for (String string : tableConfiguration.getIgnoredColumnsInError()) {
139 warnings.add(getString("Warning.4",
140 string, table.toString()));
141 }
142
143 GeneratedKey generatedKey = tableConfiguration.getGeneratedKey();
144 if (generatedKey != null
145 && introspectedTable.getColumn(generatedKey.getColumn()) == null) {
146 if (generatedKey.isIdentity()) {
147 warnings.add(getString("Warning.5",
148 generatedKey.getColumn(), table.toString()));
149 } else {
150 warnings.add(getString("Warning.6",
151 generatedKey.getColumn(), table.toString()));
152 }
153 }
154
155 for (IntrospectedColumn ic : introspectedTable.getAllColumns()) {
156 if (JavaReservedWords.containsWord(ic.getJavaProperty())) {
157 warnings.add(getString("Warning.26",
158 ic.getActualColumnName(), table.toString()));
159 }
160 }
161 }
162
163
164
165
166
167
168
169
170
171 public List<IntrospectedTable> introspectTables(TableConfiguration tc)
172 throws SQLException {
173
174
175 Map<ActualTableName, List<IntrospectedColumn>> columns = getColumns(tc);
176
177 if (columns.isEmpty()) {
178 warnings.add(getString("Warning.19", tc.getCatalog(),
179 tc.getSchema(), tc.getTableName()));
180 return null;
181 }
182
183 removeIgnoredColumns(tc, columns);
184 calculateExtraColumnInformation(tc, columns);
185 applyColumnOverrides(tc, columns);
186 calculateIdentityColumns(tc, columns);
187
188 List<IntrospectedTable> introspectedTables = calculateIntrospectedTables(
189 tc, columns);
190
191
192
193
194 Iterator<IntrospectedTable> iter = introspectedTables.iterator();
195 while (iter.hasNext()) {
196 IntrospectedTable introspectedTable = iter.next();
197
198 if (!introspectedTable.hasAnyColumns()) {
199
200
201 String warning = getString(
202 "Warning.1", introspectedTable.getFullyQualifiedTable().toString());
203 warnings.add(warning);
204 iter.remove();
205 } else if (!introspectedTable.hasPrimaryKeyColumns()
206 && !introspectedTable.hasBaseColumns()) {
207
208
209 String warning = getString(
210 "Warning.18", introspectedTable.getFullyQualifiedTable().toString());
211 warnings.add(warning);
212 iter.remove();
213 } else {
214
215
216
217 reportIntrospectionWarnings(introspectedTable, tc,
218 introspectedTable.getFullyQualifiedTable());
219 }
220 }
221
222 return introspectedTables;
223 }
224
225
226
227
228
229 private void removeIgnoredColumns(TableConfiguration tc,
230 Map<ActualTableName, List<IntrospectedColumn>> columns) {
231 for (Map.Entry<ActualTableName, List<IntrospectedColumn>> entry : columns
232 .entrySet()) {
233 Iterator<IntrospectedColumn> tableColumns = (entry.getValue())
234 .iterator();
235 while (tableColumns.hasNext()) {
236 IntrospectedColumn introspectedColumn = tableColumns.next();
237 if (tc
238 .isColumnIgnored(introspectedColumn
239 .getActualColumnName())) {
240 tableColumns.remove();
241 if (logger.isDebugEnabled()) {
242 logger.debug(getString("Tracing.3",
243 introspectedColumn.getActualColumnName(), entry
244 .getKey().toString()));
245 }
246 }
247 }
248 }
249 }
250
251 private void calculateExtraColumnInformation(TableConfiguration tc,
252 Map<ActualTableName, List<IntrospectedColumn>> columns) {
253 StringBuilder sb = new StringBuilder();
254 Pattern pattern = null;
255 String replaceString = null;
256 if (tc.getColumnRenamingRule() != null) {
257 pattern = Pattern.compile(tc.getColumnRenamingRule()
258 .getSearchString());
259 replaceString = tc.getColumnRenamingRule().getReplaceString();
260 replaceString = replaceString == null ? "" : replaceString;
261 }
262
263 for (Map.Entry<ActualTableName, List<IntrospectedColumn>> entry : columns
264 .entrySet()) {
265 for (IntrospectedColumn introspectedColumn : entry.getValue()) {
266 String calculatedColumnName;
267 if (pattern == null) {
268 calculatedColumnName = introspectedColumn
269 .getActualColumnName();
270 } else {
271 Matcher matcher = pattern.matcher(introspectedColumn
272 .getActualColumnName());
273 calculatedColumnName = matcher.replaceAll(replaceString);
274 }
275
276 if (isTrue(tc
277 .getProperty(PropertyRegistry.TABLE_USE_ACTUAL_COLUMN_NAMES))) {
278 introspectedColumn.setJavaProperty(
279 getValidPropertyName(calculatedColumnName));
280 } else if (isTrue(tc
281 .getProperty(PropertyRegistry.TABLE_USE_COMPOUND_PROPERTY_NAMES))) {
282 sb.setLength(0);
283 sb.append(calculatedColumnName);
284 sb.append('_');
285 sb.append(getCamelCaseString(
286 introspectedColumn.getRemarks(), true));
287 introspectedColumn.setJavaProperty(
288 getValidPropertyName(sb.toString()));
289 } else {
290 introspectedColumn.setJavaProperty(
291 getCamelCaseString(calculatedColumnName, false));
292 }
293
294 FullyQualifiedJavaType fullyQualifiedJavaType = javaTypeResolver
295 .calculateJavaType(introspectedColumn);
296
297 if (fullyQualifiedJavaType != null) {
298 introspectedColumn
299 .setFullyQualifiedJavaType(fullyQualifiedJavaType);
300 introspectedColumn.setJdbcTypeName(javaTypeResolver
301 .calculateJdbcTypeName(introspectedColumn));
302 } else {
303
304 boolean warn = true;
305 if (tc.isColumnIgnored(introspectedColumn
306 .getActualColumnName())) {
307 warn = false;
308 }
309
310 ColumnOverride co = tc.getColumnOverride(introspectedColumn
311 .getActualColumnName());
312 if (co != null) {
313 if (stringHasValue(co.getJavaType())
314 && stringHasValue(co.getJavaType())) {
315 warn = false;
316 }
317 }
318
319
320 if (warn) {
321 introspectedColumn
322 .setFullyQualifiedJavaType(FullyQualifiedJavaType
323 .getObjectInstance());
324 introspectedColumn.setJdbcTypeName("OTHER");
325
326 String warning = getString("Warning.14",
327 Integer.toString(introspectedColumn.getJdbcType()),
328 entry.getKey().toString(),
329 introspectedColumn.getActualColumnName());
330
331 warnings.add(warning);
332 }
333 }
334
335 if (context.autoDelimitKeywords()) {
336 if (SqlReservedWords.containsWord(introspectedColumn
337 .getActualColumnName())) {
338 introspectedColumn.setColumnNameDelimited(true);
339 }
340 }
341
342 if (tc.isAllColumnDelimitingEnabled()) {
343 introspectedColumn.setColumnNameDelimited(true);
344 }
345 }
346 }
347 }
348
349 private void calculateIdentityColumns(TableConfiguration tc,
350 Map<ActualTableName, List<IntrospectedColumn>> columns) {
351 GeneratedKey gk = tc.getGeneratedKey();
352 if (gk == null) {
353
354 return;
355 }
356
357 for (Map.Entry<ActualTableName, List<IntrospectedColumn>> entry : columns
358 .entrySet()) {
359 for (IntrospectedColumn introspectedColumn : entry.getValue()) {
360 if (isMatchedColumn(introspectedColumn, gk)) {
361 if (gk.isIdentity() || gk.isJdbcStandard()) {
362 introspectedColumn.setIdentity(true);
363 introspectedColumn.setSequenceColumn(false);
364 } else {
365 introspectedColumn.setIdentity(false);
366 introspectedColumn.setSequenceColumn(true);
367 }
368 }
369 }
370 }
371 }
372
373 private boolean isMatchedColumn(IntrospectedColumn introspectedColumn, GeneratedKey gk) {
374 if (introspectedColumn.isColumnNameDelimited()) {
375 return introspectedColumn.getActualColumnName().equals(gk.getColumn());
376 } else {
377 return introspectedColumn.getActualColumnName().equalsIgnoreCase(gk.getColumn());
378 }
379 }
380
381 private void applyColumnOverrides(TableConfiguration tc,
382 Map<ActualTableName, List<IntrospectedColumn>> columns) {
383 for (Map.Entry<ActualTableName, List<IntrospectedColumn>> entry : columns
384 .entrySet()) {
385 for (IntrospectedColumn introspectedColumn : entry.getValue()) {
386 ColumnOverride columnOverride = tc
387 .getColumnOverride(introspectedColumn
388 .getActualColumnName());
389
390 if (columnOverride != null) {
391 if (logger.isDebugEnabled()) {
392 logger.debug(getString("Tracing.4",
393 introspectedColumn.getActualColumnName(), entry
394 .getKey().toString()));
395 }
396
397 if (stringHasValue(columnOverride
398 .getJavaProperty())) {
399 introspectedColumn.setJavaProperty(columnOverride
400 .getJavaProperty());
401 }
402
403 if (stringHasValue(columnOverride
404 .getJavaType())) {
405 introspectedColumn
406 .setFullyQualifiedJavaType(new FullyQualifiedJavaType(
407 columnOverride.getJavaType()));
408 }
409
410 if (stringHasValue(columnOverride
411 .getJdbcType())) {
412 introspectedColumn.setJdbcTypeName(columnOverride
413 .getJdbcType());
414 }
415
416 if (stringHasValue(columnOverride
417 .getTypeHandler())) {
418 introspectedColumn.setTypeHandler(columnOverride
419 .getTypeHandler());
420 }
421
422 if (columnOverride.isColumnNameDelimited()) {
423 introspectedColumn.setColumnNameDelimited(true);
424 }
425
426 introspectedColumn.setProperties(columnOverride
427 .getProperties());
428 }
429 }
430 }
431 }
432
433
434
435
436
437
438
439
440
441 private Map<ActualTableName, List<IntrospectedColumn>> getColumns(
442 TableConfiguration tc) throws SQLException {
443 String localCatalog;
444 String localSchema;
445 String localTableName;
446
447 boolean delimitIdentifiers = tc.isDelimitIdentifiers()
448 || stringContainsSpace(tc.getCatalog())
449 || stringContainsSpace(tc.getSchema())
450 || stringContainsSpace(tc.getTableName());
451
452 if (delimitIdentifiers) {
453 localCatalog = tc.getCatalog();
454 localSchema = tc.getSchema();
455 localTableName = tc.getTableName();
456 } else if (databaseMetaData.storesLowerCaseIdentifiers()) {
457 localCatalog = tc.getCatalog() == null ? null : tc.getCatalog()
458 .toLowerCase();
459 localSchema = tc.getSchema() == null ? null : tc.getSchema()
460 .toLowerCase();
461 localTableName = tc.getTableName() == null ? null : tc
462 .getTableName().toLowerCase();
463 } else if (databaseMetaData.storesUpperCaseIdentifiers()) {
464 localCatalog = tc.getCatalog() == null ? null : tc.getCatalog()
465 .toUpperCase();
466 localSchema = tc.getSchema() == null ? null : tc.getSchema()
467 .toUpperCase();
468 localTableName = tc.getTableName() == null ? null : tc
469 .getTableName().toUpperCase();
470 } else {
471 localCatalog = tc.getCatalog();
472 localSchema = tc.getSchema();
473 localTableName = tc.getTableName();
474 }
475
476 if (tc.isWildcardEscapingEnabled()) {
477 String escapeString = databaseMetaData.getSearchStringEscape();
478
479 StringBuilder sb = new StringBuilder();
480 StringTokenizer st;
481 if (localSchema != null) {
482 st = new StringTokenizer(localSchema, "_%", true);
483 while (st.hasMoreTokens()) {
484 String token = st.nextToken();
485 if (token.equals("_")
486 || token.equals("%")) {
487 sb.append(escapeString);
488 }
489 sb.append(token);
490 }
491 localSchema = sb.toString();
492 }
493
494 sb.setLength(0);
495 st = new StringTokenizer(localTableName, "_%", true);
496 while (st.hasMoreTokens()) {
497 String token = st.nextToken();
498 if (token.equals("_")
499 || token.equals("%")) {
500 sb.append(escapeString);
501 }
502 sb.append(token);
503 }
504 localTableName = sb.toString();
505 }
506
507 Map<ActualTableName, List<IntrospectedColumn>> answer = new HashMap<ActualTableName, List<IntrospectedColumn>>();
508
509 if (logger.isDebugEnabled()) {
510 String fullTableName = composeFullyQualifiedTableName(localCatalog, localSchema,
511 localTableName, '.');
512 logger.debug(getString("Tracing.1", fullTableName));
513 }
514
515 ResultSet rs = databaseMetaData.getColumns(localCatalog, localSchema,
516 localTableName, null);
517
518 while (rs.next()) {
519 IntrospectedColumn introspectedColumn = ObjectFactory
520 .createIntrospectedColumn(context);
521
522 introspectedColumn.setTableAlias(tc.getAlias());
523 introspectedColumn.setJdbcType(rs.getInt("DATA_TYPE"));
524 introspectedColumn.setLength(rs.getInt("COLUMN_SIZE"));
525 introspectedColumn.setActualColumnName(rs.getString("COLUMN_NAME"));
526 introspectedColumn
527 .setNullable(rs.getInt("NULLABLE") == DatabaseMetaData.columnNullable);
528 introspectedColumn.setScale(rs.getInt("DECIMAL_DIGITS"));
529 introspectedColumn.setRemarks(rs.getString("REMARKS"));
530 introspectedColumn.setDefaultValue(rs.getString("COLUMN_DEF"));
531
532 ActualTableName atn = new ActualTableName(
533 rs.getString("TABLE_CAT"),
534 rs.getString("TABLE_SCHEM"),
535 rs.getString("TABLE_NAME"));
536
537 List<IntrospectedColumn> columns = answer.get(atn);
538 if (columns == null) {
539 columns = new ArrayList<IntrospectedColumn>();
540 answer.put(atn, columns);
541 }
542
543 columns.add(introspectedColumn);
544
545 if (logger.isDebugEnabled()) {
546 logger.debug(getString(
547 "Tracing.2",
548 introspectedColumn.getActualColumnName(), Integer
549 .toString(introspectedColumn.getJdbcType()),
550 atn.toString()));
551 }
552 }
553
554 closeResultSet(rs);
555
556 if (answer.size() > 1
557 && !stringContainsSQLWildcard(localSchema)
558 && !stringContainsSQLWildcard(localTableName)) {
559
560
561 ActualTableName inputAtn = new ActualTableName(tc.getCatalog(), tc
562 .getSchema(), tc.getTableName());
563
564 StringBuilder sb = new StringBuilder();
565 boolean comma = false;
566 for (ActualTableName atn : answer.keySet()) {
567 if (comma) {
568 sb.append(',');
569 } else {
570 comma = true;
571 }
572 sb.append(atn.toString());
573 }
574
575 warnings.add(getString("Warning.25",
576 inputAtn.toString(), sb.toString()));
577 }
578
579 return answer;
580 }
581
582 private List<IntrospectedTable> calculateIntrospectedTables(
583 TableConfiguration tc,
584 Map<ActualTableName, List<IntrospectedColumn>> columns) {
585 boolean delimitIdentifiers = tc.isDelimitIdentifiers()
586 || stringContainsSpace(tc.getCatalog())
587 || stringContainsSpace(tc.getSchema())
588 || stringContainsSpace(tc.getTableName());
589
590 List<IntrospectedTable> answer = new ArrayList<IntrospectedTable>();
591
592 for (Map.Entry<ActualTableName, List<IntrospectedColumn>> entry : columns
593 .entrySet()) {
594 ActualTableName atn = entry.getKey();
595
596
597
598
599
600
601
602
603 FullyQualifiedTable table = new FullyQualifiedTable(
604 stringHasValue(tc.getCatalog()) ? atn
605 .getCatalog() : null,
606 stringHasValue(tc.getSchema()) ? atn
607 .getSchema() : null,
608 atn.getTableName(),
609 tc.getDomainObjectName(),
610 tc.getAlias(),
611 isTrue(tc.getProperty(PropertyRegistry.TABLE_IGNORE_QUALIFIERS_AT_RUNTIME)),
612 tc.getProperty(PropertyRegistry.TABLE_RUNTIME_CATALOG),
613 tc.getProperty(PropertyRegistry.TABLE_RUNTIME_SCHEMA),
614 tc.getProperty(PropertyRegistry.TABLE_RUNTIME_TABLE_NAME),
615 delimitIdentifiers, context);
616
617 IntrospectedTable introspectedTable = ObjectFactory
618 .createIntrospectedTable(tc, table, context);
619
620 for (IntrospectedColumn introspectedColumn : entry.getValue()) {
621 introspectedTable.addColumn(introspectedColumn);
622 }
623
624 calculatePrimaryKey(table, introspectedTable);
625
626 answer.add(introspectedTable);
627 }
628
629 return answer;
630 }
631 }