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