/* This file is part of VoltDB. * Copyright (C) 2008-2017 VoltDB Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with VoltDB. If not, see <http://www.gnu.org/licenses/>. */ package org.voltdb.jdbc; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.net.URL; import java.sql.Connection; import java.sql.DriverManager; import java.sql.DriverPropertyInfo; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.logging.Logger; import java.util.regex.Pattern; import org.voltcore.utils.ssl.SSLConfiguration; public class Driver implements java.sql.Driver { public static final String JDBC_PROP_FILE_ENV = "VOLTDB_JDBC_PROPERTIES"; public static final String JDBC_PROP_FILE_PROP = "voltdb.jdbcproperties"; public static final String DEFAULT_PROP_FILENAME = "voltdb.properties"; //Driver URL prefix. private static final String URL_PREFIX = "jdbc:voltdb:"; static final String SSL_PROP= "ssl"; static final String TRUSTSTORE_CONFIG_PROP = "truststore"; static final String TRUSTSTORE_PASSWORD_PROP = "truststorepassword"; // Static so it's unit-testable, yes, lazy me static String[] getServersFromURL(String url) { // get everything between the prefix and the ? String prefix = URL_PREFIX + "//"; int end = url.length(); if (url.indexOf("?") > 0) { end = url.indexOf("?"); } String servstring = url.substring(prefix.length(), end); return servstring.split(","); } static Map<String, String> getPropsFromURL(String url) { Map<String, String> results = new HashMap<String, String>(); if (url.indexOf("?") > 0) { String propstring = url.substring(url.indexOf("?") + 1); String[] props = propstring.split("&"); for (String prop : props) { if (prop.indexOf("=") > 0) { String[] comps = prop.split("="); results.put(comps[0], comps[1]); } } } return results; } private static final int MAJOR_VERSION = 1; private static final int MINOR_VERSION = 0; static { try { DriverManager.registerDriver(new Driver()); } catch (Exception e) {} } public Driver() throws SQLException { // Required for Class.forName().newInstance() } @Override public Connection connect(String url, Properties props) throws SQLException { if (acceptsURL(url)) { try { // Properties favored order: // 1) property file specified by env variable // 2) property file specified by system property // 3) property file with default name in same path as driver jar // 4) Properties specified in the URL // 5) Properties specified to getConnection() arg // Properties fileprops = tryToFindPropsFile(); // Copy the provided properties so we don't muck with // the object the caller gave us. Properties info = (Properties) props.clone(); String prefix = URL_PREFIX + "//"; if (!url.startsWith(prefix)) { throw SQLError.get(SQLError.ILLEGAL_ARGUMENT); } // get the server strings String[] servers = Driver.getServersFromURL(url); // get the props from the URL Map<String, String> urlprops = Driver.getPropsFromURL(url); for (Entry<String, String> e : urlprops.entrySet()) { // Favor the URL over the provided props info.setProperty(e.getKey(), e.getValue()); } // Favor the file-specified properties over the other props for (Enumeration<?> e = fileprops.propertyNames(); e.hasMoreElements();) { String key = (String) e.nextElement(); info.setProperty(key, fileprops.getProperty(key)); } String user = ""; String password = ""; boolean heavyweight = false; int maxoutstandingtxns = 0; boolean reconnectOnConnectionLoss = false; boolean enableSSL = false; String truststorePath = null; String truststorePassword = null; for (Enumeration<?> e = info.propertyNames(); e.hasMoreElements();) { String key = (String) e.nextElement(); String value = info.getProperty(key); if (key.toLowerCase().equals("user")) user = value; else if (key.toLowerCase().equals("password")) password = value; else if (key.toLowerCase().equals("heavyweight")) heavyweight = (value.toLowerCase().equals("true") || value.toLowerCase().equals("yes") || value.toLowerCase().equals("1")); else if (key.toLowerCase().equals("maxoutstandingtxns")) maxoutstandingtxns = Integer.parseInt(value); else if ("autoreconnect".equals(key)) { reconnectOnConnectionLoss = ("true".equalsIgnoreCase(value) || "yes".equalsIgnoreCase(value) || "1".equals(value)); } else if (key.toLowerCase().equals(SSL_PROP)) { enableSSL = value.toLowerCase().equals("true"); } else if (key.toLowerCase().equals(TRUSTSTORE_CONFIG_PROP)) { if ((value != null) && value.trim().length() > 0) { truststorePath = value.trim(); } } else if (key.toLowerCase().equals(TRUSTSTORE_PASSWORD_PROP)) { truststorePassword = value; } // else - unknown; ignore } SSLConfiguration.SslConfig sslConfig = null; if (enableSSL) { sslConfig = new SSLConfiguration.SslConfig(null, null, truststorePath, truststorePassword); } // Return JDBC connection wrapper for the client return new JDBC4Connection(JDBC4ClientConnectionPool.get(servers, user, password, heavyweight, maxoutstandingtxns, reconnectOnConnectionLoss, sslConfig), info); } catch (Exception x) { throw SQLError.get(x, SQLError.CONNECTION_UNSUCCESSFUL); } } return null; } @Override public boolean acceptsURL(String url) throws SQLException { return Pattern.compile("^jdbc:voltdb://.+", Pattern.CASE_INSENSITIVE).matcher(url).matches(); } @Override public int getMajorVersion() { return MAJOR_VERSION; } @Override public int getMinorVersion() { return MINOR_VERSION; } @Override public DriverPropertyInfo[] getPropertyInfo(String url, Properties loginProps) throws SQLException { return new DriverPropertyInfo[0]; } @Override public boolean jdbcCompliant() { return false; } @Override public Logger getParentLogger() throws SQLFeatureNotSupportedException { throw new SQLFeatureNotSupportedException(); } private Properties tryToFindPropsFile() { Properties fileprops = new Properties(); String filename = null; // Check the env first filename = System.getenv(Driver.JDBC_PROP_FILE_ENV); if (filename == null) { filename = System.getProperty(Driver.JDBC_PROP_FILE_PROP); } if (filename == null) { // see if we can find a file in the default location URL pathToJar = this.getClass().getProtectionDomain() .getCodeSource().getLocation(); String tmp = null; try { tmp = new File(pathToJar.toURI()).getParent() + File.separator + DEFAULT_PROP_FILENAME; } catch (Exception e) { tmp = null; } filename = tmp; } if (filename != null) { File propfile = new File(filename); if (propfile.exists() && propfile.isFile()) { FileInputStream in = null; try { in = new FileInputStream(propfile); fileprops.load(in); } catch (FileNotFoundException fnfe) {} catch (IOException ioe) {} finally { if (in != null) { try { in.close(); } catch (IOException e) {} } } } } return fileprops; } }