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: 1134 $
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 ca 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(SQLException e){
407             if(logger.isDebugEnabled())
408                 logger.debug("Could not retrieve the 'isAutoIncrement' property because not yet running on Java 1.5 - defaulting to NO. " +
409                         "Table=" + tableName + ", Column=" +columnName, e);
410             // Ignore this one here
411         }
412         
413         // Convert SQL type to DataType
414         DataType dataType =
415                 dataTypeFactory.createDataType(sqlType, sqlTypeName, tableName, columnName);
416         if (dataType != DataType.UNKNOWN)
417         {
418             Column column = new Column(columnName, dataType,
419                     sqlTypeName, Column.nullableValue(nullable), columnDefaultValue, remarks,
420                     Column.AutoIncrement.autoIncrementValue(isAutoIncrement));
421             return column;
422         }
423         else
424         {
425             if (datatypeWarning)
426                 logger.warn(
427                     tableName + "." + columnName +
428                     " data type (" + sqlType + ", '" + sqlTypeName +
429                     "') not recognized and will be ignored. See FAQ for more information.");
430             
431             // datatype unknown - column not created
432             return null;
433         }
434     }
435 
436     /**
437      * Checks if the given <code>resultSet</code> matches the given schema and table name.
438      * The comparison is <b>case sensitive</b>.
439      * @param resultSet A result set produced via {@link DatabaseMetaData#getColumns(String, String, String, String)}
440      * @param schema The name of the schema to check. If <code>null</code> it is ignored in the comparison
441      * @param table The name of the table to check. If <code>null</code> it is ignored in the comparison
442      * @param caseSensitive Whether or not the comparison should be case sensitive or not
443      * @return <code>true</code> if the column metadata of the given <code>resultSet</code> matches
444      * the given schema and table parameters.
445      * @throws SQLException
446      * @since 2.4.0
447      * @deprecated since 2.4.4 - use {@link IMetadataHandler#matches(ResultSet, String, String, String, String, boolean)}
448      */
449     public static boolean matches(ResultSet resultSet,
450             String schema, String table, boolean caseSensitive) 
451     throws SQLException 
452     {
453         return matches(resultSet, null, schema, table, null, caseSensitive);
454     }
455     
456     
457     /**
458      * Checks if the given <code>resultSet</code> matches the given schema and table name.
459      * The comparison is <b>case sensitive</b>.
460      * @param resultSet A result set produced via {@link DatabaseMetaData#getColumns(String, String, String, String)}
461      * @param catalog The name of the catalog to check. If <code>null</code> it is ignored in the comparison
462      * @param schema The name of the schema to check. If <code>null</code> it is ignored in the comparison
463      * @param table The name of the table to check. If <code>null</code> it is ignored in the comparison
464      * @param column The name of the column to check. If <code>null</code> it is ignored in the comparison
465      * @param caseSensitive Whether or not the comparison should be case sensitive or not
466      * @return <code>true</code> if the column metadata of the given <code>resultSet</code> matches
467      * the given schema and table parameters.
468      * @throws SQLException
469      * @since 2.4.0
470      * @deprecated since 2.4.4 - use {@link IMetadataHandler#matches(ResultSet, String, String, String, String, boolean)}
471      */
472     public static boolean matches(ResultSet resultSet,
473             String catalog, String schema,
474             String table, String column, boolean caseSensitive) 
475     throws SQLException 
476     {
477         String catalogName = resultSet.getString(1);
478         String schemaName = resultSet.getString(2);
479         String tableName = resultSet.getString(3);
480         String columnName = resultSet.getString(4);
481 
482         // MYSQL provides only a catalog but no schema
483         if(schema != null && schemaName == null && catalog==null && catalogName != null){
484             logger.debug("Switching catalog/schema because the are mutually null");
485             schemaName = catalogName;
486             catalogName = null;
487         }
488         
489         boolean areEqual = 
490                 areEqualIgnoreNull(catalog, catalogName, caseSensitive) &&
491                 areEqualIgnoreNull(schema, schemaName, caseSensitive) &&
492                 areEqualIgnoreNull(table, tableName, caseSensitive) &&
493                 areEqualIgnoreNull(column, columnName, caseSensitive);
494         return areEqual;
495     }
496 
497     /**
498      * Compares the given values and returns true if they are equal.
499      * If the first value is <code>null</code> or empty String it always
500      * returns <code>true</code> which is the way of ignoring <code>null</code>s
501      * for this specific case.
502      * @param value1 The first value to compare. Is ignored if null or empty String
503      * @param value2 The second value to be compared
504      * @return <code>true</code> if both values are equal or if the first value
505      * is <code>null</code> or empty string.
506      * @since 2.4.4
507      */
508     public static final boolean areEqualIgnoreNull(String value1, String value2, boolean caseSensitive) 
509     {
510         if(value1==null || value1.equals(""))
511         {
512             return true;
513         }
514         else 
515         {
516             if(caseSensitive && value1.equals(value2))
517             {
518                 return true;
519             }
520             else if(!caseSensitive && value1.equalsIgnoreCase(value2))
521             {
522                 return true;
523             }
524             else
525             {
526                 return false;
527             }
528         }
529     }
530 
531     /**
532      * Corrects the case of the given String according to the way in which the database stores metadata.
533      * @param databaseIdentifier A database identifier such as a table name or a schema name for 
534      * which the case should be corrected.
535      * @param connection The connection used to lookup the database metadata. This is needed to determine 
536      * the way in which the database stores its metadata.
537      * @return The database identifier in the correct case for the RDBMS
538      * @since 2.4.4
539      */
540     public static final String correctCase(final String databaseIdentifier, Connection connection) 
541     {
542         logger.trace("correctCase(tableName={}, connection={}) - start", databaseIdentifier, connection);
543         
544         try
545         {
546             return correctCase(databaseIdentifier, connection.getMetaData());
547         } 
548         catch (SQLException e) 
549         {
550             throw new DatabaseUnitRuntimeException("Exception while trying to access database metadata", e);
551         }
552     }
553     
554     /**
555      * Corrects the case of the given String according to the way in which the database stores metadata.
556      * @param databaseIdentifier A database identifier such as a table name or a schema name for 
557      * which the case should be corrected.
558      * @param databaseMetaData The database metadata needed to determine the way in which the database stores
559      * its metadata.
560      * @return The database identifier in the correct case for the RDBMS
561      * @since 2.4.4
562      */
563     public static final String correctCase(final String databaseIdentifier, DatabaseMetaData databaseMetaData) 
564     {
565         logger.trace("correctCase(tableName={}, databaseMetaData={}) - start", databaseIdentifier, databaseMetaData);
566         
567         if (databaseIdentifier == null) {
568             throw new NullPointerException(
569                     "The parameter 'databaseIdentifier' must not be null");
570         }
571         if (databaseMetaData == null) {
572             throw new NullPointerException(
573                     "The parameter 'databaseMetaData' must not be null");
574         }
575         
576         try {
577             String resultTableName = databaseIdentifier;
578             String dbIdentifierQuoteString = databaseMetaData.getIdentifierQuoteString();
579             if(!isEscaped(databaseIdentifier, dbIdentifierQuoteString)){
580                 if(databaseMetaData.storesLowerCaseIdentifiers())
581                 {
582                     resultTableName = databaseIdentifier.toLowerCase(Locale.ENGLISH);
583                 }
584                 else if(databaseMetaData.storesUpperCaseIdentifiers())
585                 {
586                     resultTableName = databaseIdentifier.toUpperCase(Locale.ENGLISH);
587                 }
588                 else
589                 {
590                     logger.debug("Database does not store upperCase or lowerCase identifiers. " +
591                             "Will not correct case of the table names.");
592                 }
593             }
594             else
595             {
596                 if(logger.isDebugEnabled())
597                     logger.debug("The tableName '{}' is escaped. Will not correct case.", databaseIdentifier);
598             }
599             return resultTableName;
600         } 
601         catch (SQLException e) 
602         {
603             throw new DatabaseUnitRuntimeException("Exception while trying to access database metadata", e);
604         }
605     }
606 
607     /**
608      * Checks whether two given values are unequal and if so print a log message (level DEBUG)
609      * @param oldValue The old value of a property
610      * @param newValue The new value of a property
611      * @param message The message to be logged
612      * @param source The class which invokes this method - used for enriching the log message
613      * @since 2.4.4
614      */
615     public static final void logInfoIfValueChanged(String oldValue, String newValue, String message, Class source) 
616     {
617         if(logger.isInfoEnabled())
618         {
619             if(oldValue != null && !oldValue.equals(newValue))
620                 logger.debug("{}. {} oldValue={} newValue={}", new Object[] {source, message, oldValue, newValue});
621         }
622     }
623 
624     /**
625      * Checks whether two given values are unequal and if so print a log message (level DEBUG)
626      * @param oldValue The old value of a property
627      * @param newValue The new value of a property
628      * @param message The message to be logged
629      * @param source The class which invokes this method - used for enriching the log message
630      * @since 2.4.8
631      */
632     public static final void logDebugIfValueChanged(String oldValue, String newValue, String message, Class source)
633     {
634         if (logger.isDebugEnabled())
635         {
636             if (oldValue != null && !oldValue.equals(newValue))
637                 logger.debug("{}. {} oldValue={} newValue={}", new Object[] {source, message, oldValue, newValue});
638         }
639     }
640 
641     /**
642      * @param tableName
643      * @param dbIdentifierQuoteString
644      * @return
645      * @since 2.4.4
646      */
647     private static final boolean isEscaped(String tableName, String dbIdentifierQuoteString) 
648     {
649         logger.trace("isEscaped(tableName={}, dbIdentifierQuoteString={}) - start", tableName, dbIdentifierQuoteString);
650         
651         if (dbIdentifierQuoteString == null) {
652             throw new NullPointerException(
653                     "The parameter 'dbIdentifierQuoteString' must not be null");
654         }
655         boolean isEscaped = tableName!=null && (tableName.startsWith(dbIdentifierQuoteString));
656         if(logger.isDebugEnabled())
657             logger.debug("isEscaped returns '{}' for tableName={} (dbIdentifierQuoteString={})", 
658                     new Object[]{Boolean.valueOf(isEscaped), tableName, dbIdentifierQuoteString} );
659         return isEscaped;
660     }
661 
662     
663     /**
664      * Performs a method invocation and catches all exceptions that occur during the invocation.
665      * Utility which works similar to a closure, just a bit less elegant.
666      * @author gommma (gommma AT users.sourceforge.net)
667      * @author Last changed by: $Author: jbhurst $
668      * @version $Revision: 1134 $ $Date: 2010-01-07 21:32:54 +0100 (gio, 07 gen 2010) $
669      * @since 2.4.6
670      */
671     static abstract class ExceptionWrapper{
672 
673         public static final String NOT_AVAILABLE_TEXT = "<not available>";
674         
675         /**
676          * Default constructor
677          */
678         public ExceptionWrapper()
679         {
680         }
681         
682         /**
683          * Executes the call and catches all exception that might occur.
684          * @param metaData
685          * @return The result of the call
686          */
687         public final String executeWrappedCall(DatabaseMetaData metaData) {
688             try{
689                 String result = wrappedCall(metaData);
690                 return result;
691             }
692             catch(Exception e){
693                 logger.trace("Problem retrieving DB information via DatabaseMetaData", e);
694                 return NOT_AVAILABLE_TEXT;
695             }
696         }
697         /**
698          * Calls the method that might throw an exception to be handled
699          * @param metaData
700          * @return The result of the call as human readable string
701          * @throws Exception Any exception that might occur during the method invocation
702          */
703         public abstract String wrappedCall(DatabaseMetaData metaData) throws Exception;
704     }
705 
706 }