View Javadoc

1   /*
2    * #%L
3    * Bitrepository Integrity Client
4    * 
5    * $Id$
6    * $HeadURL$
7    * %%
8    * Copyright (C) 2010 - 2012 The State and University Library, The Royal Library and The State Archives, Denmark
9    * %%
10   * This program is free software: you can redistribute it and/or modify
11   * it under the terms of the GNU Lesser General Public License as 
12   * published by the Free Software Foundation, either version 2.1 of the 
13   * License, or (at your option) any later version.
14   * 
15   * This program is distributed in the hope that it will be useful,
16   * but WITHOUT ANY WARRANTY; without even the implied warranty of
17   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18   * GNU General Lesser Public License for more details.
19   * 
20   * You should have received a copy of the GNU General Lesser Public 
21   * License along with this program.  If not, see
22   * <http://www.gnu.org/licenses/lgpl-2.1.html>.
23   * #L%
24   */
25  package org.bitrepository.service.database;
26  
27  import org.bitrepository.common.ArgumentValidator;
28  import org.slf4j.Logger;
29  import org.slf4j.LoggerFactory;
30  
31  import java.sql.Connection;
32  import java.sql.PreparedStatement;
33  import java.sql.ResultSet;
34  import java.sql.SQLException;
35  import java.sql.Timestamp;
36  import java.util.ArrayList;
37  import java.util.Arrays;
38  import java.util.Date;
39  import java.util.List;
40  
41  /**
42   * Utility class for operating on databases.
43   */
44  public class DatabaseUtils {
45      /** The log.*/
46      private static Logger log = LoggerFactory.getLogger(DatabaseUtils.class);
47  
48      /** The name of the embedded derby driver.*/
49      public static final String DERBY_EMBEDDED_DRIVER = "org.apache.derby.jdbc.EmbeddedDriver";
50      
51      /** Private constructor to prevent instantiation of this utility class.*/
52      private DatabaseUtils() { }
53      
54      /**
55       * Retrieves an integer value from the database through the use of a SQL query, which requests 
56       * the wanted integer value, and the arguments for the query to become a proper SQL statement.
57       * 
58       * @param dbConnector For connecting to the database.
59       * @param query The query for retrieving the integer value.
60       * @param args The arguments for the database statement.
61       * @return The integer value from the given statement.
62       */
63      public static Integer selectIntValue(DBConnector dbConnector, String query, Object... args) {
64          ArgumentValidator.checkNotNull(dbConnector, "DBConnector dbConnector");
65          ArgumentValidator.checkNotNullOrEmpty(query, "String query");
66          ArgumentValidator.checkNotNull(args, "Object... args");
67          
68          PreparedStatement ps = null; 
69          ResultSet res = null;
70          Connection conn = null;
71          try {
72              try {
73                  conn = dbConnector.getConnection();
74                  ps = createPreparedStatement(conn, query, args);
75                  res = ps.executeQuery();
76                  if (!res.next()) {
77                      throw new IllegalStateException("No results from " + ps);
78                  }
79                  Integer resultInt = res.getInt(1);
80                  if (res.wasNull()) {
81                      resultInt = null;
82                  }
83                  if (res.next()) {
84                      throw new IllegalStateException("Too many results from " + ps);
85                  }
86                  return resultInt;
87              } finally {
88                  if(res != null) {
89                      res.close();
90                  }
91                  if(ps != null) {
92                      ps.close();
93                  }
94                  if(conn != null) {
95                      conn.close();
96                  }
97              }
98          } catch (SQLException e) {
99              throw failedExecutionOfStatement(e, conn, query, args);
100         }
101     }
102     
103     /**
104      * Retrieves a long value from the database through the use of a SQL query, which requests 
105      * the wanted long value, and the arguments for the query to become a proper SQL statement.
106      * 
107      * @param dbConnector For connecting to the database.
108      * @param query The query for retrieving the long value.
109      * @param args The arguments for the database statement.
110      * @return The long value from the given statement.
111      */
112     public static Long selectLongValue(DBConnector dbConnector, String query, Object... args) {
113         ArgumentValidator.checkNotNull(dbConnector, "DBConnector dbConnector");
114         ArgumentValidator.checkNotNullOrEmpty(query, "String query");
115         ArgumentValidator.checkNotNull(args, "Object... args");
116         
117         PreparedStatement ps = null; 
118         ResultSet res = null;
119         Connection conn = null;
120         try {
121             try {
122                 conn = dbConnector.getConnection();
123                 ps = createPreparedStatement(conn, query, args);
124                 res = ps.executeQuery();
125                 if (!res.next()) {
126                     log.trace("Got an empty result set for statement '" + query + "' with arguments '"
127                             + Arrays.asList(args) + "' on database '" + conn + "'. Returning a null.");
128                     return null;
129                 }
130                 Long resultLong = res.getLong(1);
131                 if (res.wasNull()) {
132                     resultLong = null;
133                 }
134                 if (res.next()) {
135                     throw new IllegalStateException("Too many results from " + ps);
136                 }
137                 return resultLong;
138             } finally {
139                 if(res != null) {
140                     res.close();
141                 }
142                 if(ps != null) {
143                     ps.close();
144                 }
145                 if(conn != null) {
146                     conn.close();
147                 }
148             }
149         } catch (SQLException e) {
150             throw failedExecutionOfStatement(e, conn, query, args);
151         }
152     }
153     
154     /**
155      * Retrieves the first long value from the database through the use of a SQL query and the arguments for the query 
156      * to become a proper SQL statement, which requests the wanted set of long values, where only the first is 
157      * returned.
158      * 
159      * @param dbConnector For connecting to the database.
160      * @param query The query for retrieving the long value.
161      * @param args The arguments for the database statement.
162      * @return The long value from the given statement.
163      */
164     public static Long selectFirstLongValue(DBConnector dbConnector, String query, Object... args) {
165         ArgumentValidator.checkNotNull(dbConnector, "DBConnector dbConnector");
166         ArgumentValidator.checkNotNullOrEmpty(query, "String query");
167         ArgumentValidator.checkNotNull(args, "Object... args");
168         
169         PreparedStatement ps = null; 
170         ResultSet res = null;
171         Connection conn = null;
172         try {
173             try {
174                 conn = dbConnector.getConnection();
175                 ps = createPreparedStatement(conn, query, args);
176                 res = ps.executeQuery();
177                 if (!res.next()) {
178                     log.trace("Got an empty result set for statement '" + query + "' with arguments '"
179                             + Arrays.asList(args) + "' on database '" + conn + "'. Returning a null.");
180                     return null;
181                 }
182                 Long resultLong = res.getLong(1);
183                 if (res.wasNull()) {
184                     resultLong = null;
185                 }
186                 return resultLong;
187             } finally {
188                 if(res != null) {
189                     res.close();
190                 }
191                 if(ps != null) {
192                     ps.close();
193                 }
194                 if(conn != null) {
195                     conn.close();
196                 }
197             }
198         } catch (SQLException e) {
199             throw failedExecutionOfStatement(e, conn, query, args);
200         }
201     }
202     
203     /**
204      * Retrieves a list of long values from the database through the use of a SQL query, which requests 
205      * the wanted long values, and the arguments for the query to become a proper SQL statement.
206      * 
207      * @param dbConnector For connecting to the database.
208      * @param query The query for retrieving the long value.
209      * @param args The arguments for the database statement.
210      * @return The list of long values from the given statement.
211      */
212     public static List<Long> selectLongList(DBConnector dbConnector, String query, Object... args) {
213         ArgumentValidator.checkNotNull(dbConnector, "DBConnector dbConnector");
214         ArgumentValidator.checkNotNullOrEmpty(query, "String query");
215         ArgumentValidator.checkNotNull(args, "Object... args");
216         
217         PreparedStatement ps = null; 
218         ResultSet res = null;
219         Connection conn = null;
220         try {
221             List<Long> list = new ArrayList<Long>();
222             try {
223                 conn = dbConnector.getConnection();
224                 ps = createPreparedStatement(conn, query, args);
225                 res = ps.executeQuery();
226                 
227                 while(res.next()) {
228                     list.add(res.getLong(1));
229                 }
230                 return list;
231             } finally {
232                 if(res != null) {
233                     res.close();
234                 }
235                 if(ps != null) {
236                     ps.close();
237                 }
238                 if(conn != null) {
239                     conn.close();
240                 }
241             }
242         } catch (SQLException e) {
243             throw failedExecutionOfStatement(e, conn, query, args);
244         }
245     }
246 
247     /**
248      * Retrieves an date value from the database through the use of a SQL query, which requests 
249      * the wanted date value, and the arguments for the query to become a proper SQL statement.
250      * 
251      * @param dbConnector For connecting to the database.
252      * @param query The query for retrieving the date.
253      * @param args The arguments for the database statement.
254      * @return The date from the given statement.
255      */
256     public static Date selectDateValue(DBConnector dbConnector, String query, Object... args) {
257         return retrieveDateValue(dbConnector, true, query, args);
258     }
259     
260     /**
261      * Retrieves the first date value from the database through the use of a SQL query, which requests 
262      * the wanted date value, and the arguments for the query to become a proper SQL statement.
263      * If the results set contains more than one value, the others are ignored. 
264      * 
265      * @param dbConnector For connecting to the database.
266      * @param query The query for retrieving the date.
267      * @param args The arguments for the database statement.
268      * @return The date from the given statement.
269      */
270     public static Date selectFirstDateValue(DBConnector dbConnector, String query, Object... args) {
271         return retrieveDateValue(dbConnector, false, query, args);
272     }
273     
274     /**
275      * The actual extraction from the database.
276      * @param dbConnector For connecting to the database.
277      * @param mustHaveOnlyOneResult Whether it should fail, if more than one result is found.
278      * @param query The query for retrieving the date.
279      * @param args The arguments for the database statement.
280      * @return The date from the given statement.
281      */
282     private static Date retrieveDateValue(DBConnector dbConnector, boolean mustHaveOnlyOneResult, String query, Object... args) {
283         ArgumentValidator.checkNotNull(dbConnector, "DBConnector dbConnector");
284         ArgumentValidator.checkNotNullOrEmpty(query, "String query");
285         ArgumentValidator.checkNotNull(args, "Object... args");
286         
287         PreparedStatement ps = null; 
288         ResultSet res = null;
289         Connection conn = null;
290         try {
291             try {
292                 conn = dbConnector.getConnection();
293                 ps = createPreparedStatement(conn, query, args);
294                 
295                 res = ps.executeQuery();
296                 if (!res.next()) {
297                     log.trace("Got an empty result set for statement '" + query + "' on database '"
298                             + conn + "'. Returning a null.");
299                     return null;
300                 }
301                 Timestamp resultDate = res.getTimestamp(1);
302                 if (res.wasNull()) {
303                     resultDate = null;
304                 }
305                 if (mustHaveOnlyOneResult && res.next()) {
306                     throw new IllegalStateException("Too many results from " + ps);
307                 }
308                 return resultDate;
309             } finally {
310                 if(res != null) {
311                     res.close();
312                 }
313                 if(ps != null) {
314                     ps.close();
315                 }
316                 if(conn != null) {
317                     conn.close();
318                 }
319             }
320         } catch (SQLException e) {
321             throw failedExecutionOfStatement(e, conn, query, args);
322         }
323     }
324     
325     /**
326      * Retrieves a String value from the database through the use of a SQL query, which requests 
327      * the wanted String value, and the arguments for the query to become a proper SQL statement.
328      * 
329      * @param dbConnector For connecting to the database.
330      * @param query The query to extract the String value.
331      * @param args The arguments for the statement.
332      * @return The requested string value, or null if no such value could be found.
333      */
334     public static String selectStringValue(DBConnector dbConnector, String query, Object... args) {
335         ArgumentValidator.checkNotNull(dbConnector, "DBConnector dbConnector");
336         ArgumentValidator.checkNotNullOrEmpty(query, "String query");
337         ArgumentValidator.checkNotNull(args, "Object... args");
338 
339         PreparedStatement ps = null; 
340         ResultSet res = null;
341         Connection conn = null;
342         try {
343             try {
344                 conn = dbConnector.getConnection();
345                 ps = createPreparedStatement(conn, query, args);
346                 res = ps.executeQuery();
347                 
348                 if(!res.next()) {
349                     log.trace("No string was found for the query '" + query + "'. A null has been returned.");
350                     return null;
351                 }
352                 
353                 return res.getString(1);
354             } finally {
355                 if(res != null) {
356                     res.close();
357                 }
358                 if(ps != null) {
359                     ps.close();
360                 }
361                 if(conn != null) {
362                     conn.close();
363                 }
364             }
365         } catch (SQLException e) {
366             throw failedExecutionOfStatement(e, conn, query, args);
367         }
368     }
369     
370     /**
371      * Retrieves a list of String value from the database through the use of a SQL query, which requests 
372      * the wanted list of String value, and the arguments for the query to become a proper SQL statement.
373      * 
374      * @param dbConnector For connecting to the database.
375      * @param query The SQL query for retrieving the strings.
376      * @param args The arguments for the statement.
377      * @return The requested list of strings. If no strings were found, then the list is empty.
378      */
379     public static List<String> selectStringList(DBConnector dbConnector, String query, Object... args) {
380         ArgumentValidator.checkNotNull(dbConnector, "DBConnector dbConnector");
381         ArgumentValidator.checkNotNullOrEmpty(query, "String query");
382         ArgumentValidator.checkNotNull(args, "Object... args");
383 
384         PreparedStatement ps = null; 
385         ResultSet res = null;
386         Connection conn = null;
387         try {
388             try {
389                 conn = dbConnector.getConnection();
390                 ps = createPreparedStatement(conn, query, args);
391                 res = ps.executeQuery();
392                 
393                 List<String> list = new ArrayList<String>();
394                 while(res.next()) {
395                     list.add(res.getString(1));
396                 }
397                 return list;
398             } finally {
399                 if(res != null) {
400                     res.close();
401                 }
402                 if(ps != null) {
403                     ps.close();
404                 }
405                 if(conn != null) {
406                     conn.close();
407                 }
408             }
409         } catch (SQLException e) {
410             throw failedExecutionOfStatement(e, conn, query, args);
411         }
412     }
413 
414     /**
415      * Executing a given statement, which should not return any results.
416      * This is intended to be used especially for UPDATE commands.
417      * 
418      * @param dbConnector For connecting to the database.
419      * @param query The SQL query to execute.
420      * @param args The arguments for the SQL statement.
421      */
422     public static void executeStatement(DBConnector dbConnector, String query, Object... args) {
423         ArgumentValidator.checkNotNull(dbConnector, "DBConnector dbConnector");
424         ArgumentValidator.checkNotNullOrEmpty(query, "String query");
425         ArgumentValidator.checkNotNull(args, "Object... args");
426         
427         PreparedStatement ps = null; 
428         ResultSet res = null;
429         Connection conn = null;
430         try {
431             try {
432                 conn = dbConnector.getConnection();
433                 ps = createPreparedStatement(conn, query, args);
434                 ps.executeUpdate();
435                 conn.commit();
436             } finally {
437                 if(res != null) {
438                     res.close();
439                 }
440                 if(ps != null) {
441                     ps.close();
442                 }
443                 if(conn != null) {
444                     conn.close();
445                 }
446             }
447         } catch (SQLException e) {
448             throw failedExecutionOfStatement(e, conn, query, args);
449         }
450     }
451     
452     /**
453      * Prepare a statement given a query string and some args.
454      *
455      * NB: the provided connection is not closed.
456      *
457      * @param dbConnection The connection to the database.
458      * @param query a query string  (must not be null or empty)
459      * @param args some args to insert into this query string (must not be null)
460      * @return a prepared statement
461      * @throws SQLException If unable to prepare a statement
462      */
463     public static PreparedStatement createPreparedStatement(Connection dbConnection, String query, Object... args)
464             throws SQLException {
465         log.trace("Preparing the statement: '" + query + "' with arguments '" + Arrays.asList(args) + "'");
466         PreparedStatement s = dbConnection.prepareStatement(query);
467         int i = 1;
468         for (Object arg : args) {
469             if (arg instanceof String) {
470                 s.setString(i, (String) arg);
471             } else if (arg instanceof Integer) {
472                 s.setInt(i, (Integer) arg);
473             } else if (arg instanceof Long) {
474                 s.setLong(i, (Long) arg);
475             } else if (arg instanceof Boolean) {
476                 s.setBoolean(i, (Boolean) arg);
477             } else if (arg instanceof Date) {
478                 s.setTimestamp(i, new Timestamp(((Date) arg).getTime()));
479             } else {
480                 if(arg == null) {
481                     throw new IllegalStateException("Cannot handle a null as argument for SQL query. We can only "
482                             + "handle string, int, long, date or boolean args for query: " + query);                    
483                 } else  {
484                     throw new IllegalStateException("Cannot handle type '" + arg.getClass().getName() + "'. We can only "
485                             + "handle string, int, long, date or boolean args for query: " + query);
486                 }
487             }
488             i++;
489         }
490 
491         return s;
492     }
493     
494     /**
495      * Method for throwing an exception for a failure for executing a statement.
496      * 
497      * @param e The exception for the execution to fail.
498      * @param dbConnection The connection to the database, where the failure occurred.
499      * @param query The SQL query for the statement, which caused the failure.
500      * @param args The arguments for the statement, which caused the failure.
501      * @throws IllegalStateException Always, since it is intended for this method to report the failure.
502      */
503     private static IllegalStateException failedExecutionOfStatement(Throwable e, Connection dbConnection, 
504             String query, Object... args) {
505         return new IllegalStateException("Could not execute the query '" + query + "' with the arguments '" 
506                 + Arrays.asList(args) + "' on database '" + dbConnection + "'", e);
507     }
508 }