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.File;
7   import java.io.FileInputStream;
8   import java.io.FileNotFoundException;
9   import java.io.IOException;
10  import java.util.Properties;
11  import java.util.PropertyResourceBundle;
12  import java.util.ResourceBundle;
13  import java.util.logging.Level;
14  import java.util.logging.Logger;
15  
16  /**
17   * Encapsulate the settings needed to access database source code.
18   * 
19   * 
20   * @author sturton
21   */
22  public class DBType {
23      private final static String CLASS_NAME = DBType.class.getCanonicalName();
24  
25      private final static Logger LOGGER = Logger.getLogger(DBType.class.getPackage().getName());
26  
27      private final static String INTERNAL_SETTINGS = "[Internal Settings]";
28  
29      /**
30       * The names of the properties
31       */
32      public enum property {
33          USER("user", "Name of the connecting database user"), PASSWORD("password",
34                  "The connecting database user's password"), DRIVER("driver", "JDBC driver classname"), CHARACTERSET(
35                  "characterset", "Reader character set"), LANGUAGES("languages",
36                  "Comma-separated list of PMD-supported languages"), SCHEMAS("schemas",
37                  "SchemaSpy compatible regular expression for schemas to be processed"), SOURCE_TYPES("sourcecodetypes",
38                  "Comma-separated list of supported source types"), SOURCE_NAMES("sourcecodenames",
39                  "Default comma-separated list of source code names to validate"), GET_SOURCE_CODE_STATEMENT(
40                  "getSourceCodeStatement",
41                  "SQL92 or Oracle embedded SQL statement to retrieve  code source from the database catalogue"), RETURN_TYPE(
42                  "returnType", "int equivalent of java.sql.Types return type of getSourceCodeStatement");
43  
44          private String name;
45          private String description;
46  
47          private property(String name, String description) {
48              this.name = name;
49              this.description = description;
50          }
51  
52          public String getPropertyName() {
53              return name;
54          }
55  
56          public String getDescription() {
57              return description;
58          }
59      }
60  
61      /**
62       * Where the properties were taken from
63       */
64      private String propertiesSource;
65  
66      /**
67       * Parameters from Properties
68       */
69      private Properties properties;
70  
71      // Driver Class
72      private String driverClass;
73  
74      // Database CharacterSet
75      private String characterSet;
76  
77      // String to get objects
78      private String sourceCodeTypes;
79  
80      // Languages to process
81      private String languages;
82  
83      // Return class for source code
84      private int sourceCodeReturnType;
85  
86      /**
87       * 
88       * @param dbType
89       */
90      public DBType(String dbType) throws Exception {
91          properties = loadDBProperties(dbType);
92      }
93  
94      /**
95       * Load the most specific dbType for the protocol
96       * 
97       * @param subProtocol
98       * @param subnamePrefix
99       * @throws IOException
100      */
101     public DBType(String subProtocol, String subnamePrefix) throws IOException {
102 
103         if (LOGGER.isLoggable(Level.FINE)) {
104             LOGGER.fine("subProtocol=" + subProtocol + ", subnamePrefix=" + subnamePrefix);
105         }
106 
107         if (null == subProtocol && null == subnamePrefix) {
108             throw new RuntimeException("subProtocol and subnamePrefix cannot both be null");
109         } else {
110 
111             properties = null;
112 
113             // Attempt subnamePrefix before subProtocol
114             if (subnamePrefix != null) {
115                 properties = loadDBProperties(subnamePrefix);
116             }
117             if (properties == null && subProtocol != null) {
118                 properties = loadDBProperties(subProtocol);
119             }
120 
121             if (subnamePrefix != null && properties != null) {
122                 LOGGER.log(Level.FINE, "DBType found using subnamePrefix={0}", subnamePrefix);
123             } else if (subProtocol != null && properties != null) {
124                 LOGGER.log(Level.FINE, "DBType found using subProtocol={0}", subProtocol);
125             } else {
126                 throw new RuntimeException(String.format(
127                         "Could not locate DBType properties using subProtocol=%s and subnamePrefix=%s", subProtocol,
128                         subnamePrefix));
129             }
130 
131         }
132     }
133 
134     public Properties getProperties() {
135 
136         return properties;
137     }
138 
139     /**
140      * Load properties from one or more files or resources.
141      * 
142      * <p>
143      * This method recursively finds property files or JAR resources matching
144      * {@matchstring}.
145      * </p>
146      * .
147      * <p>
148      * The method is intended to be able to use , so any
149      *
150      * @param matchString
151      * @return "current" set of properties (from one or more resources/property
152      *         files)
153      */
154     private Properties loadDBProperties(String matchString) throws IOException {
155         LOGGER.entering(CLASS_NAME, matchString);
156         // Locale locale = Control.g;
157         ResourceBundle resourceBundle = null;
158 
159         if (LOGGER.isLoggable(Level.FINEST)) {
160             LOGGER.finest("class_path+" + System.getProperty("java.class.path"));
161         }
162 
163         /*
164          * Attempt to match properties files in this order:- File path with
165          * properties suffix File path without properties suffix Resource
166          * without class prefix Resource with class prefix
167          */
168         try {
169             File propertiesFile = new File(matchString);
170             if (LOGGER.isLoggable(Level.FINEST)) {
171                 LOGGER.finest("Attempting File no file suffix: " + matchString);
172             }
173             resourceBundle = new PropertyResourceBundle(new FileInputStream(propertiesFile));
174             propertiesSource = propertiesFile.getAbsolutePath();
175             LOGGER.finest("FileSystemWithoutExtension");
176         } catch (FileNotFoundException notFoundOnFilesystemWithoutExtension) {
177             if (LOGGER.isLoggable(Level.FINEST)) {
178                 LOGGER.finest("notFoundOnFilesystemWithoutExtension");
179                 LOGGER.finest("Attempting File with added file suffix: " + matchString + ".properties");
180             }
181             try {
182                 File propertiesFile = new File(matchString + ".properties");
183                 resourceBundle = new PropertyResourceBundle(new FileInputStream(propertiesFile));
184                 propertiesSource = propertiesFile.getAbsolutePath();
185                 LOGGER.finest("FileSystemWithExtension");
186             } catch (FileNotFoundException notFoundOnFilesystemWithExtensionTackedOn) {
187                 if (LOGGER.isLoggable(Level.FINEST)) {
188                     LOGGER.finest("Attempting JARWithoutClassPrefix: " + matchString);
189                 }
190                 try {
191                     resourceBundle = ResourceBundle.getBundle(matchString);
192                     propertiesSource = "[" + INTERNAL_SETTINGS + "]" + File.separator + matchString + ".properties";
193                     LOGGER.finest("InJarWithoutPath");
194                 } catch (Exception notInJarWithoutPath) {
195                     if (LOGGER.isLoggable(Level.FINEST)) {
196                         LOGGER.finest("Attempting JARWithClass prefix: " + DBType.CLASS_NAME + "." + matchString);
197                     }
198                     try {
199                         resourceBundle = ResourceBundle.getBundle(DBType.CLASS_NAME + "." + matchString);
200                         propertiesSource = "[" + INTERNAL_SETTINGS + "]" + File.separator + matchString + ".properties";
201                         LOGGER.finest("found InJarWithPath");
202                     } catch (Exception notInJarWithPath) {
203                         notInJarWithPath.printStackTrace();
204                         notFoundOnFilesystemWithExtensionTackedOn.printStackTrace();
205                         throw new RuntimeException(" Could not locate DBTYpe settings : " + matchString,
206                                 notInJarWithPath);
207                     }
208                 }
209             }
210         }
211 
212         // Properties in this matched resource
213         Properties matchedProperties = getResourceBundleAsProperties(resourceBundle);
214         resourceBundle = null;
215         String saveLoadedFrom = getPropertiesSource();
216 
217         /*
218          * If the matched properties contain the "extends" key, use the value as
219          * a matchstring, to recursively set the properties before overwriting
220          * any previous properties with the matched properties.
221          */
222         String extendedPropertyFile = (String) matchedProperties.remove("extends");
223         if (null != extendedPropertyFile && !"".equals(extendedPropertyFile.trim())) {
224             Properties extendedProperties = loadDBProperties(extendedPropertyFile.trim());
225 
226             // Overwrite extended properties with properties in the matched
227             // resource
228             extendedProperties.putAll(matchedProperties);
229             matchedProperties = extendedProperties;
230         }
231 
232         /*
233          * Record the location of the original matched resource/property file,
234          * and the current set of properties secured.
235          */
236         propertiesSource = saveLoadedFrom;
237         setProperties(matchedProperties);
238 
239         return matchedProperties;
240     }
241 
242     /**
243      * Options that are specific to a type of database. E.g. things like
244      * <code>host</code>, <code>port</code> or <code>db</code>, but <b>don't</b>
245      * have a setter in this class.
246      *
247      * @param dbSpecificOptions
248      */
249 
250     /**
251      * Convert <code>resourceBundle</code> to usable {@Properties}.
252      *
253      * @param resourceBundle ResourceBundle
254      * @return Properties
255      */
256     public static Properties getResourceBundleAsProperties(ResourceBundle resourceBundle) {
257         Properties properties = new Properties();
258         for (String key : resourceBundle.keySet()) {
259             properties.put(key, resourceBundle.getObject(key));
260         }
261 
262         return properties;
263     }
264 
265     @Override
266     public int hashCode() {
267         final int prime = 31;
268         int result = 1;
269         result = prime * result + ((characterSet == null) ? 0 : characterSet.hashCode());
270         result = prime * result + ((driverClass == null) ? 0 : driverClass.hashCode());
271         result = prime * result + ((languages == null) ? 0 : languages.hashCode());
272         result = prime * result + ((properties == null) ? 0 : properties.hashCode());
273         result = prime * result + ((propertiesSource == null) ? 0 : propertiesSource.hashCode());
274         result = prime * result + sourceCodeReturnType;
275         result = prime * result + ((sourceCodeTypes == null) ? 0 : sourceCodeTypes.hashCode());
276         return result;
277     }
278 
279     @Override
280     public boolean equals(Object obj) {
281         if (this == obj) {
282             return true;
283         }
284         if (obj == null) {
285             return false;
286         }
287         if (getClass() != obj.getClass()) {
288             return false;
289         }
290         DBType other = (DBType) obj;
291         if (characterSet == null) {
292             if (other.characterSet != null) {
293                 return false;
294             }
295         } else if (!characterSet.equals(other.characterSet)) {
296             return false;
297         }
298         if (driverClass == null) {
299             if (other.driverClass != null) {
300                 return false;
301             }
302         } else if (!driverClass.equals(other.driverClass)) {
303             return false;
304         }
305         if (languages == null) {
306             if (other.languages != null) {
307                 return false;
308             }
309         } else if (!languages.equals(other.languages)) {
310             return false;
311         }
312         if (properties == null) {
313             if (other.properties != null) {
314                 return false;
315             }
316         } else if (!properties.equals(other.properties)) {
317             return false;
318         }
319         if (propertiesSource == null) {
320             if (other.propertiesSource != null) {
321                 return false;
322             }
323         } else if (!propertiesSource.equals(other.propertiesSource)) {
324             return false;
325         }
326         if (sourceCodeReturnType != other.sourceCodeReturnType) {
327             return false;
328         }
329         if (sourceCodeTypes == null) {
330             if (other.sourceCodeTypes != null) {
331                 return false;
332             }
333         } else if (!sourceCodeTypes.equals(other.sourceCodeTypes)) {
334             return false;
335         }
336         return true;
337     }
338 
339     /**
340      * @return the driverClass
341      */
342     public String getDriverClass() {
343         return driverClass;
344     }
345 
346     /**
347      * @return the characterSet
348      */
349     public String getCharacterSet() {
350         return characterSet;
351     }
352 
353     /**
354      * @return the sourceCodeTypes
355      */
356     public String getSourceCodeTypes() {
357         return sourceCodeTypes;
358     }
359 
360     /**
361      * @return the languages
362      */
363     public String getLanguages() {
364         return languages;
365     }
366 
367     /**
368      * @return the sourceCodeReturnType
369      */
370     public int getSourceCodeReturnType() {
371         return sourceCodeReturnType;
372     }
373 
374     /**
375      * @return the propertiesSource
376      */
377     public String getPropertiesSource() {
378         return propertiesSource;
379     }
380 
381     /**
382      * @param properties the properties to set
383      */
384     public void setProperties(Properties properties) {
385         this.properties = properties;
386 
387         // Driver Class
388         if (null != this.properties.getProperty("driver")) {
389             this.driverClass = this.properties.getProperty("driver");
390         }
391 
392         // Database CharacterSet
393         if (null != this.properties.getProperty("characterset")) {
394             this.characterSet = this.properties.getProperty("characterset");
395         }
396 
397         // String to get objects
398         if (null != this.properties.getProperty("sourcecodetypes")) {
399             this.sourceCodeTypes = this.properties.getProperty("sourcecodetypes");
400         }
401 
402         // Languages to process
403         if (null != this.properties.getProperty("languages")) {
404             this.languages = this.properties.getProperty("languages");
405         }
406 
407         // Return class for source code
408         if (null != this.properties.getProperty("returnType")) {
409             if (LOGGER.isLoggable(Level.FINEST)) {
410                 LOGGER.finest("returnType" + this.properties.getProperty("returnType"));
411             }
412             this.sourceCodeReturnType = Integer.parseInt(this.properties.getProperty("returnType"));
413         }
414 
415     }
416 
417     public String toString() {
418         return CLASS_NAME + "@" + propertiesSource;
419     }
420 }