View Javadoc
1   /**
2    *    Copyright 2006-2016 the original author or authors.
3    *
4    *    Licensed under the Apache License, Version 2.0 (the "License");
5    *    you may not use this file except in compliance with the License.
6    *    You may obtain a copy of the License at
7    *
8    *       http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *    Unless required by applicable law or agreed to in writing, software
11   *    distributed under the License is distributed on an "AS IS" BASIS,
12   *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *    See the License for the specific language governing permissions and
14   *    limitations under the License.
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   * The Class DatabaseIntrospector.
57   *
58   * @author Jeff Butler
59   */
60  public class DatabaseIntrospector {
61  
62      /** The database meta data. */
63      private DatabaseMetaData databaseMetaData;
64      
65      /** The java type resolver. */
66      private JavaTypeResolver javaTypeResolver;
67      
68      /** The warnings. */
69      private List<String> warnings;
70      
71      /** The context. */
72      private Context context;
73      
74      /** The logger. */
75      private Log logger;
76  
77      /**
78       * Instantiates a new database introspector.
79       *
80       * @param context
81       *            the context
82       * @param databaseMetaData
83       *            the database meta data
84       * @param javaTypeResolver
85       *            the java type resolver
86       * @param warnings
87       *            the warnings
88       */
89      public DatabaseIntrospector(Context context,
90              DatabaseMetaData databaseMetaData,
91              JavaTypeResolver javaTypeResolver, List<String> warnings) {
92          super();
93          this.context = context;
94          this.databaseMetaData = databaseMetaData;
95          this.javaTypeResolver = javaTypeResolver;
96          this.warnings = warnings;
97          logger = LogFactory.getLog(getClass());
98      }
99  
100     /**
101      * Calculate primary key.
102      *
103      * @param table
104      *            the table
105      * @param introspectedTable
106      *            the introspected table
107      */
108     private void calculatePrimaryKey(FullyQualifiedTable table,
109             IntrospectedTable introspectedTable) {
110         ResultSet rs = null;
111 
112         try {
113             rs = databaseMetaData.getPrimaryKeys(
114                     table.getIntrospectedCatalog(), table
115                             .getIntrospectedSchema(), table
116                             .getIntrospectedTableName());
117         } catch (SQLException e) {
118             closeResultSet(rs);
119             warnings.add(getString("Warning.15")); //$NON-NLS-1$
120             return;
121         }
122 
123         try {
124             // keep primary columns in key sequence order
125             Map<Short, String> keyColumns = new TreeMap<Short, String>();
126             while (rs.next()) {
127                 String columnName = rs.getString("COLUMN_NAME"); //$NON-NLS-1$
128                 short keySeq = rs.getShort("KEY_SEQ"); //$NON-NLS-1$
129                 keyColumns.put(keySeq, columnName);
130             }
131             
132             for (String columnName : keyColumns.values()) {
133                 introspectedTable.addPrimaryKeyColumn(columnName);
134             }
135         } catch (SQLException e) {
136             // ignore the primary key if there's any error
137         } finally {
138             closeResultSet(rs);
139         }
140     }
141 
142     /**
143      * Close result set.
144      *
145      * @param rs
146      *            the rs
147      */
148     private void closeResultSet(ResultSet rs) {
149         if (rs != null) {
150             try {
151                 rs.close();
152             } catch (SQLException e) {
153                 // ignore
154             }
155         }
156     }
157 
158     /**
159      * Report introspection warnings.
160      *
161      * @param introspectedTable
162      *            the introspected table
163      * @param tableConfiguration
164      *            the table configuration
165      * @param table
166      *            the table
167      */
168     private void reportIntrospectionWarnings(
169             IntrospectedTable introspectedTable,
170             TableConfiguration tableConfiguration, FullyQualifiedTable table) {
171         // make sure that every column listed in column overrides
172         // actually exists in the table
173         for (ColumnOverride columnOverride : tableConfiguration
174                 .getColumnOverrides()) {
175             if (introspectedTable.getColumn(columnOverride.getColumnName()) == null) {
176                 warnings.add(getString("Warning.3", //$NON-NLS-1$
177                         columnOverride.getColumnName(), table.toString()));
178             }
179         }
180 
181         // make sure that every column listed in ignored columns
182         // actually exists in the table
183         for (String string : tableConfiguration.getIgnoredColumnsInError()) {
184             warnings.add(getString("Warning.4", //$NON-NLS-1$
185                     string, table.toString()));
186         }
187 
188         GeneratedKey generatedKey = tableConfiguration.getGeneratedKey();
189         if (generatedKey != null
190                 && introspectedTable.getColumn(generatedKey.getColumn()) == null) {
191             if (generatedKey.isIdentity()) {
192                 warnings.add(getString("Warning.5", //$NON-NLS-1$
193                         generatedKey.getColumn(), table.toString()));
194             } else {
195                 warnings.add(getString("Warning.6", //$NON-NLS-1$
196                         generatedKey.getColumn(), table.toString()));
197             }
198         }
199         
200         for (IntrospectedColumn ic : introspectedTable.getAllColumns()) {
201             if (JavaReservedWords.containsWord(ic.getJavaProperty())) {
202                 warnings.add(getString("Warning.26", //$NON-NLS-1$
203                         ic.getActualColumnName(), table.toString()));
204             }
205         }
206     }
207 
208     /**
209      * Returns a List of IntrospectedTable elements that matches the specified table configuration.
210      *
211      * @param tc
212      *            the tc
213      * @return a list of introspected tables
214      * @throws SQLException
215      *             the SQL exception
216      */
217     public List<IntrospectedTable> introspectTables(TableConfiguration tc)
218             throws SQLException {
219 
220         // get the raw columns from the DB
221         Map<ActualTableName, List<IntrospectedColumn>> columns = getColumns(tc);
222 
223         if (columns.isEmpty()) {
224             warnings.add(getString("Warning.19", tc.getCatalog(), //$NON-NLS-1$
225                     tc.getSchema(), tc.getTableName()));
226             return null;
227         }
228 
229         removeIgnoredColumns(tc, columns);
230         calculateExtraColumnInformation(tc, columns);
231         applyColumnOverrides(tc, columns);
232         calculateIdentityColumns(tc, columns);
233 
234         List<IntrospectedTable> introspectedTables = calculateIntrospectedTables(
235                 tc, columns);
236 
237         // now introspectedTables has all the columns from all the
238         // tables in the configuration. Do some validation...
239 
240         Iterator<IntrospectedTable> iter = introspectedTables.iterator();
241         while (iter.hasNext()) {
242             IntrospectedTable introspectedTable = iter.next();
243 
244             if (!introspectedTable.hasAnyColumns()) {
245                 // add warning that the table has no columns, remove from the
246                 // list
247                 String warning = getString(
248                                 "Warning.1", introspectedTable.getFullyQualifiedTable().toString()); //$NON-NLS-1$
249                 warnings.add(warning);
250                 iter.remove();
251             } else if (!introspectedTable.hasPrimaryKeyColumns()
252                     && !introspectedTable.hasBaseColumns()) {
253                 // add warning that the table has only BLOB columns, remove from
254                 // the list
255                 String warning = getString(
256                                 "Warning.18", introspectedTable.getFullyQualifiedTable().toString()); //$NON-NLS-1$ 
257                 warnings.add(warning);
258                 iter.remove();
259             } else {
260                 // now make sure that all columns called out in the
261                 // configuration
262                 // actually exist
263                 reportIntrospectionWarnings(introspectedTable, tc,
264                         introspectedTable.getFullyQualifiedTable());
265             }
266         }
267 
268         return introspectedTables;
269     }
270 
271     /**
272      * Removes the ignored columns.
273      *
274      * @param tc
275      *            the tc
276      * @param columns
277      *            the columns
278      */
279     private void removeIgnoredColumns(TableConfiguration tc,
280             Map<ActualTableName, List<IntrospectedColumn>> columns) {
281         for (Map.Entry<ActualTableName, List<IntrospectedColumn>> entry : columns
282                 .entrySet()) {
283             Iterator<IntrospectedColumn> tableColumns = entry.getValue()
284                     .iterator();
285             while (tableColumns.hasNext()) {
286                 IntrospectedColumn introspectedColumn = tableColumns.next();
287                 if (tc
288                         .isColumnIgnored(introspectedColumn
289                                 .getActualColumnName())) {
290                     tableColumns.remove();
291                     if (logger.isDebugEnabled()) {
292                         logger.debug(getString("Tracing.3", //$NON-NLS-1$
293                                 introspectedColumn.getActualColumnName(), entry
294                                         .getKey().toString()));
295                     }
296                 }
297             }
298         }
299     }
300 
301     /**
302      * Calculate extra column information.
303      *
304      * @param tc
305      *            the tc
306      * @param columns
307      *            the columns
308      */
309     private void calculateExtraColumnInformation(TableConfiguration tc,
310             Map<ActualTableName, List<IntrospectedColumn>> columns) {
311         StringBuilder sb = new StringBuilder();
312         Pattern pattern = null;
313         String replaceString = null;
314         if (tc.getColumnRenamingRule() != null) {
315             pattern = Pattern.compile(tc.getColumnRenamingRule()
316                     .getSearchString());
317             replaceString = tc.getColumnRenamingRule().getReplaceString();
318             replaceString = replaceString == null ? "" : replaceString; //$NON-NLS-1$
319         }
320 
321         for (Map.Entry<ActualTableName, List<IntrospectedColumn>> entry : columns
322                 .entrySet()) {
323             for (IntrospectedColumn introspectedColumn : entry.getValue()) {
324                 String calculatedColumnName;
325                 if (pattern == null) {
326                     calculatedColumnName = introspectedColumn
327                             .getActualColumnName();
328                 } else {
329                     Matcher matcher = pattern.matcher(introspectedColumn
330                             .getActualColumnName());
331                     calculatedColumnName = matcher.replaceAll(replaceString);
332                 }
333 
334                 if (isTrue(tc
335                         .getProperty(PropertyRegistry.TABLE_USE_ACTUAL_COLUMN_NAMES))) {
336                     introspectedColumn.setJavaProperty(
337                             getValidPropertyName(calculatedColumnName));
338                 } else if (isTrue(tc
339                                 .getProperty(PropertyRegistry.TABLE_USE_COMPOUND_PROPERTY_NAMES))) {
340                     sb.setLength(0);
341                     sb.append(calculatedColumnName);
342                     sb.append('_');
343                     sb.append(getCamelCaseString(
344                             introspectedColumn.getRemarks(), true));
345                     introspectedColumn.setJavaProperty(
346                             getValidPropertyName(sb.toString()));
347                 } else {
348                     introspectedColumn.setJavaProperty(
349                             getCamelCaseString(calculatedColumnName, false));
350                 }
351 
352                 FullyQualifiedJavaType fullyQualifiedJavaType = javaTypeResolver
353                         .calculateJavaType(introspectedColumn);
354 
355                 if (fullyQualifiedJavaType != null) {
356                     introspectedColumn
357                             .setFullyQualifiedJavaType(fullyQualifiedJavaType);
358                     introspectedColumn.setJdbcTypeName(javaTypeResolver
359                             .calculateJdbcTypeName(introspectedColumn));
360                 } else {
361                     // type cannot be resolved. Check for ignored or overridden
362                     boolean warn = true;
363                     if (tc.isColumnIgnored(introspectedColumn
364                             .getActualColumnName())) {
365                         warn = false;
366                     }
367 
368                     ColumnOverride co = tc.getColumnOverride(introspectedColumn
369                             .getActualColumnName());
370                     if (co != null
371                             && stringHasValue(co.getJavaType())
372                             && stringHasValue(co.getJavaType())) {
373                         warn = false;
374                     }
375 
376                     // if the type is not supported, then we'll report a warning
377                     if (warn) {
378                         introspectedColumn
379                                 .setFullyQualifiedJavaType(FullyQualifiedJavaType
380                                         .getObjectInstance());
381                         introspectedColumn.setJdbcTypeName("OTHER"); //$NON-NLS-1$
382 
383                         String warning = getString("Warning.14", //$NON-NLS-1$
384                                 Integer.toString(introspectedColumn.getJdbcType()),
385                                 entry.getKey().toString(),
386                                 introspectedColumn.getActualColumnName());
387 
388                         warnings.add(warning);
389                     }
390                 }
391 
392                 if (context.autoDelimitKeywords()
393                     && SqlReservedWords.containsWord(introspectedColumn
394                             .getActualColumnName())) {
395                     introspectedColumn.setColumnNameDelimited(true);
396                 }
397 
398                 if (tc.isAllColumnDelimitingEnabled()) {
399                     introspectedColumn.setColumnNameDelimited(true);
400                 }
401             }
402         }
403     }
404 
405     /**
406      * Calculate identity columns.
407      *
408      * @param tc
409      *            the tc
410      * @param columns
411      *            the columns
412      */
413     private void calculateIdentityColumns(TableConfiguration tc,
414             Map<ActualTableName, List<IntrospectedColumn>> columns) {
415         GeneratedKey gk = tc.getGeneratedKey();
416         if (gk == null) {
417             // no generated key, then no identity or sequence columns
418             return;
419         }
420         
421         for (Map.Entry<ActualTableName, List<IntrospectedColumn>> entry : columns
422                 .entrySet()) {
423             for (IntrospectedColumn introspectedColumn : entry.getValue()) {
424                 if (isMatchedColumn(introspectedColumn, gk)) {
425                     if (gk.isIdentity() || gk.isJdbcStandard()) {
426                         introspectedColumn.setIdentity(true);
427                         introspectedColumn.setSequenceColumn(false);
428                     } else {
429                         introspectedColumn.setIdentity(false);
430                         introspectedColumn.setSequenceColumn(true);
431                     }
432                 }
433             }
434         }
435     }
436     
437     /**
438      * Checks if is matched column.
439      *
440      * @param introspectedColumn
441      *            the introspected column
442      * @param gk
443      *            the gk
444      * @return true, if is matched column
445      */
446     private boolean isMatchedColumn(IntrospectedColumn introspectedColumn, GeneratedKey gk) {
447         if (introspectedColumn.isColumnNameDelimited()) {
448             return introspectedColumn.getActualColumnName().equals(gk.getColumn());
449         } else {
450             return introspectedColumn.getActualColumnName().equalsIgnoreCase(gk.getColumn());
451         }
452     }
453 
454     /**
455      * Apply column overrides.
456      *
457      * @param tc
458      *            the tc
459      * @param columns
460      *            the columns
461      */
462     private void applyColumnOverrides(TableConfiguration tc,
463             Map<ActualTableName, List<IntrospectedColumn>> columns) {
464         for (Map.Entry<ActualTableName, List<IntrospectedColumn>> entry : columns
465                 .entrySet()) {
466             for (IntrospectedColumn introspectedColumn : entry.getValue()) {
467                 ColumnOverride columnOverride = tc
468                         .getColumnOverride(introspectedColumn
469                                 .getActualColumnName());
470 
471                 if (columnOverride != null) {
472                     if (logger.isDebugEnabled()) {
473                         logger.debug(getString("Tracing.4", //$NON-NLS-1$
474                                 introspectedColumn.getActualColumnName(), entry
475                                         .getKey().toString()));
476                     }
477 
478                     if (stringHasValue(columnOverride
479                             .getJavaProperty())) {
480                         introspectedColumn.setJavaProperty(columnOverride
481                                 .getJavaProperty());
482                     }
483 
484                     if (stringHasValue(columnOverride
485                             .getJavaType())) {
486                         introspectedColumn
487                                 .setFullyQualifiedJavaType(new FullyQualifiedJavaType(
488                                         columnOverride.getJavaType()));
489                     }
490 
491                     if (stringHasValue(columnOverride
492                             .getJdbcType())) {
493                         introspectedColumn.setJdbcTypeName(columnOverride
494                                 .getJdbcType());
495                     }
496 
497                     if (stringHasValue(columnOverride
498                             .getTypeHandler())) {
499                         introspectedColumn.setTypeHandler(columnOverride
500                                 .getTypeHandler());
501                     }
502 
503                     if (columnOverride.isColumnNameDelimited()) {
504                         introspectedColumn.setColumnNameDelimited(true);
505                     }
506 
507                     introspectedColumn.setProperties(columnOverride
508                             .getProperties());
509                 }
510             }
511         }
512     }
513 
514     /**
515      * This method returns a Map<ActualTableName, List<ColumnDefinitions>> of columns returned from the database
516      * introspection.
517      *
518      * @param tc
519      *            the tc
520      * @return introspected columns
521      * @throws SQLException
522      *             the SQL exception
523      */
524     private Map<ActualTableName, List<IntrospectedColumn>> getColumns(
525             TableConfiguration tc) throws SQLException {
526         String localCatalog;
527         String localSchema;
528         String localTableName;
529 
530         boolean delimitIdentifiers = tc.isDelimitIdentifiers()
531                 || stringContainsSpace(tc.getCatalog())
532                 || stringContainsSpace(tc.getSchema())
533                 || stringContainsSpace(tc.getTableName());
534 
535         if (delimitIdentifiers) {
536             localCatalog = tc.getCatalog();
537             localSchema = tc.getSchema();
538             localTableName = tc.getTableName();
539         } else if (databaseMetaData.storesLowerCaseIdentifiers()) {
540             localCatalog = tc.getCatalog() == null ? null : tc.getCatalog()
541                     .toLowerCase();
542             localSchema = tc.getSchema() == null ? null : tc.getSchema()
543                     .toLowerCase();
544             localTableName = tc.getTableName() == null ? null : tc
545                     .getTableName().toLowerCase();
546         } else if (databaseMetaData.storesUpperCaseIdentifiers()) {
547             localCatalog = tc.getCatalog() == null ? null : tc.getCatalog()
548                     .toUpperCase();
549             localSchema = tc.getSchema() == null ? null : tc.getSchema()
550                     .toUpperCase();
551             localTableName = tc.getTableName() == null ? null : tc
552                     .getTableName().toUpperCase();
553         } else {
554             localCatalog = tc.getCatalog();
555             localSchema = tc.getSchema();
556             localTableName = tc.getTableName();
557         }
558 
559         if (tc.isWildcardEscapingEnabled()) {
560             String escapeString = databaseMetaData.getSearchStringEscape();
561 
562             StringBuilder sb = new StringBuilder();
563             StringTokenizer st;
564             if (localSchema != null) {
565                 st = new StringTokenizer(localSchema, "_%", true); //$NON-NLS-1$
566                 while (st.hasMoreTokens()) {
567                     String token = st.nextToken();
568                     if (token.equals("_") //$NON-NLS-1$
569                             || token.equals("%")) { //$NON-NLS-1$
570                         sb.append(escapeString);
571                     }
572                     sb.append(token);
573                 }
574                 localSchema = sb.toString();
575             }
576 
577             sb.setLength(0);
578             st = new StringTokenizer(localTableName, "_%", true); //$NON-NLS-1$
579             while (st.hasMoreTokens()) {
580                 String token = st.nextToken();
581                 if (token.equals("_") //$NON-NLS-1$
582                         || token.equals("%")) { //$NON-NLS-1$
583                     sb.append(escapeString);
584                 }
585                 sb.append(token);
586             }
587             localTableName = sb.toString();
588         }
589 
590         Map<ActualTableName, List<IntrospectedColumn>> answer = new HashMap<ActualTableName, List<IntrospectedColumn>>();
591 
592         if (logger.isDebugEnabled()) {
593             String fullTableName = composeFullyQualifiedTableName(localCatalog, localSchema,
594                             localTableName, '.');
595             logger.debug(getString("Tracing.1", fullTableName)); //$NON-NLS-1$
596         }
597 
598         ResultSet rs = databaseMetaData.getColumns(localCatalog, localSchema,
599                 localTableName, null);
600 
601         while (rs.next()) {
602             IntrospectedColumn introspectedColumn = ObjectFactory
603                     .createIntrospectedColumn(context);
604 
605             introspectedColumn.setTableAlias(tc.getAlias());
606             introspectedColumn.setJdbcType(rs.getInt("DATA_TYPE")); //$NON-NLS-1$
607             introspectedColumn.setLength(rs.getInt("COLUMN_SIZE")); //$NON-NLS-1$
608             introspectedColumn.setActualColumnName(rs.getString("COLUMN_NAME")); //$NON-NLS-1$
609             introspectedColumn
610                     .setNullable(rs.getInt("NULLABLE") == DatabaseMetaData.columnNullable); //$NON-NLS-1$
611             introspectedColumn.setScale(rs.getInt("DECIMAL_DIGITS")); //$NON-NLS-1$
612             introspectedColumn.setRemarks(rs.getString("REMARKS")); //$NON-NLS-1$
613             introspectedColumn.setDefaultValue(rs.getString("COLUMN_DEF")); //$NON-NLS-1$
614 
615             ActualTableName atn = new ActualTableName(
616                     rs.getString("TABLE_CAT"), //$NON-NLS-1$
617                     rs.getString("TABLE_SCHEM"), //$NON-NLS-1$
618                     rs.getString("TABLE_NAME")); //$NON-NLS-1$
619 
620             List<IntrospectedColumn> columns = answer.get(atn);
621             if (columns == null) {
622                 columns = new ArrayList<IntrospectedColumn>();
623                 answer.put(atn, columns);
624             }
625 
626             columns.add(introspectedColumn);
627 
628             if (logger.isDebugEnabled()) {
629                 logger.debug(getString(
630                         "Tracing.2", //$NON-NLS-1$
631                         introspectedColumn.getActualColumnName(), Integer
632                                 .toString(introspectedColumn.getJdbcType()),
633                         atn.toString()));
634             }
635         }
636 
637         closeResultSet(rs);
638 
639         if (answer.size() > 1
640                 && !stringContainsSQLWildcard(localSchema)
641                 && !stringContainsSQLWildcard(localTableName)) {
642             // issue a warning if there is more than one table and
643             // no wildcards were used
644             ActualTableName inputAtn = new ActualTableName(tc.getCatalog(), tc
645                     .getSchema(), tc.getTableName());
646 
647             StringBuilder sb = new StringBuilder();
648             boolean comma = false;
649             for (ActualTableName atn : answer.keySet()) {
650                 if (comma) {
651                     sb.append(',');
652                 } else {
653                     comma = true;
654                 }
655                 sb.append(atn.toString());
656             }
657 
658             warnings.add(getString("Warning.25", //$NON-NLS-1$
659                     inputAtn.toString(), sb.toString()));
660         }
661 
662         return answer;
663     }
664 
665     /**
666      * Calculate introspected tables.
667      *
668      * @param tc
669      *            the tc
670      * @param columns
671      *            the columns
672      * @return the list
673      */
674     private List<IntrospectedTable> calculateIntrospectedTables(
675             TableConfiguration tc,
676             Map<ActualTableName, List<IntrospectedColumn>> columns) {
677         boolean delimitIdentifiers = tc.isDelimitIdentifiers()
678                 || stringContainsSpace(tc.getCatalog())
679                 || stringContainsSpace(tc.getSchema())
680                 || stringContainsSpace(tc.getTableName());
681 
682         List<IntrospectedTable> answer = new ArrayList<IntrospectedTable>();
683 
684         for (Map.Entry<ActualTableName, List<IntrospectedColumn>> entry : columns
685                 .entrySet()) {
686             ActualTableName atn = entry.getKey();
687 
688             // we only use the returned catalog and schema if something was
689             // actually
690             // specified on the table configuration. If something was returned
691             // from the DB for these fields, but nothing was specified on the
692             // table
693             // configuration, then some sort of DB default is being returned
694             // and we don't want that in our SQL
695             FullyQualifiedTable table = new FullyQualifiedTable(
696                     stringHasValue(tc.getCatalog()) ? atn
697                             .getCatalog() : null,
698                     stringHasValue(tc.getSchema()) ? atn
699                             .getSchema() : null,
700                     atn.getTableName(),
701                     tc.getDomainObjectName(),
702                     tc.getAlias(),
703                     isTrue(tc.getProperty(PropertyRegistry.TABLE_IGNORE_QUALIFIERS_AT_RUNTIME)),
704                     tc.getProperty(PropertyRegistry.TABLE_RUNTIME_CATALOG),
705                     tc.getProperty(PropertyRegistry.TABLE_RUNTIME_SCHEMA),
706                     tc.getProperty(PropertyRegistry.TABLE_RUNTIME_TABLE_NAME),
707                     delimitIdentifiers, context);
708 
709             IntrospectedTable introspectedTable = ObjectFactory
710                     .createIntrospectedTable(tc, table, context);
711 
712             for (IntrospectedColumn introspectedColumn : entry.getValue()) {
713                 introspectedTable.addColumn(introspectedColumn);
714             }
715 
716             calculatePrimaryKey(table, introspectedTable);
717             
718             enhanceIntrospectedTable(introspectedTable);
719 
720             answer.add(introspectedTable);
721         }
722 
723         return answer;
724     }
725 
726     /**
727      * This method calls database metadata to retrieve some extra information about the table
728      * such as remarks associated with the table and the type.
729      * 
730      * If there is any error, we just add a warning and continue.
731      * 
732      * @param introspectedTable
733      */
734     private void enhanceIntrospectedTable(IntrospectedTable introspectedTable) {
735         try {
736             FullyQualifiedTable fqt = introspectedTable.getFullyQualifiedTable();
737 
738             ResultSet rs = databaseMetaData.getTables(fqt.getIntrospectedCatalog(), fqt.getIntrospectedSchema(),
739                     fqt.getIntrospectedTableName(), null);
740             if (rs.next()) {
741                 String remarks = rs.getString("REMARKS"); //$NON-NLS-1$
742                 String tableType = rs.getString("TABLE_TYPE"); //$NON-NLS-1$
743                 introspectedTable.setRemarks(remarks);
744                 introspectedTable.setTableType(tableType);
745             }
746             closeResultSet(rs);
747         } catch (SQLException e) {
748             warnings.add(getString("Warning.27", e.getMessage())); //$NON-NLS-1$
749         }
750     }
751 }