View Javadoc
1   /**
2    * Copyright 2014 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.subject.provider;
21  
22  import java.sql.Connection;
23  import java.sql.SQLException;
24  import java.util.ArrayList;
25  import java.util.List;
26  import java.util.regex.Matcher;
27  import java.util.regex.Pattern;
28  
29  import org.apache.commons.lang.StringUtils;
30  
31  import edu.internet2.middleware.subject.SubjectUtils;
32  
33  
34  
35  /**
36   * type of database we are connecting to
37   */
38  public enum JdbcDatabaseType {
39  
40    /** oracle db */
41    oracle {
42  
43      /**
44       * 
45       * @see edu.internet2.middleware.subject.provider.JdbcDatabaseType#pageQuery(java.lang.String, int)
46       */
47      @Override
48      public String pageQuery(String query, int pageSize) {
49        
50        String selectPart = selectPart(query);
51        if (selectPart == null) {
52          return query;
53        }
54        
55        List<String> aliases = columnAliases(selectPart);
56        
57        if (aliases == null) {
58          return query;
59        }
60        
61        //lets build the new statement
62        StringBuilder result = new StringBuilder("select ");
63        
64        for (int i=0;i<aliases.size();i++) {
65          result.append(aliases.get(i));
66          if (i < (aliases.size()-1)) {
67            result.append(", ");
68          }
69        }
70        result.append(" from (").append(query).append(") where rownum <= ").append(pageSize);
71        return result.toString();
72      }
73  
74  
75      /**
76       * 
77       * @see edu.internet2.middleware.subject.provider.JdbcDatabaseType#matchesUrlDefinitely(java.lang.String)
78       */
79      @Override
80      public boolean matchesUrlDefinitely(String url) {
81        return url != null && url.toLowerCase().startsWith("jdbc:oracle:");
82      }
83  
84      /**
85       * 
86       * @see edu.internet2.middleware.subject.provider.JdbcDatabaseType#matchesUrlMaybe(java.lang.String)
87       */
88      @Override
89      public boolean matchesUrlMaybe(String url) {
90        return url != null && url.toLowerCase().contains("oracle");
91      }
92  
93    },
94    
95    /** mysql db */
96    mysql {
97  
98      /**
99       * 
100      * @see edu.internet2.middleware.subject.provider.JdbcDatabaseType#pageQuery(java.lang.String, int)
101      */
102     @Override
103     public String pageQuery(String query, int pageSize) {
104       return query + " limit 0," + pageSize;//
105     }
106 
107     /**
108      * 
109      * @see edu.internet2.middleware.subject.provider.JdbcDatabaseType#matchesUrlDefinitely(java.lang.String)
110      */
111     @Override
112     public boolean matchesUrlDefinitely(String url) {
113       return url != null && url.toLowerCase().startsWith("jdbc:mysql:");
114     }
115 
116     /**
117      * 
118      * @see edu.internet2.middleware.subject.provider.JdbcDatabaseType#matchesUrlMaybe(java.lang.String)
119      */
120     @Override
121     public boolean matchesUrlMaybe(String url) {
122       return url != null && url.toLowerCase().contains("mysql");
123     }
124   },
125   
126   /** postgres */
127   postgres {
128 
129     /**
130      * 
131      * @see edu.internet2.middleware.subject.provider.JdbcDatabaseType#pageQuery(java.lang.String, int)
132      */
133     @Override
134     public String pageQuery(String query, int pageSize) {
135       return query + " limit " + pageSize;//
136     }
137 
138     /**
139      * 
140      * @see edu.internet2.middleware.subject.provider.JdbcDatabaseType#matchesUrlDefinitely(java.lang.String)
141      */
142     @Override
143     public boolean matchesUrlDefinitely(String url) {
144       return url != null && url.toLowerCase().startsWith("jdbc:postgresql:");
145     }
146 
147     /**
148      * 
149      * @see edu.internet2.middleware.subject.provider.JdbcDatabaseType#matchesUrlMaybe(java.lang.String)
150      */
151     @Override
152     public boolean matchesUrlMaybe(String url) {
153       return url != null && url.toLowerCase().contains("postgres");
154     }
155 
156   },
157   
158   /** hsql */
159   hsqldb {
160 
161     /**
162      * 
163      * @see edu.internet2.middleware.subject.provider.JdbcDatabaseType#pageQuery(java.lang.String, int)
164      */
165     @Override
166     public String pageQuery(String query, int pageSize) {
167       return null;
168     }
169 
170     /**
171      * 
172      * @see edu.internet2.middleware.subject.provider.JdbcDatabaseType#matchesUrlDefinitely(java.lang.String)
173      */
174     @Override
175     public boolean matchesUrlDefinitely(String url) {
176       return url != null && url.toLowerCase().startsWith("jdbc:hsqldb:");
177     }
178 
179     /**
180      * 
181      * @see edu.internet2.middleware.subject.provider.JdbcDatabaseType#matchesUrlMaybe(java.lang.String)
182      */
183     @Override
184     public boolean matchesUrlMaybe(String url) {
185       return url != null && url.toLowerCase().contains("hsql");
186     }
187   };
188 
189   /**
190    * return the database type for this connection or null
191    * @param connection
192    * @return the database type
193    */
194   public static JdbcDatabaseType resolveDatabaseType(Connection connection) {
195     String url = null;
196     
197     try {
198       url = connection.getMetaData().getURL();
199     } catch (SQLException sqle) {
200       return null;
201     }
202     
203     for (JdbcDatabaseType jdbcDatabaseType : JdbcDatabaseType.values()) {
204       if (jdbcDatabaseType.matchesUrlDefinitely(url)) {
205         return jdbcDatabaseType;
206       }
207     }
208     
209     for (JdbcDatabaseType jdbcDatabaseType : JdbcDatabaseType.values()) {
210       if (jdbcDatabaseType.matchesUrlMaybe(url)) {
211         return jdbcDatabaseType;
212       }
213     }
214     
215     return null;
216    }
217   
218   /** get the select part of a SQL */
219   private static Pattern selectClausePattern = Pattern.compile("^(.*?\\s)(from\\s.*)$", Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
220 
221   /** get the select part of a SQL */
222   private static Pattern lastWordPattern = Pattern.compile("^.*\\s(.*)$", Pattern.DOTALL);
223 
224   /**
225    * get the column aliases from a query select a,b,c from
226    * @param fromClause
227    * @return the aliases
228    */
229   public static List<String> columnAliases(String fromClause) {
230     if (fromClause == null) {
231       return null;
232     }
233     String[] cols = SubjectUtils.splitTrim(fromClause, ",");
234     List<String> result = new ArrayList<String>();
235     //we need the last whole string
236     String alias = null;
237     for (String col : cols) {
238       Matcher matcher = lastWordPattern.matcher(col);
239       //see if there are spaces
240       if (matcher.matches()) {
241         alias = matcher.group(1);
242       } else {
243         alias = col;
244       }
245       //we dont do this...
246       if (StringUtils.equals("*", alias)) {
247         return null;
248       }
249       result.add(alias);
250     }
251     return result;
252   }
253   
254   /**
255    * get the select part from a query
256    * @param query
257    * @return the select part, everything up to the from, or null if cant find
258    */
259   public static String selectPart(String query) {
260     
261     if (query == null) {
262       return null;
263     }
264     
265     String fromAndAfter = query;
266     String upToFrom = "";
267     
268     while (true) {
269       Matcher matcher = selectClausePattern.matcher(fromAndAfter);
270       
271       if (!matcher.matches()) {
272         return null;
273       }
274       
275       upToFrom += matcher.group(1);
276       fromAndAfter = matcher.group(2);
277       
278       //see if up to from is good with parens
279       int leftParenCount = StringUtils.countMatches(upToFrom, "(");
280       int rightParenCount = StringUtils.countMatches(upToFrom, ")");
281       
282       if (leftParenCount == rightParenCount) {
283         return upToFrom;
284       }
285     }    
286     
287   }
288   
289   /**
290    * change a query into a paging query
291    * @param query
292    * @param pageSize
293    * @return the new query
294    */
295   public abstract String pageQuery(String query, int pageSize);
296   
297   /**
298    * 
299    * @param url
300    * @return true if this is definitely this db type
301    */
302   public abstract boolean matchesUrlDefinitely(String url);
303 
304   /**
305    * 
306    * @param url
307    * @return true if this is maybe this db type
308    */
309   public abstract boolean matchesUrlMaybe(String url);
310 
311   
312   
313   /**
314    * do a case-insensitive matching
315    * 
316    * @param string
317    * @param exceptionOnNull will not allow null or blank entries
318    * @return the enum or null or exception if not found
319    */
320   public static JdbcDatabaseType valueOfIgnoreCase(String string, boolean exceptionOnNull) {
321     return SubjectUtils.enumValueOfIgnoreCase(JdbcDatabaseType.class, 
322         string, exceptionOnNull);
323   
324   }
325   
326   
327   
328 }