![JAR search and dependency download from the Maven repository](/logo.png)
org.kawanfw.sql.api.client.RemoteDriver Maven / Gradle / Ivy
/*
* This file is part of AceQL.
* AceQL: Remote JDBC access over HTTP.
* Copyright (C) 2015, KawanSoft SAS
* (http://www.kawansoft.com). All rights reserved.
*
* AceQL is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* AceQL 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA
*
* Any modifications to this file must keep this entire header
* intact.
*/
package org.kawanfw.sql.api.client;
import java.io.File;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.PasswordAuthentication;
import java.net.Proxy;
import java.net.URL;
import java.net.Proxy.Type;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.DriverPropertyInfo;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.lang3.StringUtils;
import org.kawanfw.commons.api.client.SessionParameters;
import org.kawanfw.commons.json.SessionParametersGson;
import org.kawanfw.commons.util.ClientLogger;
import org.kawanfw.commons.util.FrameworkDebug;
import org.kawanfw.file.api.client.RemoteInputStream;
import org.kawanfw.file.api.client.RemoteOutputStream;
import org.kawanfw.file.api.client.RemoteSession;
import org.kawanfw.sql.util.JdbcUrlHeader;
/**
*
* The Driver class in order to access remote
* SQL databases through HTTP from Android or Java desktop programs.
*
* user & password are the only required properties.
*
* Properties:
*
* - user: username to connect to the remote database as.
* - password: password to use when authenticating.
* - proxyType: java.net.Proxy Type to use: HTTP or SOCKS. Defaults to HTTP.
* - proxyHostname: java.net.Proxy hostname to use.
* - proxyPort: java.net.Proxy Port to use.
* - proxyUsername: Proxy credential username.
* - proxyPassword: Proxy credential password.
* - maxLengthForString: int for the maximum authorized length for a
* string for upload or download Should be <= 2097152 (2Mb). Defaults to
* 2097152.
* - acceptAllSslCertificates: boolean to say if client sides allows
* HTTPS call with all SSL Certificates, including "invalid" or self-signed
* Certificates. Defaults to false.
* - encryptionPassword: the password to use to encrypt all request
* parameter names and values. Defaults to
null
.
* - htmlEncodingOn: boolean to say if the upload/download of Clob
* using character stream or ASCII stream must be html encoded. Defaults to
* true.
* - compression: boolean to say if the Driver is configured to
* contact the remote server using http compression. Defaults to
true
.
* - statelessMode: boolean to say if the Driver is configured to
* contact the remote server in stateless mode. Defaults to false.
* - uploadChunkLength: long for upload chunk length to be used by
* {@link RemoteOutputStream} and
* {@link RemoteSession#upload(File, String)}. Defaults to 10Mb. 0 means
* files are not chunked.
* - downloadChunkLength: long for download chunk length to be used by
* {@link RemoteInputStream} and
* {@link RemoteSession#download(String, File)}. Defaults to 10Mb. 0
* means files are not chunked.
* - joinResultSetMetaData: boolean to tell server to download ResultSet MetaData
* along with ResultSet. Defaults to {@code false}.
*
*
*
* @author Nicolas de Pomereu
* @since 1.0
*
*/
public class RemoteDriver implements java.sql.Driver {
/** The debug flag */
private static boolean DEBUG = FrameworkDebug.isSet(RemoteDriver.class);
/**
* Constructor.
*/
public RemoteDriver() {
}
static {
try {
DriverManager.registerDriver(new RemoteDriver());
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* Attempts to make a database connection to the given URL.
*
* The driver will return "null" if it realizes it is the wrong kind of
* driver to connect to the given URL. {@link #acceptsURL} will return null.
*
*
* The driver will throwSQLException
if it is the right driver
* to connect to the given URL but has trouble connecting to the database.
*
*
* The java.util.Properties
argument can be used to pass
* arbitrary string tag/value pairs as connection arguments. At least "user"
* and "password" properties should be included in the
* Properties
object.
*
* @param url
* the URL of the database to which to connect
* @param info
* a list of arbitrary string tag/value pairs as connection
* arguments. At least a "user" and "password" property should be
* included.
* @return a Connection
object that represents a connection to
* the URL
* @exception SQLException
* if a database access error occurs
*/
@Override
public Connection connect(String url, Properties info) throws SQLException {
if (url == null) {
throw new SQLException("url not set. Please provide an url.");
}
if (!acceptsURL(url)) {
return null;
}
Properties info2 = new Properties();
RemoteDriverUtil.copyProperties(info, info2);
// Properties may be passed in url
if (url.contains("?")) {
String query = StringUtils.substringAfter(url, "?");
Map mapProps = RemoteDriverUtil.getQueryMap(query);
Set set = mapProps.keySet();
for (String propName : set) {
info2.setProperty(propName, mapProps.get(propName));
}
url = StringUtils.substringBefore(url, "?");
}
String username = info2.getProperty("user");
String password = info2.getProperty("password");
if (username == null) {
throw new SQLException("user not set. Please provide a user.");
}
if (password == null) {
throw new SQLException(
"password not set. Please provide a password.");
}
// Add proxy lookup
String proxyType = info2.getProperty("proxyType");
String proxyHostname = info2.getProperty("proxyHostname");
String proxyPort = info2.getProperty("proxyPort");
String proxyUsername = info2.getProperty("proxyUsername");
String proxyPassword = info2.getProperty("proxyPassword");
String statelessMode = info2.getProperty("statelessMode");
String joinResultSetMetaData = info2
.getProperty("joinResultSetMetaData");
int port = -1;
Proxy proxy = null;
if (proxyHostname != null) {
try {
port = Integer.parseInt(proxyPort);
} catch (NumberFormatException e) {
throw new SQLException(
"Invalid proxy port. Port is not numeric: "
+ proxyPort);
}
if (proxyType == null) {
proxyType = "HTTP";
}
proxy = new Proxy(Type.valueOf(proxyType), new InetSocketAddress(
proxyHostname, port));
}
boolean statelessModeBoolean = Boolean.parseBoolean(statelessMode);
SessionParameters sessionParameters = getSessionParameters(info2);
debug(sessionParameters.toString());
// if (url.startsWith("jdbc:kawanfw://")) {
// url = url.replace("jdbc:kawanfw", "http");
// }
// If we have passed the "proxy" property, build back the
// instance from the property value
// 1) Treat the case the user did a property.put(proxy) instead of
// property.setProperty(proxy.toString())
if (proxy == null) {
Object objProxy = info2.get("proxy");
if (objProxy != null && objProxy instanceof Proxy) {
proxy = (Proxy) proxy;
}
// 2) Treat the case the user as correctly used
// property.setProperty(httpProxy.toString())
else {
String proxyStr = info2.getProperty("proxy");
debug("proxyStr:" + proxyStr);
if (proxyStr != null) {
proxy = RemoteDriverUtil.buildProxy(proxyStr);
}
}
}
// If we have passed the "sessionParameters" property, build back
// the
// instance from the property value
// 1) Treat the case the user did a property.put(sessionParameters)
// instead of property.setProperty(sessionParameters.toString())
Object objSessionParameters = info2.get("sessionParameters");
if (objSessionParameters != null
&& objSessionParameters instanceof SessionParameters) {
String jsonString = SessionParametersGson
.toJson((SessionParameters) (objSessionParameters));
if (jsonString != null) {
sessionParameters = SessionParametersGson
.fromJson(jsonString);
}
}
// 2) Treat the case the user as correctly used
// property.setProperty(sessionParameters.toString())
else {
String jsonString = info2.getProperty("sessionParameters");
if (jsonString != null) {
sessionParameters = SessionParametersGson
.fromJson(jsonString);
}
}
debug("url : " + url);
debug("Proxy : " + proxy);
debug("sessionParameters: " + sessionParameters);
boolean doJoinResultSetMetaData = false;
if (joinResultSetMetaData != null) {
doJoinResultSetMetaData = Boolean
.parseBoolean(joinResultSetMetaData);
debug("joinResultSetMetaData: " + doJoinResultSetMetaData);
}
PasswordAuthentication passwordAuthentication = null;
if (proxy != null && proxyUsername != null) {
passwordAuthentication = new PasswordAuthentication(proxyUsername,
proxyPassword.toCharArray());
}
Connection connection = new RemoteConnection(url, username,
password.toCharArray(), proxy, passwordAuthentication, sessionParameters,
statelessModeBoolean, doJoinResultSetMetaData);
return connection;
}
/**
* Returns the SessionParameters from the passed Properties
*
* @param info
* the properties
* @return SessionParameters from the passed Properties
*/
private SessionParameters getSessionParameters(Properties info)
throws SQLException {
SessionParameters sessionParameters = new SessionParameters();
String maxLengthForString = info.getProperty("maxLengthForString");
String encryptionPassword = info.getProperty("encryptionPassword");
String htmlEncoding = info.getProperty("htmlEncoding");
String acceptAllSslCertificates = info
.getProperty("acceptAllSslCertificates");
String compression = info.getProperty("compression");
String uploadChunkLength = info.getProperty("uploadChunkLength");
String downloadChunkLength = info.getProperty("downloadChunkLength");
String joinResultSetMetaData = info
.getProperty("joinResultSetMetaData");
// debug("");
// debug("maxLengthForString: " + maxLengthForString);
// debug("uploadBufferSize : " + uploadBufferSize);
// debug("downloadBufferSize: " + downloadBufferSize);
// debug("encryptionPassword: " + encryptionPassword);
debug("joinResultSetMetaData: " + joinResultSetMetaData);
if (maxLengthForString != null) {
int maxLengthForStringInteger;
try {
maxLengthForStringInteger = Integer
.parseInt(maxLengthForString);
sessionParameters
.setMaxLengthForString(maxLengthForStringInteger);
} catch (NumberFormatException e) {
throw new SQLException(
"Invalid maxLengthForString. Not numeric: "
+ maxLengthForString);
}
}
if (encryptionPassword != null) {
sessionParameters.setEncryptionPassword(encryptionPassword
.toCharArray());
}
if (htmlEncoding != null) {
sessionParameters.setHtmlEncodingOn(Boolean
.parseBoolean(htmlEncoding));
}
if (compression != null) {
sessionParameters.setCompressionOn(Boolean
.parseBoolean(compression));
}
if (acceptAllSslCertificates != null) {
sessionParameters.setAcceptAllSslCertificates(Boolean
.parseBoolean(acceptAllSslCertificates));
}
if (uploadChunkLength != null) {
long uploadChunkLengthLong;
try {
uploadChunkLengthLong = Long.parseLong(uploadChunkLength);
sessionParameters
.setUploadChunkLength(uploadChunkLengthLong);
} catch (NumberFormatException e) {
throw new SQLException(
"Invalid uploadChunkLength. Not numeric: "
+ uploadChunkLength);
}
}
if (downloadChunkLength != null) {
long downloadChunkLengthLong;
try {
downloadChunkLengthLong = Long.parseLong(downloadChunkLength);
sessionParameters
.setDownloadChunkLength(downloadChunkLengthLong);
} catch (NumberFormatException e) {
throw new SQLException(
"Invalid downloadChunkLength. Not numeric: "
+ downloadChunkLength);
}
}
return sessionParameters;
}
/**
* Retrieves whether the driver thinks that it can open a connection to the
* given URL. Typically drivers will return true
if they
* understand the subprotocol specified in the URL and false
if
* they do not.
*
* The AceQL driver requires an URL which is an http url in the format:
* {@code jdbc:aceql:http(s):///}
*
*
* Example:
* {@code jdbc:aceql:https://www.aceql.com:9443/ServerSqlManager}
*
* Note that the {@code "jdbc:aceql:"} prefix is optional and thus an URL
* such as {@code https://www.aceql.com:9443/ServerSqlManager} is accepted
*
* @param url
* the URL of the database
* @return true
if this driver understands the given URL;
* false
otherwise
* @exception SQLException
* if a database access error occurs
*/
@Override
public boolean acceptsURL(String url) throws SQLException {
if (url == null) {
throw new IllegalArgumentException("url is null!");
}
String urlHeader = JdbcUrlHeader.JDBC_URL_HEADER;
if (url.startsWith(urlHeader)) {
url = JdbcUrlHeader.getUrlHttpOnly(url);
return isHttpProtocolUrl(url);
}
// We still accept for now old raw format that starts directly with
// "http://"
System.err.println("WARNING: url should be in: \"" + urlHeader
+ "http://hostname:port/ServerSqlManager\" format.");
return isHttpProtocolUrl(url);
}
/**
* Return true if the passed string is an URL with HTTP(S) protocol
*
* @param url
* the URL to test
* @return true if the URL is HTTP
*/
private boolean isHttpProtocolUrl(String url) {
URL theUrl = null;
try {
theUrl = new URL(url);
} catch (MalformedURLException e) {
return false;
}
String protocol = theUrl.getProtocol();
if (protocol.equals("http") || protocol.equals("https")) {
return true;
} else {
return false;
}
}
/**
* Build a new DriverPropertyInfo with the passed property
*
* @param property
* the property to pass as name and value
* @param info
* the properties
* @return a DriverPropertyInfo with the propery name and value
*/
private DriverPropertyInfo getNewDriverPropertyInfo(String property,
Properties info) {
return new DriverPropertyInfo(property, info.getProperty(property));
}
/**
* Gets information about the possible properties for this driver.
*
* The getPropertyInfo
method is intended to allow a generic
* GUI tool to discover what properties it should prompt a human for in
* order to get enough information to connect to a database. Note that
* depending on the values the human has supplied so far, additional values
* may become necessary, so it may be necessary to iterate though several
* calls to the getPropertyInfo
method.
*
* @param url
* the URL of the database to which to connect
* @param info
* a proposed list of tag/value pairs that will be sent on
* connect open
* @return an array of DriverPropertyInfo
objects describing
* possible properties.
* @exception SQLException
* if a database access error occurs
*/
@Override
public DriverPropertyInfo[] getPropertyInfo(String url, Properties info)
throws SQLException {
List driverPropertyInfoList = new ArrayList();
if (info != null) {
info.remove("RemarksReporting");
}
DriverPropertyInfo driverPropertyInfo = null;
driverPropertyInfo = getNewDriverPropertyInfo("user", info);
driverPropertyInfo.description = "Username to connect to the remote database as";
driverPropertyInfo.required = true;
driverPropertyInfoList.add(driverPropertyInfo);
driverPropertyInfo = new DriverPropertyInfo("password", null);
driverPropertyInfo.description = "Password to use when authenticating";
driverPropertyInfo.required = true;
driverPropertyInfoList.add(driverPropertyInfo);
driverPropertyInfo = getNewDriverPropertyInfo("proxyType", info);
driverPropertyInfo.description = "Proxy Type to use: HTTP or SOCKS. Defaults to HTTP";
driverPropertyInfo.required = false;
driverPropertyInfoList.add(driverPropertyInfo);
driverPropertyInfo = getNewDriverPropertyInfo("proxyHostname", info);
driverPropertyInfo.description = "Proxy hostname to use";
driverPropertyInfo.required = false;
driverPropertyInfoList.add(driverPropertyInfo);
driverPropertyInfo = getNewDriverPropertyInfo("proxyPort", info);
driverPropertyInfo.description = "Proxy Port to use";
driverPropertyInfo.required = false;
driverPropertyInfoList.add(driverPropertyInfo);
driverPropertyInfo = getNewDriverPropertyInfo("proxyUsername", info);
driverPropertyInfo.description = "Proxy credential username";
driverPropertyInfo.required = false;
driverPropertyInfoList.add(driverPropertyInfo);
driverPropertyInfo = getNewDriverPropertyInfo("proxyPassword", info);
driverPropertyInfo.description = "Proxy credential password";
driverPropertyInfo.required = false;
driverPropertyInfoList.add(driverPropertyInfo);
driverPropertyInfo = getNewDriverPropertyInfo("maxLengthForString",
info);
driverPropertyInfo.description = "Int for the maximum authorized length for a string for upload or download Should be <= 2097152 (2Mb). Defaults to 2097152.";
driverPropertyInfo.required = false;
driverPropertyInfoList.add(driverPropertyInfo);
driverPropertyInfo = getNewDriverPropertyInfo("encryptionPassword",
info);
driverPropertyInfo.description = "The password to use to encrypt all request parameter names and values. Defaults to null (no encryption).";
driverPropertyInfo.required = false;
driverPropertyInfoList.add(driverPropertyInfo);
driverPropertyInfo = getNewDriverPropertyInfo("htmlEncoding", info);
driverPropertyInfo.description = "Boolean that says if the upload/download of Clob using character stream or ASCII stream must be html encoded. Defaults to true.";
driverPropertyInfo.required = false;
driverPropertyInfoList.add(driverPropertyInfo);
driverPropertyInfo = getNewDriverPropertyInfo("compression", info);
driverPropertyInfo.description = "Boolean to say if the Driver is configured to contact the remote server using http compression. Defaults to true. Defaults to true.";
driverPropertyInfo.required = false;
driverPropertyInfoList.add(driverPropertyInfo);
driverPropertyInfo = getNewDriverPropertyInfo(
"acceptAllSslCertificates", info);
driverPropertyInfo.description = "Boolean thats says if client sides allows HTTPS call with all SSL Certificates, including \"invalid\" or self-signed Certificates. Defaults to false.";
driverPropertyInfo.required = false;
driverPropertyInfoList.add(driverPropertyInfo);
driverPropertyInfo = getNewDriverPropertyInfo("statelessMode", info);
driverPropertyInfo.description = "Boolean that says if the Driver is configured to contact the remote server in stateless mode. Defaults to false.";
driverPropertyInfo.required = false;
driverPropertyInfoList.add(driverPropertyInfo);
driverPropertyInfo = getNewDriverPropertyInfo("uploadChunkLength", info);
driverPropertyInfo.description = "Long for upload chunk length to be used by FileSession.upload(File, String). Defaults to 10Mb. 0 means files are not chunked.";
driverPropertyInfo.required = false;
driverPropertyInfoList.add(driverPropertyInfo);
driverPropertyInfo = getNewDriverPropertyInfo("downloadChunkLength",
info);
driverPropertyInfo.description = "Long for download chunk length to be used by FileSession.download(String, File). Defaults to 10Mb. 0 means files are not chunked.";
driverPropertyInfo.required = false;
driverPropertyInfoList.add(driverPropertyInfo);
driverPropertyInfo = getNewDriverPropertyInfo("joinResultSetMetaData",
info);
driverPropertyInfo.description = "Boolean to say if ResultSet MetaData is to be downloaded along with ResultSet. Defaults to false.";
driverPropertyInfo.required = false;
driverPropertyInfoList.add(driverPropertyInfo);
DriverPropertyInfo[] arrayOfDriverPropertyInfo = driverPropertyInfoList
.toArray(new DriverPropertyInfo[driverPropertyInfoList.size()]);
return arrayOfDriverPropertyInfo;
}
/**
* Retrieves this driver's major version number.
*
* @return this driver's major version number
*/
@Override
public int getMajorVersion() {
return 3;
}
/**
* Gets the driver's minor version number.
*
* @return this driver's minor version number
*/
@Override
public int getMinorVersion() {
return 1;
}
/**
* Reports whether this driver is a genuine JDBC CompliantTM driver. A driver may only report
* true
here if it passes the JDBC compliance tests; otherwise
* it is required to return false
.
*
* JDBC compliance requires full support for the JDBC API and full support
* for SQL 92 Entry Level.
*
* Because the driver is not a genuine JDBC CompliantTM driver, method returns false
*
* @return false
*/
@Override
public boolean jdbcCompliant() {
return false;
}
/**
* debug tool
*/
private static void debug(String s) {
if (DEBUG) {
ClientLogger.getLogger().log(Level.WARNING, s);
}
}
// /////////////////////////////////////////////////////////
// JAVA 7 METHOD EMULATION //
// /////////////////////////////////////////////////////////
/**
* Return the parent Logger of all the Loggers used by this driver. This
* should be the Logger farthest from the root Logger that is still an
* ancestor of all of the Loggers used by this driver. Configuring this
* Logger will affect all of the log messages generated by the driver. In
* the worst case, this may be the root Logger.
*
* @return the parent Logger for this driver
* @throws SQLFeatureNotSupportedException
* if the driver does not use java.util.logging
.
* @since 1.7
*/
// @Override do not not override for Java 6 compatibility
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
throw new SQLFeatureNotSupportedException();
}
}