/** * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ package net.sourceforge.pmd.util.database; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; import java.net.URLDecoder; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; /** * Provide a single parameter to specify database objects to process. * * <p> * Wrap JDBC settings for use by PMD: optional parameters specify the source * code to be passed to PMD, or are inherited from the associated * {@link DBType}. * </p> * * <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>. * </p> * * <p> * The subprotocol and optional subname parts should be a valid DBType * JDBC(-ish) URL jdbc:oracle:thin:username/password@//192.168.100.21:1521/ORCL * JDBC(-ish) URL jdbc:thin:username/password@//192.168.100.21:1521/ORCL * </p> * * <p> * The query includes one or more of these: * </p> * <dl> * <dt>characterset</dt> * <dd>utf8</dd> * <dt>languages</dt> * <dd>comma-separated list of desired PMD languages</dd> * <dt>schemas</dt> * <dd>comma-separated list of database schemas</dd> * <dt>sourcecodetypes</dt> * <dd>comma-separated list of database source code types</dd> * <dt>sourcecodenames</dt> * <dd>comma-separated list of database source code names</dd> * </dl> * * @see URI * @author sturton */ public class DBURI { private static final String CLASS_NAME = DBURI.class.getCanonicalName(); private static final Logger LOGGER = Logger.getLogger(CLASS_NAME); /** * A JDBC URL with an associated query. * * Formats: jdbc:oracle:thin:[<user>/<password>]@//<host>[:<port>]/<service> * jdbc:oracle:oci:[<user>/<password>]@//<host>[:<port>]/<service> * * Example: jdbc:oracle:thin:@//myserver.com/customer_db * jdbc:oracle:oci:scott/tiger@//myserver.com:5521/customer_db */ private URI uri; private DBType dbType; private String url; /** * JDBC subprotocol */ private String subprotocol; /** * JDBC subname prefix */ private String subnamePrefix; /** * Parameters from URI */ private Map<String, String> parameters; // Schema List - defaults to connecting user private List<String> schemasList; // Object Type List - potentially inferred from the JDBC URL private List<String> sourceCodeTypesList; // source Code Type List private List<String> sourceCodeNamesList; // Language List - potentially inferred from the JDBC URL private List<String> languagesList; // Driver Class - potentially inferred from the JDBC URL private String driverClass; // Database CharacterSet private String characterSet; // String to get objects - defaults inferred from the JDBC URL private String sourceCodeTypes; // String to get source code - defaults inferred from the JDBC URL private String sourceCodeNames; // languages to process - defaults inferred from the JDBC URL private String languages; // Return class for source code, mapped fron java.sql.Types private int sourceCodeType; /** * Create DBURI from a string, combining a JDBC URL and query parameters. * * <p> * From the JDBC URL component, infer: * </p> * <ul> * <li>JDBC driver class</li> * <li>supported languages</li> * <li>default source code types</li> * <li>default schemas</li> * </ul> * * <p> * From the query component, define these values, overriding any defaults: * </p> * <ul> * <li>parsing language</li> * <li>source code types</li> * <li>schemas</li> * <li>source code</li> * </ul> * * @param string * URL string * @throws URISyntaxException */ public DBURI(String string) throws URISyntaxException { /* * A JDBC URL is an opaque URL and does not have a query. * * We pretend that it does, strip off the query, use the real JDBC URL * component to infer languages JDBC driver class supported languages * default source code types default schemas generate a faux HTTP URI * with the query, extract the query parameters */ uri = new URI(string); try { // Split the string between JDBC URL and the query String[] splitURI = string.split("\\?"); if (splitURI.length > 1) { url = splitURI[0]; } else { url = string; } LOGGER.log(Level.FINE, "Extracted URL={0}", url); // Explode URL into its separate components setFields(); // If the original URI string contained a query component, split it // into parameters if (splitURI.length > 1) { // Generate a fake HTTP URI to allow easy extraction of the // query parameters String chimeraString = "http://local?" + string.substring(url.length() + 1); LOGGER.log(Level.FINEST, "chimeraString={0}", chimeraString); URI chimeraURI = new URI(chimeraString); dump("chimeraURI", chimeraURI); parameters = getParameterMap(chimeraURI); LOGGER.log(Level.FINEST, "parameterMap=={0}", parameters); characterSet = parameters.get("characterset"); sourceCodeTypes = parameters.get("sourcecodetypes"); sourceCodeNames = parameters.get("sourcecodenames"); languages = parameters.get("languages"); // Populate the lists if (null != sourceCodeNames) { sourceCodeNamesList = Arrays.asList(sourceCodeNames.split(",")); } if (null != languages) { languagesList = Arrays.asList(languages.split(",")); } if (null != parameters.get("schemas")) { schemasList = Arrays.asList(parameters.get("schemas").split(",")); } if (null != sourceCodeTypes) { sourceCodeTypesList = Arrays.asList(sourceCodeTypes.split(",")); } } } catch (URISyntaxException ex) { URISyntaxException uriException = new URISyntaxException(string, "Problem generating DBURI."); uriException.initCause(ex); throw uriException; } catch (IOException e) { throw new RuntimeException(e); } } /** * Create a DBURI from standard individual {@link URI} components. * * <p> * From the JDBC URL components, infer: * </p> * <ul> * <li>JDBC driver class</li> * <li>supported languages</li> * <li>default source code types</li> * <li>default schemas</li> * </ul> * * <p> * From the query component, define these values, overriding any defaults: * </p> * <ul> * <li>parsing language</li> * <li>source code types</li> * <li>schemas</li> * <li>source code</li> * </ul> * * @param scheme * @param userInfo * @param host * @param port * @param path * @param query * @param fragment * @throws URISyntaxException */ public DBURI(String scheme, String userInfo, String host, int port, String path, String query, String fragment) throws URISyntaxException { uri = new URI(scheme, userInfo, host, port, path, query, fragment); } /** * Return extracted parameters from dburi. * * @param dburi * @return extracted parameters * @throws UnsupportedEncodingException */ private Map<String, String> getParameterMap(URI dburi) throws UnsupportedEncodingException { Map<String, String> map = new HashMap<>(); String query = dburi.getRawQuery(); LOGGER.log(Level.FINEST, "dburi,getQuery()={0}", query); if (null != query && !"".equals(query)) { String[] params = query.split("&"); for (String param : params) { String[] splits = param.split("="); String name = splits[0]; String value = null; if (splits.length > 1) { value = splits[1]; } map.put(name, (null == value) ? value : URLDecoder.decode(value, "UTF-8")); } } return map; } /** * Dump this URI to the log. * * @param description * @param dburi */ static void dump(String description, URI dburi) { String dumpString = String.format( "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", description, dburi.isOpaque(), dburi.isAbsolute(), dburi.getScheme(), dburi.getSchemeSpecificPart(), dburi.getHost(), dburi.getPort(), dburi.getPath(), dburi.getFragment(), dburi.getQuery()); LOGGER.fine(dumpString); String query = dburi.getQuery(); if (null != query && !"".equals(query)) { String[] params = query.split("&"); Map<String, String> map = new HashMap<>(); for (String param : params) { String[] splits = param.split("="); String name = splits[0]; String value = null; if (splits.length > 1) { value = splits[1]; } map.put(name, value); LOGGER.fine(String.format("name=%s,value=%s\n", name, value)); } } // return map; } public URI getUri() { return uri; } public void setUri(URI uri) { this.uri = uri; } public DBType getDbType() { return dbType; } public void setDbType(DBType dbType) { this.dbType = dbType; } public List<String> getSchemasList() { return schemasList; } public void setSchemasList(List<String> schemasList) { this.schemasList = schemasList; } public List<String> getSourceCodeTypesList() { return sourceCodeTypesList; } public void setSourceCodeTypesList(List<String> sourceCodeTypesList) { this.sourceCodeTypesList = sourceCodeTypesList; } public List<String> getSourceCodeNamesList() { return sourceCodeNamesList; } public void setSourceCodeNamesList(List<String> sourceCodeNamesList) { this.sourceCodeNamesList = sourceCodeNamesList; } public List<String> getLanguagesList() { return languagesList; } public void setLanguagesList(List<String> languagesList) { this.languagesList = languagesList; } public String getDriverClass() { return driverClass; } public void setDriverClass(String driverClass) { this.driverClass = driverClass; } public String getCharacterSet() { return characterSet; } public void setCharacterSet(String characterSet) { this.characterSet = characterSet; } public int getSourceCodeType() { return sourceCodeType; } public void setSourceCodeType(int sourceCodeType) { this.sourceCodeType = sourceCodeType; } public String getSubprotocol() { return subprotocol; } public void setSubprotocol(String subprotocol) { this.subprotocol = subprotocol; } public String getSubnamePrefix() { return subnamePrefix; } public void setSubnamePrefix(String subnamePrefix) { this.subnamePrefix = subnamePrefix; } public Map<String, String> getParameters() { return parameters; } public void setParameters(Map<String, String> parameters) { this.parameters = parameters; } /** * @return the url */ public String getURL() { return url; } /** * @param jdbcURL * the url to set */ public void setURL(String jdbcURL) { this.url = jdbcURL; } /** * Populate the URI and query collections from the original string * * @throws URISyntaxException * @throws IOException */ private void setFields() throws URISyntaxException, IOException { if (url.startsWith("jdbc:")) { // java.net.URI is intended for "normal" URLs URI jdbcURI = new URI(getURL().substring(5)); LOGGER.log(Level.FINE, "setFields - substr(jdbcURL,5):{0}", getURL().substring(5)); dump("substr(jdbcURL,5)", jdbcURI); // jdbc:subprotocol:subname String[] uriParts = url.split(":"); for (String part : uriParts) { LOGGER.log(Level.FINEST, "JDBCpart={0}", part); } /* * Expect jdbc : subprotocol [ : subname ] : connection details * uriParts.length < 3 Error uriParts.length = 3 Driver information * may be inferred from part[1] - the subprotocol uriParts.length >= * 4 Driver information may be inferred from part[2]- the first part * of the subname */ if (3 == uriParts.length) { subprotocol = uriParts[1]; } else if (4 <= uriParts.length) { subprotocol = uriParts[1]; subnamePrefix = uriParts[2]; } else { throw new URISyntaxException(getURL(), "Could not understand JDBC URL", 1); } LOGGER.log(Level.FINE, "subprotocol={0}'' subnamePrefix={1}", new Object[] { subprotocol, subnamePrefix }); // Set values from DBType defaults this.dbType = new DBType(subprotocol, subnamePrefix); LOGGER.log(Level.FINER, "DBType properties found at {0} with {1} properties.", new Object[] { dbType.getPropertiesSource(), dbType.getProperties().size() }); LOGGER.log(Level.FINEST, "DBType properties are:- {0}", dbType.getProperties()); if (null != dbType.getDriverClass()) { this.driverClass = dbType.getDriverClass(); } if (null != dbType.getCharacterSet()) { this.characterSet = dbType.getCharacterSet(); } if (null != dbType.getLanguages()) { this.languages = dbType.getLanguages(); } if (null != dbType.getSourceCodeTypes()) { sourceCodeTypes = dbType.getSourceCodeTypes(); } LOGGER.finer("DBType other properties follow ..."); if (null != dbType.getProperties().getProperty("schemas")) { schemasList = Arrays.asList(dbType.getProperties().getProperty("schemas").split(",")); } sourceCodeNames = dbType.getProperties().getProperty("sourcecodenames"); String returnType = dbType.getProperties().getProperty("returnType"); if (null != returnType) { sourceCodeType = Integer.parseInt(returnType); } LOGGER.finer("DBType populating lists "); // Populate the lists if (null != sourceCodeNames) { sourceCodeNamesList = Arrays.asList(sourceCodeNames.split(",")); } if (null != languages) { languagesList = Arrays.asList(languages.split(",")); } if (null != sourceCodeTypes) { sourceCodeTypesList = Arrays.asList(sourceCodeTypes.split(",")); } LOGGER.finer("DBType lists generated"); } } @Override public String toString() { return uri.toASCIIString(); } }