View Javadoc

1   /**
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd.util.database;
5   
6   import java.io.IOException;
7   import java.io.UnsupportedEncodingException;
8   import java.net.URI;
9   import java.net.URISyntaxException;
10  import java.net.URLDecoder;
11  import java.util.Arrays;
12  import java.util.HashMap;
13  import java.util.List;
14  import java.util.Map;
15  import java.util.logging.Level;
16  import java.util.logging.Logger;
17  
18  /**
19   * Provide a single parameter to specify database objects to process.
20   *
21   * <p>
22   * Wrap JDBC settings for use by PMD: optional parameters specify the source code to
23   * be passed to PMD, or are inherited from the associated {@link DBType}.
24   * </p>
25   *
26   *<p>A DBURI is a <i>faux</i>-URI: it does not have a formal specification and  comprises a JDBC(-ish) URL and an optional query, e.g. <code>jdbc : subprotocol  [ : subname ] : connection details [ query ] </code>.
27   * 
28   * The subprotocol and optional subname parts should be a valid DBType
29   * JDBC(-ish) URL jdbc:oracle:thin:username/password@//192.168.100.21:1521/ORCL
30   * JDBC(-ish) URL jdbc:thin:username/password@//192.168.100.21:1521/ORCL
31   * 
32   * <p>The query includes one or more of these:- 
33   * <dl>
34   * <dt>characterset</dt><dd>utf8</dd>
35   * <dt>languages</dt><dd>comma-separated list of desired PMD languages</dd>
36   * <dt>schemas</dt><dd>comma-separated list of database schemas</dd>
37   * <dt>sourcecodetypes</dt><dd>comma-separated list of database source code types</dd>
38   * <dt>sourcecodenames</dt><dd>comma-separated list of database source code names</dd>
39   * </dl>
40   * </p>
41   * 
42   *  @see http://docs.oracle.com/javase/7/docs/api/java/net/URI.html
43   * @author sturton
44   */
45  public class DBURI {
46  
47  private final static String CLASS_NAME = DBURI.class.getCanonicalName();
48  
49  private final static Logger LOGGER = Logger.getLogger(CLASS_NAME); 
50  
51  
52    /**
53     * A JDBC URL with an associated query.
54     * 
55     * Formats:
56     * jdbc:oracle:thin:[<user>/<password>]@//<host>[:<port>]/<service> 
57     * jdbc:oracle:oci:[<user>/<password>]@//<host>[:<port>]/<service>
58     *
59     * Example:
60     * jdbc:oracle:thin:@//myserver.com/customer_db 
61     * jdbc:oracle:oci:scott/tiger@//myserver.com:5521/customer_db
62     */
63  
64    private URI uri;
65  
66    private DBType dbType;
67  
68    private String url ;
69  
70    /**
71     * JDBC subprotocol  
72     */
73    private String subprotocol ;
74    
75    /**
76     * JDBC subname prefix   
77     */
78    private String subnamePrefix ;
79    
80    /**
81     * Parameters from URI
82     */
83    private  Map<String, String> parameters ;
84  
85    //Schema List - defaults to connecting user
86    private List<String> schemasList ;
87  
88    //Object Type List - potentially inferred from the JDBC URL
89    private List<String> sourceCodeTypesList ;
90  
91    //source Code Type List 
92    private List<String> sourceCodeNamesList ;
93  
94    //Language List - potentially inferred from the JDBC URL
95    private List<String> languagesList ;
96  
97    //Driver Class - potentially inferred from the JDBC URL
98    private String driverClass ;
99  
100   //Database CharacterSet
101   private String characterSet;
102 
103   //String to get objects - defaults inferred from the JDBC URL
104   private String sourceCodeTypes;
105 
106   //String to get source code - defaults inferred from the JDBC URL
107   private String sourceCodeNames;
108   
109   //languages to process - defaults inferred from the JDBC URL
110   private String languages;
111 
112   //Return class for source code, mapped fron java.sql.Types 
113   private int sourceCodeType; 
114   
115 
116   /**
117    * Create DBURI from a string, combining a JDBC URL and query parameters.
118    * 
119    *<p> 
120    * From the JDBC URL component, infer:- 
121    * <ul>
122    * <li>JDBC driver class</li>
123    * <li>supported languages</li>
124    * <li>default source code types</li>
125    * <li>default schemas</li>
126    * </ul>
127    *</p> 
128    * 
129    *<p> 
130    * From the query component, define these values, overriding any defaults:- 
131    * <ul>
132    * <li>parsing language</li>
133    * <li>source code types</li>
134    * <li>schemas</li>
135    * <li>source code</li>
136    * </ul>
137    *</p> 
138    * 
139    * @param  string URL string
140    * @throws URISyntaxException
141    * @throws Exception
142    */
143   public DBURI(String string) throws URISyntaxException
144   {
145     /*
146      * A JDBC URL is an opaque URL and does not have a query.
147      * 
148      * We pretend that it does,
149      * strip off the query,
150      * use the real JDBC URL component to infer 
151      *   languages
152      *   JDBC driver class
153      *   supported languages
154      *   default source code types
155      *   default schemas
156      * generate a faux HTTP URI with the query,
157      * extract the query parameters 
158      */
159 
160     uri = new URI (string);
161 
162     try
163     {
164       //Split the string between JDBC URL and the query
165       String[] splitURI = string.split("\\?");
166 
167       if (splitURI.length > 1)
168       {
169         url = splitURI[0];
170       }
171       else
172       {
173         url = string;
174       }
175 
176       LOGGER.log(Level.FINE, "Extracted URL={0}", url);
177 
178       //Explode URL into its separate components
179       setFields() ;
180 
181       //If the original URI string contained a query component, split it into parameters  
182       if (splitURI.length > 1)
183       {
184         //Generate a fake HTTP URI to allow easy extraction of the query parameters 
185         String chimeraString = "http://local?" + string.substring(url.length()+1); 
186         LOGGER.log(Level.FINEST, "chimeraString={0}", chimeraString);
187         URI chimeraURI = new URI(chimeraString) ; 
188         dump("chimeraURI",chimeraURI);
189         
190         parameters = getParameterMap(chimeraURI);
191 
192         LOGGER.log(Level.FINEST, "parameterMap=={0}", parameters);
193 
194         characterSet = parameters.get("characterset");
195         sourceCodeTypes = parameters.get("sourcecodetypes");
196         sourceCodeNames = parameters.get("sourcecodenames");
197         languages = parameters.get("languages");
198 
199         //Populate the lists 
200         if (null!=sourceCodeNames)
201         {
202           sourceCodeNamesList = Arrays.asList(sourceCodeNames.split(","));
203         }
204 
205         if (null!=languages)
206         {
207           languagesList = Arrays.asList(languages.split(","));
208         }
209 
210         if (null!=parameters.get("schemas"))
211         {
212           schemasList = Arrays.asList(parameters.get("schemas").split(","));
213         }
214 
215         if (null!=sourceCodeTypes)
216         {
217           sourceCodeTypesList = Arrays.asList(sourceCodeTypes.split(","));
218         }
219 
220       }
221 
222     } catch (URISyntaxException ex) {
223       URISyntaxException uriException = new URISyntaxException(string, "Problem generating DBURI.");
224       uriException.initCause(ex);
225       throw uriException;
226     } catch (IOException e) {
227         throw new RuntimeException(e);
228     }
229   }
230 
231   /**
232    * Create a DBURI from standard individual {@link URI} components.
233    * 
234    *<p> 
235    * From the JDBC URL components, infer:- 
236    * <ul>
237    * <li>JDBC driver class</li>
238    * <li>supported languages</li>
239    * <li>default source code types</li>
240    * <li>default schemas</li>
241    * </ul>
242    *</p> 
243    * 
244    *<p> 
245    * From the query component, define these values, overriding any defaults:- 
246    * <ul>
247    * <li>parsing language</li>
248    * <li>source code types</li>
249    * <li>schemas</li>
250    * <li>source code</li>
251    * </ul>
252    *</p> 
253    * 
254    * @param scheme 
255    * @param userInfo
256    * @param host
257    * @param port
258    * @param path
259    * @param query
260    * @param fragment
261    * @throws URISyntaxException
262    */
263   public DBURI(String scheme, String userInfo, String host, int port, String path, String query, String fragment)
264   throws URISyntaxException
265   {
266    uri = new URI(scheme, userInfo, host, port, path, query, fragment);
267 
268   }
269 
270   /**
271    * Return extracted parameters from dburi.
272    * 
273    * @param dburi
274    * @return extracted parameters
275    * @throws UnsupportedEncodingException 
276    */
277   private Map<String, String> getParameterMap (URI dburi) throws UnsupportedEncodingException {
278 
279     Map<String, String> map = new HashMap<String, String>();  
280     String query = dburi.getRawQuery();
281     LOGGER.log(Level.FINEST, "dburi,getQuery()={0}", query);
282     if (null != query && !query.equals(""))
283     {
284       String[] params = query.split("&");  
285       for (String param : params)  
286       {  
287           String[] splits = param.split("=");  
288           String name =splits[0];
289           String value = null ;
290           if (splits.length > 1 ) 
291           {
292             value = splits[1] ;
293           }
294           map.put(name, (null==value) ? value:  URLDecoder.decode(value,"UTF-8"));  
295       }  
296     }
297     return map;  
298   }
299 
300   /**
301    * Dump this URI to the log.
302    * 
303    * @param description
304    * @param dburi 
305    */
306   static void dump(String description , URI dburi) {
307 
308     String dumpString = String.format(
309                      "dump (%s)\n: isOpaque=%s, isAbsolute=%s Scheme=%s,\n SchemeSpecificPart=%s,\n Host=%s,\n Port=%s,\n Path=%s,\n Fragment=%s,\n Query=%s\n"
310                      , description
311                      , dburi.isOpaque()
312                      , dburi.isAbsolute()
313                      , dburi.getScheme()
314                      , dburi.getSchemeSpecificPart()
315                      , dburi.getHost()
316                      , dburi.getPort()
317                      , dburi.getPath()
318                      , dburi.getFragment()
319                      , dburi.getQuery()
320                      );
321 
322     LOGGER.fine(dumpString);
323 
324     String query = dburi.getQuery();
325     if (null != query && !query.equals(""))
326     {
327       String[] params = query.split("&");  
328       Map<String, String> map = new HashMap<String, String>();  
329       for (String param : params)  
330       {  
331           String[] splits = param.split("=");  
332           String name =splits[0];
333           String value = null ;
334           if (splits.length > 1 ) 
335           {
336             value = splits[1] ;
337           }
338           map.put(name, value);  
339           LOGGER.fine(String.format("name=%s,value=%s\n",name,value));
340       }  
341     }
342     //return map;  
343   }
344 
345   public URI getUri() {
346     return uri;
347   }
348 
349   public void setUri(URI uri) {
350     this.uri = uri;
351   }
352 
353   public DBType getDbType() {
354     return dbType;
355   }
356 
357   public void setDbType(DBType dbType) {
358     this.dbType = dbType;
359   }
360 
361   public List<String> getSchemasList() {
362     return schemasList;
363   }
364 
365   public void setSchemasList(List<String> schemasList) {
366     this.schemasList = schemasList;
367   }
368 
369   public List<String> getSourceCodeTypesList() {
370     return sourceCodeTypesList;
371   }
372 
373   public void setSourceCodeTypesList(List<String> sourceCodeTypesList) {
374     this.sourceCodeTypesList = sourceCodeTypesList;
375   }
376 
377   public List<String> getSourceCodeNamesList() {
378     return sourceCodeNamesList;
379   }
380 
381   public void setSourceCodeNamesList(List<String> sourceCodeNamesList) {
382     this.sourceCodeNamesList = sourceCodeNamesList;
383   }
384 
385   public List<String> getLanguagesList() {
386     return languagesList;
387   }
388 
389   public void setLanguagesList(List<String> languagesList) {
390     this.languagesList = languagesList;
391   }
392 
393   public String getDriverClass() {
394     return driverClass;
395   }
396 
397   public void setDriverClass(String driverClass) {
398     this.driverClass = driverClass;
399   }
400 
401   public String getCharacterSet() {
402     return characterSet;
403   }
404 
405   public void setCharacterSet(String characterSet) {
406     this.characterSet = characterSet;
407   }
408 
409   public int getSourceCodeType() {
410     return sourceCodeType;
411   }
412 
413   public void setSourceCodeType(int sourceCodeType) {
414     this.sourceCodeType = sourceCodeType;
415   }
416 
417   public String getSubprotocol() {
418     return subprotocol;
419   }
420 
421   public void setSubprotocol(String subprotocol) {
422     this.subprotocol = subprotocol;
423   }
424 
425   public String getSubnamePrefix() {
426     return subnamePrefix;
427   }
428 
429   public void setSubnamePrefix(String subnamePrefix) {
430     this.subnamePrefix = subnamePrefix;
431   }
432 
433   public Map<String, String> getParameters() {
434     return parameters;
435   }
436 
437   public void setParameters(Map<String, String> parameters) {
438     this.parameters = parameters;
439   }
440 
441   /**
442    * @return the url
443    */
444   public String getURL() {
445     return url;
446   }
447 
448   /**
449    * @param url the url to set
450    */
451   public void setURL(String jdbcURL) {
452     this.url = jdbcURL;
453   }
454 
455   /**
456    * Populate the URI and query collections from the original string
457    * 
458    * @throws URISyntaxException 
459    * @throws IOException 
460    */
461   private void setFields() throws URISyntaxException, IOException {
462     if (url.startsWith("jdbc:"))
463     {
464       //java.net.URI is intended for "normal" URLs
465       URI jdbcURI = new URI(getURL().substring(5)) ; 
466 
467       LOGGER.log(Level.FINE, "setFields - substr(jdbcURL,5):{0}", getURL().substring(5)) ; 
468       dump("substr(jdbcURL,5)", jdbcURI);
469 
470       // jdbc:subprotocol:subname
471       String[] uriParts = url.split(":"); 
472       for ( String part : uriParts)
473       {
474         LOGGER.log(Level.FINEST, "JDBCpart={0}", part);
475       }
476 
477       /* Expect jdbc : subprotocol  [ : subname ] : connection details  
478        *  uriParts.length < 3 Error
479        *  uriParts.length = 3 Driver information may be inferred from part[1] - the subprotocol
480        *  uriParts.length >= 4 Driver information may be inferred from part[2]- the first part of the subname
481        */ 
482       if ( 3 == uriParts.length )
483       {
484         subprotocol = uriParts[1];
485       }
486       else if ( 4 <= uriParts.length )
487       {
488         subprotocol = uriParts[1];
489         subnamePrefix = uriParts[2];
490       }
491       else 
492       {
493         throw new URISyntaxException(getURL(), "Could not understand JDBC URL",1);
494       }
495 
496       LOGGER.log(Level.FINE, "subprotocol={0}'' subnamePrefix={1}", new Object[]{subprotocol, subnamePrefix});
497 
498       //Set values from DBType defaults 
499       this.dbType = new DBType(subprotocol, subnamePrefix) ;
500 
501       LOGGER.log(Level.FINER, "DBType properties found at {0} with {1} properties.", new Object[]{dbType.getPropertiesSource(), dbType.getProperties().size()});
502 
503       LOGGER.log(Level.FINEST, "DBType properties are:- {0}", dbType.getProperties());
504                    
505 
506       if (null!= dbType.getDriverClass())
507       {
508         this.driverClass = dbType.getDriverClass() ;
509       }
510 
511       if (null!= dbType.getCharacterSet()   )
512       {
513         this.characterSet = dbType.getCharacterSet() ;
514       }
515 
516       if (null!= dbType.getLanguages())
517       {
518         this.languages = dbType.getLanguages() ;
519       }
520 
521       if (null!= dbType.getSourceCodeTypes())
522       {
523         sourceCodeTypes = dbType.getSourceCodeTypes();
524       }
525 
526       LOGGER.finer("DBType other properties follow  ...") ;
527 
528       if (null!=dbType.getProperties().getProperty("schemas") ) 
529       {
530         schemasList = Arrays.asList(dbType.getProperties().getProperty("schemas").split(",") ); 
531       }
532 
533       sourceCodeNames = dbType.getProperties().getProperty("sourcecodenames") ;
534 
535       String returnType = dbType.getProperties().getProperty("returnType") ;
536       if (null != returnType) 
537       {
538         sourceCodeType = Integer.parseInt(returnType);
539       }
540 
541       LOGGER.finer("DBType populating lists ") ;
542       //Populate the lists 
543       if (null!=sourceCodeNames)
544       {
545         sourceCodeNamesList = Arrays.asList(sourceCodeNames.split(","));
546       }
547 
548       if (null!=languages)
549       {
550         languagesList = Arrays.asList(languages.split(","));
551       }
552 
553       if (null!=sourceCodeTypes)
554       {
555         sourceCodeTypesList = Arrays.asList(sourceCodeTypes.split(","));
556       }
557 
558       LOGGER.finer("DBType lists generated") ;
559     }
560 
561   }
562   
563   @Override
564   public String toString()
565   {
566     return uri.toASCIIString();
567   }
568 }