View Javadoc
1   /*
2    *
3    * The DbUnit Database Testing Framework
4    * Copyright (C)2005, DbUnit.org
5    *
6    * This library is free software; you can redistribute it and/or
7    * modify it under the terms of the GNU Lesser General Public
8    * License as published by the Free Software Foundation; either
9    * version 2.1 of the License, or (at your option) any later version.
10   *
11   * This library is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14   * Lesser General Public License for more details.
15   *
16   * You should have received a copy of the GNU Lesser General Public
17   * License along with this library; if not, write to the Free Software
18   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19   *
20   */
21  
22  package org.dbunit.util;
23  
24  import java.io.PrintStream;
25  import java.sql.Connection;
26  import java.sql.DatabaseMetaData;
27  import java.sql.ResultSet;
28  import java.sql.SQLException;
29  import java.sql.Statement;
30  import java.util.Locale;
31  
32  import org.dbunit.DatabaseUnitRuntimeException;
33  import org.dbunit.database.IMetadataHandler;
34  import org.dbunit.dataset.Column;
35  import org.dbunit.dataset.datatype.DataType;
36  import org.dbunit.dataset.datatype.DataTypeException;
37  import org.dbunit.dataset.datatype.IDataTypeFactory;
38  import org.slf4j.Logger;
39  import org.slf4j.LoggerFactory;
40  
41  /**
42   * Helper for SQL-related stuff.
43   * <br>
44   * TODO: testcases, also think about refactoring so that methods are not static anymore (for better extensibility)
45   * @author Felipe Leme (dbunit@felipeal.net)
46   * @version $Revision$
47   * @since Nov 5, 2005
48   * 
49   */
50  public class SQLHelper {
51  
52      /**
53       * The database product name reported by Sybase JDBC drivers.
54       */
55      public static final String DB_PRODUCT_SYBASE = "Sybase";
56  
57      /**
58       * Logger for this class
59       */
60      private static final Logger logger = LoggerFactory.getLogger(SQLHelper.class);
61  
62      // class is "static"
63      private SQLHelper() {}
64  
65      /**
66       * Gets the primary column for a table.
67       * @param conn connection with the database
68       * @param table table name
69       * @return name of primary column for a table (assuming it's just 1 column).
70       * @throws SQLException raised while getting the meta data
71       */
72      public static String getPrimaryKeyColumn( Connection conn, String table ) throws SQLException {
73          logger.debug("getPrimaryKeyColumn(conn={}, table={}) - start", conn, table);
74  
75          DatabaseMetaData metadata = conn.getMetaData();
76          ResultSet rs = metadata.getPrimaryKeys( null, null, table );
77          rs.next();
78          String pkColumn = rs.getString(4);
79          return pkColumn;
80      }
81  
82      /**
83       * Close a result set and a prepared statement, checking for null references.
84       * @param rs result set to be closed
85       * @param stmt prepared statement to be closed
86       * @throws SQLException exception raised in either close() method
87       */
88      public static void close(ResultSet rs, Statement stmt) throws SQLException {
89          logger.debug("close(rs={}, stmt={}) - start", rs, stmt);
90  
91          try {
92              SQLHelper.close(rs);
93          } finally {
94              SQLHelper.close( stmt );
95          }
96      }
97  
98      /**
99       * Close a SQL statement, checking for null references.
100      * @param stmt statement to be closed
101      * @throws SQLException exception raised while closing the statement
102      */
103     public static void close(Statement stmt) throws SQLException {
104         logger.debug("close(stmt={}) - start", stmt);
105 
106         if ( stmt != null ) {
107             stmt.close();
108         }
109     }
110 
111     /**
112      * Closes the given result set in a null-safe way
113      * @param resultSet
114      * @throws SQLException
115      */
116     public static void close(ResultSet resultSet) throws SQLException {
117         logger.debug("close(resultSet={}) - start", resultSet);
118 
119         if(resultSet != null) {
120             resultSet.close();
121         }
122     }
123 
124     /**
125      * Returns <code>true</code> if the given schema exists for the given connection.
126      * @param connection The connection to a database
127      * @param schema The schema to be searched
128      * @return Returns <code>true</code> if the given schema exists for the given connection.
129      * @throws SQLException
130      * @since 2.3.0
131      */
132     public static boolean schemaExists(Connection connection, String schema)
133             throws SQLException
134             {
135         logger.trace("schemaExists(connection={}, schema={}) - start", connection, schema);
136 
137         if(schema == null)
138         {
139             throw new NullPointerException("The parameter 'schema' must not be null");
140         }
141 
142         DatabaseMetaData metaData = connection.getMetaData();
143         ResultSet rs = metaData.getSchemas(); //null, schemaPattern);
144         try
145         {
146             while(rs.next())
147             {
148                 String foundSchema = rs.getString("TABLE_SCHEM");
149                 if(foundSchema.equals(schema))
150                 {
151                     return true;
152                 }
153             }
154 
155             // Especially for MySQL check the catalog
156             if(catalogExists(connection, schema))
157             {
158                 logger.debug("Found catalog with name {}. Returning true because DB is probably on MySQL", schema);
159                 return true;
160             }
161 
162             return false;
163         }
164         finally
165         {
166             rs.close();
167         }
168             }
169 
170     /**
171      * Checks via {@link DatabaseMetaData#getCatalogs()} whether or not the given catalog exists.
172      * @param connection
173      * @param catalog
174      * @return
175      * @throws SQLException
176      * @since 2.4.4
177      */
178     private static boolean catalogExists(Connection connection, String catalog) throws SQLException
179     {
180         logger.trace("catalogExists(connection={}, catalog={}) - start", connection, catalog);
181 
182         if(catalog == null)
183         {
184             throw new NullPointerException("The parameter 'catalog' must not be null");
185         }
186 
187         DatabaseMetaData metaData = connection.getMetaData();
188         ResultSet rs = metaData.getCatalogs();
189         try
190         {
191             while(rs.next())
192             {
193                 String foundCatalog = rs.getString("TABLE_CAT");
194                 if(foundCatalog.equals(catalog))
195                 {
196                     return true;
197                 }
198             }
199             return false;
200         }
201         finally
202         {
203             rs.close();
204         }
205 
206     }
207 
208     /**
209      * Checks if the given table exists.
210      * @param metaData The database meta data
211      * @param schema The schema in which the table should be searched. If <code>null</code>
212      * the schema is not used to narrow the table name.
213      * @param tableName The table name to be searched
214      * @return Returns <code>true</code> if the given table exists in the given schema.
215      * Else returns <code>false</code>.
216      * @throws SQLException
217      * @since 2.3.0
218      * @deprecated since 2.4.5 - use {@link IMetadataHandler#tableExists(DatabaseMetaData, String, String)}
219      */
220     public static boolean tableExists(DatabaseMetaData metaData, String schema,
221             String tableName)
222                     throws SQLException
223                     {
224         ResultSet tableRs = metaData.getTables(null, schema, tableName, null);
225         try
226         {
227             return tableRs.next();
228         }
229         finally
230         {
231             SQLHelper.close(tableRs);
232         }
233                     }
234 
235     /**
236      * Utility method for debugging to print all tables of the given metadata on the given stream
237      * @param metaData
238      * @param outputStream
239      * @throws SQLException
240      */
241     public static void printAllTables(DatabaseMetaData metaData, PrintStream outputStream) throws SQLException
242     {
243         ResultSet rs = metaData.getTables(null, null, null, null);
244         try
245         {
246             while (rs.next())
247             {
248                 String catalog = rs.getString("TABLE_CAT");
249                 String schema = rs.getString("TABLE_SCHEM");
250                 String table = rs.getString("TABLE_NAME");
251                 StringBuffer tableInfo = new StringBuffer();
252      			if(catalog!=null) tableInfo.append(catalog).append(".");
253      			if(schema!=null) tableInfo.append(schema).append(".");
254                 tableInfo.append(table);
255                 // Print the info
256                 outputStream.println(tableInfo);
257             }
258             outputStream.flush();
259         }
260         finally
261         {
262             SQLHelper.close(rs);
263         }
264 
265     }
266 
267     /**
268      * Returns the database and JDBC driver information as pretty formatted string
269      * @param metaData The JDBC database metadata needed to retrieve database information
270      * @return The database information as formatted string
271      */
272     public static String getDatabaseInfo(DatabaseMetaData metaData)
273     {
274         StringBuffer sb = new StringBuffer();
275         sb.append("\n");
276 
277         String dbInfo = null;
278 
279         dbInfo = new ExceptionWrapper(){
280             public String wrappedCall(DatabaseMetaData metaData) throws Exception {
281                 return metaData.getDatabaseProductName();
282             }
283         }.executeWrappedCall(metaData);
284         sb.append("\tdatabase product name=").append(dbInfo).append("\n");
285 
286         dbInfo = new ExceptionWrapper(){
287             public String wrappedCall(DatabaseMetaData metaData) throws Exception {
288                 return metaData.getDatabaseProductVersion();
289             }
290         }.executeWrappedCall(metaData);
291         sb.append("\tdatabase version=").append(dbInfo).append("\n");
292 
293         dbInfo = new ExceptionWrapper(){
294             public String wrappedCall(DatabaseMetaData metaData) throws Exception {
295                 return String.valueOf(metaData.getDatabaseMajorVersion());
296             }
297         }.executeWrappedCall(metaData);
298         sb.append("\tdatabase major version=").append(dbInfo).append("\n");
299 
300         dbInfo = new ExceptionWrapper(){
301             public String wrappedCall(DatabaseMetaData metaData) throws Exception {
302                 return String.valueOf(metaData.getDatabaseMinorVersion());
303             }
304         }.executeWrappedCall(metaData);
305         sb.append("\tdatabase minor version=").append(dbInfo).append("\n");
306 
307         dbInfo = new ExceptionWrapper(){
308             public String wrappedCall(DatabaseMetaData metaData) throws Exception {
309                 return metaData.getDriverName();
310             }
311         }.executeWrappedCall(metaData);
312         sb.append("\tjdbc driver name=").append(dbInfo).append("\n");
313 
314         dbInfo = new ExceptionWrapper(){
315             public String wrappedCall(DatabaseMetaData metaData) throws Exception {
316                 return metaData.getDriverVersion();
317             }
318         }.executeWrappedCall(metaData);
319         sb.append("\tjdbc driver version=").append(dbInfo).append("\n");
320 
321         dbInfo = new ExceptionWrapper(){
322             public String wrappedCall(DatabaseMetaData metaData) throws Exception {
323                 return String.valueOf(metaData.getDriverMajorVersion());
324             }
325         }.executeWrappedCall(metaData);
326         sb.append("\tjdbc driver major version=").append(dbInfo).append("\n");
327 
328         dbInfo = new ExceptionWrapper(){
329             public String wrappedCall(DatabaseMetaData metaData) throws Exception {
330                 return String.valueOf(metaData.getDriverMinorVersion());
331             }
332         }.executeWrappedCall(metaData);
333         sb.append("\tjdbc driver minor version=").append(dbInfo).append("\n");
334 
335         return sb.toString();
336     }
337 
338     /**
339      * Prints the database and JDBC driver information to the given output stream
340      * @param metaData The JDBC database metadata needed to retrieve database information
341      * @param outputStream The stream to which the information is printed
342      * @throws SQLException
343      */
344     public static void printDatabaseInfo(DatabaseMetaData metaData, PrintStream outputStream) throws SQLException
345     {
346         String dbInfo = getDatabaseInfo(metaData);
347         try {
348             outputStream.println(dbInfo);
349         }
350         finally {
351             outputStream.flush();
352         }
353     }
354 
355     /**
356      * Detects whether or not the given metadata describes the connection to a Sybase database
357      * or not.
358      * @param metaData The metadata to be checked whether it is a Sybase connection
359      * @return <code>true</code> if and only if the given metadata belongs to a Sybase database.
360      * @throws SQLException
361      */
362     public static boolean isSybaseDb(DatabaseMetaData metaData) throws SQLException
363     {
364         String dbProductName = metaData.getDatabaseProductName();
365         boolean isSybase = (dbProductName != null && dbProductName.equals(DB_PRODUCT_SYBASE));
366         return isSybase;
367     }
368 
369 
370     /**
371      * Utility method to create a {@link Column} object from a SQL {@link ResultSet} object.
372      * 
373      * @param resultSet A result set produced via {@link DatabaseMetaData#getColumns(String, String, String, String)}
374      * @param dataTypeFactory The factory used to lookup the {@link DataType} for this column
375      * @param datatypeWarning Whether or not a warning should be printed if the column could not
376      * be created because of an unknown datatype.
377      * @return The {@link Column} or <code>null</code> if the column could not be initialized because of an
378      * unknown datatype.
379      * @throws SQLException
380      * @throws DataTypeException
381      * @since 2.4.0
382      */
383     public static final Column createColumn(ResultSet resultSet,
384             IDataTypeFactory dataTypeFactory, boolean datatypeWarning)
385                     throws SQLException, DataTypeException
386                     {
387         String tableName = resultSet.getString(3);
388         String columnName = resultSet.getString(4);
389         int sqlType = resultSet.getInt(5);
390         //If Types.DISTINCT like SQL DOMAIN, then get Source Date Type of SQL-DOMAIN
391         if(sqlType == java.sql.Types.DISTINCT)
392         {
393             sqlType = resultSet.getInt("SOURCE_DATA_TYPE");
394         }
395 
396         String sqlTypeName = resultSet.getString(6);
397         //        int columnSize = resultSet.getInt(7);
398         int nullable = resultSet.getInt(11);
399         String remarks = resultSet.getString(12);
400         String columnDefaultValue = resultSet.getString(13);
401         // This is only available since Java 5 - so we can try it and if it does not work default it
402         String isAutoIncrement = Column.AutoIncrement.NO.getKey();
403         try {
404             isAutoIncrement = resultSet.getString(23);
405         }
406         catch (Exception e)
407         {
408             // Ignore this one here
409             final String msg =
410                     "Could not retrieve the 'isAutoIncrement' property"
411                             + " because not yet running on Java 1.5 -"
412                             + " defaulting to NO. Table={}, Column={}";
413             logger.debug(msg, tableName, columnName, e);
414         }
415 
416         // Convert SQL type to DataType
417         DataType dataType =
418                 dataTypeFactory.createDataType(sqlType, sqlTypeName, tableName, columnName);
419         if (dataType != DataType.UNKNOWN)
420         {
421             Column column = new Column(columnName, dataType,
422                     sqlTypeName, Column.nullableValue(nullable), columnDefaultValue, remarks,
423                     Column.AutoIncrement.autoIncrementValue(isAutoIncrement));
424             return column;
425         }
426         else
427         {
428             if (datatypeWarning)
429                 logger.warn(
430                         tableName + "." + columnName +
431                         " data type (" + sqlType + ", '" + sqlTypeName +
432                         "') not recognized and will be ignored. See FAQ for more information.");
433 
434             // datatype unknown - column not created
435             return null;
436         }
437                     }
438 
439     /**
440      * Checks if the given <code>resultSet</code> matches the given schema and table name.
441      * The comparison is <b>case sensitive</b>.
442      * @param resultSet A result set produced via {@link DatabaseMetaData#getColumns(String, String, String, String)}
443      * @param schema The name of the schema to check. If <code>null</code> it is ignored in the comparison
444      * @param table The name of the table to check. If <code>null</code> it is ignored in the comparison
445      * @param caseSensitive Whether or not the comparison should be case sensitive or not
446      * @return <code>true</code> if the column metadata of the given <code>resultSet</code> matches
447      * the given schema and table parameters.
448      * @throws SQLException
449      * @since 2.4.0
450      * @deprecated since 2.4.4 - use {@link IMetadataHandler#matches(ResultSet, String, String, String, String, boolean)}
451      */
452     public static boolean matches(ResultSet resultSet,
453             String schema, String table, boolean caseSensitive)
454                     throws SQLException
455                     {
456         return matches(resultSet, null, schema, table, null, caseSensitive);
457                     }
458 
459 
460     /**
461      * Checks if the given <code>resultSet</code> matches the given schema and table name.
462      * The comparison is <b>case sensitive</b>.
463      * @param resultSet A result set produced via {@link DatabaseMetaData#getColumns(String, String, String, String)}
464      * @param catalog The name of the catalog to check. If <code>null</code> it is ignored in the comparison
465      * @param schema The name of the schema to check. If <code>null</code> it is ignored in the comparison
466      * @param table The name of the table to check. If <code>null</code> it is ignored in the comparison
467      * @param column The name of the column to check. If <code>null</code> it is ignored in the comparison
468      * @param caseSensitive Whether or not the comparison should be case sensitive or not
469      * @return <code>true</code> if the column metadata of the given <code>resultSet</code> matches
470      * the given schema and table parameters.
471      * @throws SQLException
472      * @since 2.4.0
473      * @deprecated since 2.4.4 - use {@link IMetadataHandler#matches(ResultSet, String, String, String, String, boolean)}
474      */
475     public static boolean matches(ResultSet resultSet,
476             String catalog, String schema,
477             String table, String column, boolean caseSensitive)
478                     throws SQLException
479                     {
480         String catalogName = resultSet.getString(1);
481         String schemaName = resultSet.getString(2);
482         String tableName = resultSet.getString(3);
483         String columnName = resultSet.getString(4);
484 
485         // MYSQL provides only a catalog but no schema
486         if(schema != null && schemaName == null && catalog==null && catalogName != null){
487             logger.debug("Switching catalog/schema because the are mutually null");
488             schemaName = catalogName;
489             catalogName = null;
490         }
491 
492         boolean areEqual =
493                 areEqualIgnoreNull(catalog, catalogName, caseSensitive) &&
494                 areEqualIgnoreNull(schema, schemaName, caseSensitive) &&
495                 areEqualIgnoreNull(table, tableName, caseSensitive) &&
496                 areEqualIgnoreNull(column, columnName, caseSensitive);
497         return areEqual;
498                     }
499 
500     /**
501      * Compares the given values and returns true if they are equal.
502      * If the first value is <code>null</code> or empty String it always
503      * returns <code>true</code> which is the way of ignoring <code>null</code>s
504      * for this specific case.
505      * @param value1 The first value to compare. Is ignored if null or empty String
506      * @param value2 The second value to be compared
507      * @return <code>true</code> if both values are equal or if the first value
508      * is <code>null</code> or empty string.
509      * @since 2.4.4
510      */
511     public static final boolean areEqualIgnoreNull(String value1, String value2, boolean caseSensitive)
512     {
513         if(value1==null || value1.equals(""))
514         {
515             return true;
516         }
517         else
518         {
519             if(caseSensitive && value1.equals(value2))
520             {
521                 return true;
522             }
523             else if(!caseSensitive && value1.equalsIgnoreCase(value2))
524             {
525                 return true;
526             }
527             else
528             {
529                 return false;
530             }
531         }
532     }
533 
534     /**
535      * Corrects the case of the given String according to the way in which the database stores metadata.
536      * @param databaseIdentifier A database identifier such as a table name or a schema name for
537      * which the case should be corrected.
538      * @param connection The connection used to lookup the database metadata. This is needed to determine
539      * the way in which the database stores its metadata.
540      * @return The database identifier in the correct case for the RDBMS
541      * @since 2.4.4
542      */
543     public static final String correctCase(final String databaseIdentifier, Connection connection)
544     {
545         logger.trace("correctCase(tableName={}, connection={}) - start", databaseIdentifier, connection);
546 
547         try
548         {
549             return correctCase(databaseIdentifier, connection.getMetaData());
550         }
551         catch (SQLException e)
552         {
553             throw new DatabaseUnitRuntimeException("Exception while trying to access database metadata", e);
554         }
555     }
556 
557     /**
558      * Corrects the case of the given String according to the way in which the database stores metadata.
559      * @param databaseIdentifier A database identifier such as a table name or a schema name for
560      * which the case should be corrected.
561      * @param databaseMetaData The database metadata needed to determine the way in which the database stores
562      * its metadata.
563      * @return The database identifier in the correct case for the RDBMS
564      * @since 2.4.4
565      */
566     public static final String correctCase(final String databaseIdentifier, DatabaseMetaData databaseMetaData)
567     {
568         logger.trace("correctCase(tableName={}, databaseMetaData={}) - start", databaseIdentifier, databaseMetaData);
569 
570         if (databaseIdentifier == null) {
571             throw new NullPointerException(
572                     "The parameter 'databaseIdentifier' must not be null");
573         }
574         if (databaseMetaData == null) {
575             throw new NullPointerException(
576                     "The parameter 'databaseMetaData' must not be null");
577         }
578 
579         try {
580             String resultTableName = databaseIdentifier;
581             String dbIdentifierQuoteString = databaseMetaData.getIdentifierQuoteString();
582             if(!isEscaped(databaseIdentifier, dbIdentifierQuoteString)){
583                 if(databaseMetaData.storesLowerCaseIdentifiers())
584                 {
585                     resultTableName = databaseIdentifier.toLowerCase(Locale.ENGLISH);
586                 }
587                 else if(databaseMetaData.storesUpperCaseIdentifiers())
588                 {
589                     resultTableName = databaseIdentifier.toUpperCase(Locale.ENGLISH);
590                 }
591                 else
592                 {
593                     logger.debug("Database does not store upperCase or lowerCase identifiers. " +
594                             "Will not correct case of the table names.");
595                 }
596             }
597             else
598             {
599                 if(logger.isDebugEnabled())
600                     logger.debug("The tableName '{}' is escaped. Will not correct case.", databaseIdentifier);
601                 }
602             return resultTableName;
603         }
604         catch (SQLException e)
605         {
606             throw new DatabaseUnitRuntimeException("Exception while trying to access database metadata", e);
607         }
608     }
609 
610     /**
611      * Checks whether two given values are unequal and if so print a log message (level DEBUG)
612      * @param oldValue The old value of a property
613      * @param newValue The new value of a property
614      * @param message The message to be logged
615      * @param source The class which invokes this method - used for enriching the log message
616      * @since 2.4.4
617      */
618     public static final void logInfoIfValueChanged(String oldValue, String newValue, String message, Class source)
619     {
620         if(logger.isInfoEnabled())
621         {
622             if(oldValue != null && !oldValue.equals(newValue))
623                 logger.debug("{}. {} oldValue={} newValue={}", new Object[] {source, message, oldValue, newValue});
624             }
625         }
626 
627     /**
628      * Checks whether two given values are unequal and if so print a log message (level DEBUG)
629      * @param oldValue The old value of a property
630      * @param newValue The new value of a property
631      * @param message The message to be logged
632      * @param source The class which invokes this method - used for enriching the log message
633      * @since 2.4.8
634      */
635     public static final void logDebugIfValueChanged(String oldValue, String newValue, String message, Class source)
636     {
637         if (logger.isDebugEnabled())
638         {
639             if (oldValue != null && !oldValue.equals(newValue))
640                 logger.debug("{}. {} oldValue={} newValue={}", new Object[] {source, message, oldValue, newValue});
641             }
642         }
643 
644     /**
645      * @param tableName
646      * @param dbIdentifierQuoteString
647      * @return
648      * @since 2.4.4
649      */
650     private static final boolean isEscaped(String tableName, String dbIdentifierQuoteString)
651     {
652         logger.trace("isEscaped(tableName={}, dbIdentifierQuoteString={}) - start", tableName, dbIdentifierQuoteString);
653 
654         if (dbIdentifierQuoteString == null) {
655             throw new NullPointerException(
656                     "The parameter 'dbIdentifierQuoteString' must not be null");
657         }
658         boolean isEscaped = tableName!=null && (tableName.startsWith(dbIdentifierQuoteString));
659         if(logger.isDebugEnabled())
660             logger.debug("isEscaped returns '{}' for tableName={} (dbIdentifierQuoteString={})",
661                     new Object[]{Boolean.valueOf(isEscaped), tableName, dbIdentifierQuoteString} );
662         return isEscaped;
663     }
664 
665 
666     /**
667      * Performs a method invocation and catches all exceptions that occur during the invocation.
668      * Utility which works similar to a closure, just a bit less elegant.
669      * @author gommma (gommma AT users.sourceforge.net)
670      * @author Last changed by: $Author$
671      * @version $Revision$ $Date$
672      * @since 2.4.6
673      */
674     static abstract class ExceptionWrapper{
675 
676         public static final String NOT_AVAILABLE_TEXT = "<not available>";
677 
678         /**
679          * Default constructor
680          */
681         public ExceptionWrapper()
682         {
683         }
684 
685         /**
686          * Executes the call and catches all exception that might occur.
687          * @param metaData
688          * @return The result of the call
689          */
690         public final String executeWrappedCall(DatabaseMetaData metaData) {
691             try{
692                 String result = wrappedCall(metaData);
693                 return result;
694             }
695             catch(Exception e){
696                 logger.trace("Problem retrieving DB information via DatabaseMetaData", e);
697                 return NOT_AVAILABLE_TEXT;
698             }
699         }
700         /**
701          * Calls the method that might throw an exception to be handled
702          * @param metaData
703          * @return The result of the call as human readable string
704          * @throws Exception Any exception that might occur during the method invocation
705          */
706         public abstract String wrappedCall(DatabaseMetaData metaData) throws Exception;
707     }
708 
709 }