/** * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ package net.sourceforge.pmd.util.database; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.Properties; import java.util.PropertyResourceBundle; import java.util.ResourceBundle; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.commons.io.IOUtils; /** * Encapsulate the settings needed to access database source code. * * * @author sturton */ public class DBType { private static final Logger LOGGER = Logger.getLogger(DBType.class.getPackage().getName()); private static final String INTERNAL_SETTINGS = "[Internal Settings]"; /** * The names of the properties */ public enum Property { USER("user", "Name of the connecting database user"), PASSWORD("password", "The connecting database user's password"), DRIVER("driver", "JDBC driver classname"), CHARACTERSET("characterset", "Reader character set"), LANGUAGES("languages", "Comma-separated list of PMD-supported languages"), SCHEMAS("schemas", "SchemaSpy compatible regular expression for schemas to be processed"), SOURCE_TYPES("sourcecodetypes", "Comma-separated list of supported source types"), SOURCE_NAMES("sourcecodenames", "Default comma-separated list of source code names to validate"), GET_SOURCE_CODE_STATEMENT( "getSourceCodeStatement", "SQL92 or Oracle embedded SQL statement to retrieve code source from the database catalogue"), RETURN_TYPE("returnType", "int equivalent of java.sql.Types return type of getSourceCodeStatement"); private String name; private String description; Property(String name, String description) { this.name = name; this.description = description; } public String getPropertyName() { return name; } public String getDescription() { return description; } } /** * Where the properties were taken from */ private String propertiesSource; /** * Parameters from Properties */ private Properties properties; // Driver Class private String driverClass; // Database CharacterSet private String characterSet; // String to get objects private String sourceCodeTypes; // Languages to process private String languages; // Return class for source code private int sourceCodeReturnType; /** * * @param dbType */ public DBType(String dbType) throws Exception { properties = loadDBProperties(dbType); } /** * Load the most specific dbType for the protocol * * @param subProtocol * @param subnamePrefix * @throws IOException */ public DBType(String subProtocol, String subnamePrefix) throws IOException { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("subProtocol=" + subProtocol + ", subnamePrefix=" + subnamePrefix); } if (null == subProtocol && null == subnamePrefix) { throw new RuntimeException("subProtocol and subnamePrefix cannot both be null"); } else { properties = null; // Attempt subnamePrefix before subProtocol if (subnamePrefix != null) { properties = loadDBProperties(subnamePrefix); } if (properties == null && subProtocol != null) { properties = loadDBProperties(subProtocol); } if (subnamePrefix != null && properties != null) { LOGGER.log(Level.FINE, "DBType found using subnamePrefix={0}", subnamePrefix); } else if (subProtocol != null && properties != null) { LOGGER.log(Level.FINE, "DBType found using subProtocol={0}", subProtocol); } else { throw new RuntimeException( String.format("Could not locate DBType properties using subProtocol=%s and subnamePrefix=%s", subProtocol, subnamePrefix)); } } } public Properties getProperties() { return properties; } /** * Load properties from one or more files or resources. * * <p> * This method recursively finds property files or JAR resources matching * {@matchstring}. * </p> * . * <p> * The method is intended to be able to use , so any * * @param matchString * @return "current" set of properties (from one or more resources/property * files) */ private Properties loadDBProperties(String matchString) throws IOException { LOGGER.entering(DBType.class.getCanonicalName(), matchString); // Locale locale = Control.g; ResourceBundle resourceBundle = null; InputStream stream = null; if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest("class_path+" + System.getProperty("java.class.path")); } /* * Attempt to match properties files in this order:- File path with * properties suffix File path without properties suffix Resource * without class prefix Resource with class prefix */ try { File propertiesFile = new File(matchString); if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest("Attempting File no file suffix: " + matchString); } stream = new FileInputStream(propertiesFile); resourceBundle = new PropertyResourceBundle(stream); propertiesSource = propertiesFile.getAbsolutePath(); LOGGER.finest("FileSystemWithoutExtension"); } catch (FileNotFoundException notFoundOnFilesystemWithoutExtension) { if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest("notFoundOnFilesystemWithoutExtension"); LOGGER.finest("Attempting File with added file suffix: " + matchString + ".properties"); } try { File propertiesFile = new File(matchString + ".properties"); stream = new FileInputStream(propertiesFile); resourceBundle = new PropertyResourceBundle(stream); propertiesSource = propertiesFile.getAbsolutePath(); LOGGER.finest("FileSystemWithExtension"); } catch (FileNotFoundException notFoundOnFilesystemWithExtensionTackedOn) { if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest("Attempting JARWithoutClassPrefix: " + matchString); } try { resourceBundle = ResourceBundle.getBundle(matchString); propertiesSource = "[" + INTERNAL_SETTINGS + "]" + File.separator + matchString + ".properties"; LOGGER.finest("InJarWithoutPath"); } catch (Exception notInJarWithoutPath) { if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest("Attempting JARWithClass prefix: " + DBType.class.getCanonicalName() + "." + matchString); } try { resourceBundle = ResourceBundle.getBundle(DBType.class.getPackage().getName() + "." + matchString); propertiesSource = "[" + INTERNAL_SETTINGS + "]" + File.separator + matchString + ".properties"; LOGGER.finest("found InJarWithPath"); } catch (Exception notInJarWithPath) { notInJarWithPath.printStackTrace(); notFoundOnFilesystemWithExtensionTackedOn.printStackTrace(); throw new RuntimeException(" Could not locate DBTYpe settings : " + matchString, notInJarWithPath); } } } } finally { IOUtils.closeQuietly(stream); } // Properties in this matched resource Properties matchedProperties = getResourceBundleAsProperties(resourceBundle); resourceBundle = null; String saveLoadedFrom = getPropertiesSource(); /* * If the matched properties contain the "extends" key, use the value as * a matchstring, to recursively set the properties before overwriting * any previous properties with the matched properties. */ String extendedPropertyFile = (String) matchedProperties.remove("extends"); if (null != extendedPropertyFile && !"".equals(extendedPropertyFile.trim())) { Properties extendedProperties = loadDBProperties(extendedPropertyFile.trim()); // Overwrite extended properties with properties in the matched // resource extendedProperties.putAll(matchedProperties); matchedProperties = extendedProperties; } /* * Record the location of the original matched resource/property file, * and the current set of properties secured. */ propertiesSource = saveLoadedFrom; setProperties(matchedProperties); return matchedProperties; } /** * Options that are specific to a type of database. E.g. things like * <code>host</code>, <code>port</code> or <code>db</code>, but <b>don't</b> * have a setter in this class. * * @param dbSpecificOptions */ /** * Convert <code>resourceBundle</code> to usable {@link Properties}. * * @param resourceBundle * ResourceBundle * @return Properties */ public static Properties getResourceBundleAsProperties(ResourceBundle resourceBundle) { Properties properties = new Properties(); for (String key : resourceBundle.keySet()) { properties.put(key, resourceBundle.getObject(key)); } return properties; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((characterSet == null) ? 0 : characterSet.hashCode()); result = prime * result + ((driverClass == null) ? 0 : driverClass.hashCode()); result = prime * result + ((languages == null) ? 0 : languages.hashCode()); result = prime * result + ((properties == null) ? 0 : properties.hashCode()); result = prime * result + ((propertiesSource == null) ? 0 : propertiesSource.hashCode()); result = prime * result + sourceCodeReturnType; result = prime * result + ((sourceCodeTypes == null) ? 0 : sourceCodeTypes.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } DBType other = (DBType) obj; if (characterSet == null) { if (other.characterSet != null) { return false; } } else if (!characterSet.equals(other.characterSet)) { return false; } if (driverClass == null) { if (other.driverClass != null) { return false; } } else if (!driverClass.equals(other.driverClass)) { return false; } if (languages == null) { if (other.languages != null) { return false; } } else if (!languages.equals(other.languages)) { return false; } if (properties == null) { if (other.properties != null) { return false; } } else if (!properties.equals(other.properties)) { return false; } if (propertiesSource == null) { if (other.propertiesSource != null) { return false; } } else if (!propertiesSource.equals(other.propertiesSource)) { return false; } if (sourceCodeReturnType != other.sourceCodeReturnType) { return false; } if (sourceCodeTypes == null) { if (other.sourceCodeTypes != null) { return false; } } else if (!sourceCodeTypes.equals(other.sourceCodeTypes)) { return false; } return true; } /** * @return the driverClass */ public String getDriverClass() { return driverClass; } /** * @return the characterSet */ public String getCharacterSet() { return characterSet; } /** * @return the sourceCodeTypes */ public String getSourceCodeTypes() { return sourceCodeTypes; } /** * @return the languages */ public String getLanguages() { return languages; } /** * @return the sourceCodeReturnType */ public int getSourceCodeReturnType() { return sourceCodeReturnType; } /** * @return the propertiesSource */ public String getPropertiesSource() { return propertiesSource; } /** * @param properties * the properties to set */ public void setProperties(Properties properties) { this.properties = properties; // Driver Class if (null != this.properties.getProperty("driver")) { this.driverClass = this.properties.getProperty("driver"); } // Database CharacterSet if (null != this.properties.getProperty("characterset")) { this.characterSet = this.properties.getProperty("characterset"); } // String to get objects if (null != this.properties.getProperty("sourcecodetypes")) { this.sourceCodeTypes = this.properties.getProperty("sourcecodetypes"); } // Languages to process if (null != this.properties.getProperty("languages")) { this.languages = this.properties.getProperty("languages"); } // Return class for source code if (null != this.properties.getProperty("returnType")) { if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest("returnType" + this.properties.getProperty("returnType")); } this.sourceCodeReturnType = Integer.parseInt(this.properties.getProperty("returnType")); } } @Override public String toString() { return DBType.class.getCanonicalName() + "@" + propertiesSource; } }