View Javadoc
1   /*******************************************************************************
2    * Copyright 2012 Internet2
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  /**
17   * @author mchyzer
18   * $Id$
19   */
20  package edu.internet2.middleware.grouperInstaller.util;
21  
22  import java.io.File;
23  import java.sql.Connection;
24  import java.sql.DriverManager;
25  import java.sql.PreparedStatement;
26  import java.sql.ResultSet;
27  import java.sql.SQLException;
28  import java.util.ArrayList;
29  import java.util.HashSet;
30  import java.util.List;
31  import java.util.Set;
32  
33  import edu.internet2.middleware.grouperInstaller.driverShim.MySqlShim;
34  import edu.internet2.middleware.grouperInstaller.driverShim.OracleShim;
35  import edu.internet2.middleware.grouperInstaller.driverShim.PostgresShim;
36  import edu.internet2.middleware.grouperInstaller.driverShim.SqlServerShim;
37  import edu.internet2.middleware.grouperInstallerExt.org.apache.commons.logging.Log;
38  
39  
40  /**
41   * the instance can be reused, it doesnt store state except the connection user/pass etc
42   */
43  public class GiDbUtils {
44  
45    /**
46     * url of db
47     */
48    private String url;
49    
50    /**
51     * user of db
52     */
53    private String user;
54    
55    /**
56     * pass of db
57     */
58    private String pass;
59    
60    /**
61     * construct
62     * @param url
63     * @param user
64     * @param pass
65     */
66    public GiDbUtils(String url, String user, String pass) {
67      super();
68      this.url = url;
69      this.user = user;
70      this.pass = pass;
71    }
72  
73    /**
74     * strings of driver classes that are registered
75     */
76    private static Set<String> driversRegistered = new HashSet<String>();
77    
78    
79    /**
80     * 
81     * @param appDir where we can find drivers
82     */
83    public void registerDriverOnce(String appDir) {
84      String driver = convertUrlToDriverClassIfNeeded(this.url, null);
85      if (driversRegistered.contains(driver)) {
86        return;
87      }
88      //see if its on classpath
89      try {
90        Class.forName(driver);
91        //already on classpath?  thats good
92      } catch (ClassNotFoundException e) {
93        
94        //we need to find jar and add
95        String prefix = null;
96        if (this.isMysql()) {
97          prefix = "mysql";
98        } else if (this.isOracle()) {
99          prefix = "ojdbc";
100       } else if (this.isPostgres()) {
101         prefix = "postgres";
102       } else {
103         throw new RuntimeException("What kind of database is this???? " + this.url);
104       }
105 
106       List<File> allFiles = GrouperInstallerUtils.fileListRecursive(new File(appDir));
107       File driverJar = null;
108       for (File file : allFiles) {
109         if (file.getName().endsWith(".jar") && file.getName().startsWith(prefix)) {
110           
111           //find the latest file with correct prefix and suffix
112           if (driverJar == null || driverJar.lastModified() < file.lastModified()) {
113             driverJar = file;
114           }
115         }
116       }
117       
118       if (driverJar == null) {
119         System.out.println("Cant find driver jar that starts with '" + prefix + "' and ends with .jar!!! in directory: " + appDir);
120         System.exit(1);
121       }
122       try {
123         if (this.isMysql()) {
124           MySqlShim.init(driverJar);
125           MySqlShimrInstaller/driverShim/MySqlShim.html#MySqlShim">MySqlShim mysqlShim = new MySqlShim();
126           DriverManager.registerDriver(mysqlShim);
127         } else if (this.isOracle()) {
128           OracleShim.init(driverJar);
129           OracleShimnstaller/driverShim/OracleShim.html#OracleShim">OracleShim oracleShim = new OracleShim();
130           DriverManager.registerDriver(oracleShim);
131         } else if (this.isPostgres()) {
132           PostgresShim.init(driverJar);
133           PostgresShimller/driverShim/PostgresShim.html#PostgresShim">PostgresShim postgresShim = new PostgresShim();
134           DriverManager.registerDriver(postgresShim);
135         } else {
136           throw new RuntimeException("What kind of database is this???? " + this.url);
137         }
138       } catch (SQLException sqle) {
139         throw new RuntimeException("Problem registering driver: " + this.url, sqle);
140       }
141     }
142     driversRegistered.add(driver);
143   }
144   
145   /** 
146    * oracle types
147    */
148   public static enum DbType {
149 
150     /** string type */
151     STRING {
152 
153       /**
154        * 
155        * @see edu.internet2.middleware.GiDbUtils.util.GcDbUtils.DbType#processResultSet(java.sql.ResultSet, int)
156        */
157       @Override
158       public Object processResultSet(ResultSet resultSet, int indexZeroIndexed) {
159         try {
160           return resultSet.getString(indexZeroIndexed+1);
161         } catch (SQLException e) {
162           throw new RuntimeException("Error reading col (zero indexed) " + indexZeroIndexed, e);
163         }
164       }
165 
166       /**
167        * @see edu.internet2.middleware.GiDbUtils.util.GcDbUtils.DbType#attachParam(PreparedStatement, Object, int)
168        */
169       @Override
170       public void attachParam(PreparedStatement preparedStatement, Object arg,
171           int indexZeroIndexed) {
172         String argString = GrouperInstallerUtils.stringValue(arg);
173         try {
174           preparedStatement.setString(indexZeroIndexed+1, argString);
175         } catch (SQLException e) {
176           throw new RuntimeException("Error setting param (zero indexed) " + indexZeroIndexed + ": " + argString, e);
177         }
178       }
179     };
180 
181     /**
182      * get the data
183      * @param resultSet
184      * @param indexZeroIndexed
185      * @return the object or null
186      */
187     public abstract Object processResultSet(ResultSet resultSet, int indexZeroIndexed);
188     
189     /**
190      * attach a param to a prepared statement
191      * @param preparedStatement
192      * @param arg
193      * @param indexZeroIndexed
194      */
195     public abstract void attachParam(PreparedStatement preparedStatement, Object arg, int indexZeroIndexed);
196     
197     /**
198      * convert an object arg to a dbtype
199      * @param object
200      * @return dbtype
201      */
202     public static DbType fromObject(Object object) {
203       
204       if (object == null || object instanceof String) {
205         return DbType.STRING;
206       }
207       throw new RuntimeException("Unsupported type: " + GrouperInstallerUtils.className(object) + ", " + object);
208     }
209     
210     /**
211      * convert an object arg to a dbtype
212      * @param theClass
213      * @return dbtype
214      */
215     public static DbType fromClass(Class<?> theClass) {
216       
217       if (theClass instanceof Class<?> && String.class.equals(theClass)) {
218         return DbType.STRING;
219       }
220       
221       throw new RuntimeException("Unsupported type: " + theClass);
222     }
223     
224   }
225   
226   /**
227    * see if the config file seems to be postgres
228    * @return see if postgres
229    */
230   public boolean isPostgres() {
231     return isPostgres(this.url);
232   }
233   
234   /**
235    * see if the config file seems to be postgres
236    * @param connectionUrl
237    * @return see if postgres
238    */
239   public static boolean isPostgres(String connectionUrl) {
240     return GrouperInstallerUtils.defaultString(connectionUrl).toLowerCase().contains(":postgresql:");
241   }
242   
243   /**
244    * see if the config file seems to be oracle
245    * @return see if oracle
246    */
247   public boolean isOracle() {
248     return isOracle(this.url);
249   }
250   
251   /**
252    * see if the config file seems to be oracle
253    * @param connectionUrl
254    * @return see if oracle
255    */
256   public static boolean isOracle(String connectionUrl) {
257     return GrouperInstallerUtils.defaultString(connectionUrl).toLowerCase().contains(":oracle:");
258   }
259   
260   /**
261    * see if the config file seems to be mysql
262    * @return see if mysql
263    */
264   public boolean isMysql() {
265     return isMysql(this.url);
266   }
267   
268   /**
269    * see if the config file seems to be mysql
270    * @param connectionUrl
271    * @return see if mysql
272    */
273   public static boolean isMysql(String connectionUrl) {
274     return GrouperInstallerUtils.defaultString(connectionUrl).toLowerCase().contains(":mysql:");
275   }
276   
277   /**
278    * if there is no driver class specified, then try to derive it from the URL
279    * @param connectionUrl
280    * @param driverClassName
281    * @return the driver class
282    */
283   public static String convertUrlToDriverClassIfNeeded(String connectionUrl, String driverClassName) {
284     //default some of the stuff
285     if (GrouperInstallerUtils.isBlank(driverClassName)) {
286       
287       if (isMysql(connectionUrl)) {
288         driverClassName = "com.mysql.jdbc.Driver";
289       } else if (isOracle(connectionUrl)) {
290         driverClassName = "oracle.jdbc.driver.OracleDriver";
291       } else if (isPostgres(connectionUrl)) { 
292         driverClassName = "org.postgresql.Driver";
293       } else {
294         
295         //if this is blank we will figure it out later
296         if (!GrouperInstallerUtils.isBlank(connectionUrl)) {
297         
298           String error = "Cannot determine the driver class from database URL: " + connectionUrl;
299           System.err.println(error);
300           LOG.error(error);
301           return null;
302         }
303       }
304     }
305     return driverClassName;
306 
307   }
308 
309   /**
310    * get a connection to the oracle DB
311    * @return a connection
312    */
313   public Connection connection() {
314     
315     String oracleUrl = null;
316     
317     try {
318 
319       // this should be done with original driver or shim by now
320 //      String driverClass = convertUrlToDriverClassIfNeeded(this.url, null);
321 //  
322 //      GrouperInstallerUtils.forName(driverClass);
323       
324       Connection conn = DriverManager.getConnection(this.url, this.user, this.pass);
325       conn.setAutoCommit(false);
326       return conn;
327     } catch (Exception e) {
328       throw new RuntimeException("Error connecting to: " + oracleUrl, e);
329     }
330   }
331 
332   /**
333    * run a query and get rows back
334    * @param rowType type of each row returned, e.g. String.class or Object[]
335    * @param <T> generic type
336    * @param query
337    * @param returnColTypes
338    * @return the list of objects
339    */
340   public <T> List<T> listSelect(Class<T> rowType, String query, List<DbType> returnColTypes) {
341     return listSelect(rowType, query, returnColTypes, null);
342   }
343 
344   /**
345    * select a value from the db
346    * @param <T>
347    * @param colType
348    * @param query
349    * @return the data
350    */
351   public <T> T select(Class<T> colType, String query) {
352     List<T> rows = listSelect(colType, query, GrouperInstallerUtils.toList(DbType.fromClass(colType)));
353     
354     T data = GrouperInstallerUtils.listPopOne(rows);
355     
356     return data;
357   }
358   
359   /**
360    * run a query and get rows back
361    * @param rowType type of each row returned, e.g. String.class or Object[]
362    * @param <T> generic type
363    * @param query
364    * @param returnColTypes
365    * @param args preparedstatement arguments
366    * @return the list of objects
367    */
368   @SuppressWarnings("unchecked")
369   public <T> List<T> listSelect(Class<T> rowType, String query, List<DbType> returnColTypes, List<Object> args) {
370     Connection connection = null;
371     PreparedStatement preparedStatement = null;
372     ResultSet resultSet = null;
373     List<T> results = new ArrayList<T>();
374     
375     try {
376       
377       int returnColTypesLength = GrouperInstallerUtils.length(returnColTypes);
378       if (returnColTypesLength==0) {
379         throw new RuntimeException("Why is returnColTypesLength == 0???");
380       }
381       
382       connection = this.connection();
383       preparedStatement = connection.prepareStatement(query);
384       
385       //attach arguments
386       int argsLength = GrouperInstallerUtils.length(args);
387 
388       for (int i=0; i<argsLength;i++) {
389         Object arg = args.get(i);
390         DbType dbType = DbType.fromObject(arg);
391         dbType.attachParam(preparedStatement, arg, i);
392       }
393       
394       resultSet = preparedStatement.executeQuery();
395 
396       if (rowType.isArray() != returnColTypesLength>1) {
397         throw new RuntimeException("If returnColTypesLength > 1 (" + returnColTypesLength + ") then you must pass in an array as the return type");
398       }
399       
400       while (resultSet.next()) {
401         
402         //if array of cols
403         if (rowType.isArray()) {
404         
405           Object[] result = new Object[returnColTypesLength];
406           results.add((T)result);
407           
408           for (int i=0; i<returnColTypesLength;i++) {
409             result[i] = returnColTypes.get(i).processResultSet(resultSet, i);
410           }
411         } else {
412           //if one col
413           results.add((T)returnColTypes.get(0).processResultSet(resultSet, 0));
414         }
415         
416       }
417     } catch (Exception e) {
418       throw new RuntimeException("Error with query: " + query, e);
419     } 
420     finally {
421       rollbackQuietly(connection);
422       GrouperInstallerUtils.closeQuietly(resultSet);
423       GrouperInstallerUtils.closeQuietly(preparedStatement);
424       GrouperInstallerUtils.closeQuietly(connection);
425     }
426     return results;
427 
428   }
429   
430   /**
431    * rollback quiently
432    * @param connection
433    */
434   public void rollbackQuietly(Connection connection) {
435     try {
436       if (connection != null) {
437         connection.rollback();
438       }
439     } catch (Exception e) {
440       LOG.error("Problem rolling back", e);
441       //probably should rethrow but dont want to mess up the other closes...
442     }
443 
444   }
445   
446   /**
447    * execute a query (insert/update/delete/etc)
448    * @param query query to execute
449    * @param arg preparedstatement argument
450    * @return either (1) the row count for SQL Data Manipulation Language (DML) statements
451      *         or (2) 0 for SQL statements that return nothing
452    */
453   public int executeUpdate(String query, Object arg) {
454     
455     List<Object> args = GrouperInstallerUtils.toList(arg);
456     return executeUpdate(query, args);
457     
458   }
459   
460   /**
461    * execute a query (insert/update/delete/etc)
462    * @param query query to execute
463    * @return either (1) the row count for SQL Data Manipulation Language (DML) statements
464    *         or (2) 0 for SQL statements that return nothing
465    */
466   public int executeUpdate(String query) {
467     
468     return executeUpdate(query, null);
469     
470   }
471   
472   /**
473    * execute a query (insert/update/delete/etc)
474    * @param query query to execute
475    * @param args preparedstatement arguments
476    * @return either (1) the row count for SQL Data Manipulation Language (DML) statements
477      *         or (2) 0 for SQL statements that return nothing
478    */
479   public int executeUpdate(String query, List<Object> args) {
480     return executeUpdate(query, args, true);
481   }
482   
483   /**
484    * check connection to the db
485    * @return exception if there is one
486    */
487   public Exception checkConnection() {
488     
489     String query = checkConnectionQuery();
490     
491     try {
492       select(String.class, query);
493     } catch (Exception e) {
494       return e;
495     }
496     return null;
497   }
498 
499   /**
500    * check connection to the db
501    * @return the query to check connection with
502    */
503   public String checkConnectionQuery() {
504     if (this.isMysql()) {
505       return "select 1";
506     }
507     if (this.isOracle()) {
508       return "select 1 from dual";
509     }
510     if (this.isPostgres()) {
511       return "select 1";
512     }
513     throw new RuntimeException("Cant find which database type from URL: " + this.url);
514 
515   }
516 
517   /**
518    * execute a query (insert/update/delete/etc)
519    * @param query query to execute
520    * @param args preparedstatement arguments
521    * @param commit if we should commit
522    * @return either (1) the row count for SQL Data Manipulation Language (DML) statements
523      *         or (2) 0 for SQL statements that return nothing
524    */
525   public int executeUpdate(String query, List<Object> args, boolean commit) {
526     Connection connection = null;
527     PreparedStatement preparedStatement = null;
528 
529     try {
530       connection = this.connection();
531       preparedStatement = connection.prepareStatement(query);
532       
533       //attach arguments
534       int argsLength = GrouperInstallerUtils.length(args);
535 
536       for (int i=0; i<argsLength;i++) {
537         Object arg = args.get(i);
538         DbType dbType = DbType.fromObject(arg);
539         dbType.attachParam(preparedStatement, arg, i);
540       }
541       
542       int result = preparedStatement.executeUpdate();
543       
544       if (commit) {
545         connection.commit();
546       }
547       
548       return result;
549       
550     } catch (Exception e) {
551       rollbackQuietly(connection);
552       throw new RuntimeException("Error with query: " + query, e);
553     } 
554     finally {
555       GrouperInstallerUtils.closeQuietly(preparedStatement);
556       GrouperInstallerUtils.closeQuietly(connection);
557     }
558 
559   }
560 
561   /**
562    * 
563    */
564   private static Log LOG = GrouperInstallerUtils.retrieveLog(GiDbUtils.class);
565 }