/**
* 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;
}
}