src-main.org.awakefw.sql.api.client.AwakeDriver Maven / Gradle / Ivy
Show all versions of awake-sql Show documentation
/*
* This file is part of Awake SQL.
* Awake SQL: Remote JDBC access over HTTP.
* Copyright (C) 2013, KawanSoft SAS
* (http://www.kawansoft.com). All rights reserved.
*
* Awake SQL is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* Awake SQL 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 General Public License
* along with this program; if not, see .
*
* If you develop commercial activities using Awake SQL, you must:
* a) disclose and distribute all source code of your own product,
* b) license your own product under the GNU General Public License.
*
* You can be released from the requirements of the license by
* purchasing a commercial license. Buying such a license will allow you
* to ship Awake SQL with your closed source products without disclosing
* the source code.
*
* For more information, please contact KawanSoft SAS at this
* address: [email protected]
*
* Any modifications to this file must keep this entire header
* intact.
*/
package org.awakefw.sql.api.client;
import java.lang.reflect.Constructor;
import java.net.MalformedURLException;
import java.net.URL;
import java.sql.Connection;
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.Properties;
import java.util.Set;
import java.util.logging.Logger;
import org.apache.commons.lang3.StringUtils;
import org.awakefw.commons.api.client.HttpProtocolParameters;
import org.awakefw.commons.api.client.HttpProxy;
import org.awakefw.file.api.util.AwakeDebug;
import org.awakefw.file.util.AwakeClientLogger;
/**
*
* The Awake SQL Driver class in order to access remote SQL databases through HTTP from
* Android or Java desktop programs.
*
* Note that The preferred way to connect to a
* remote database is {@link AwakeConnection}, as described in
* the Tutorial.
*
* The Driver implementation is provided because it's necessary for usage with
* third party programs as SQL client front end (SQuirreL, Netbeans IDE, etc.)
*
* user & password are the only required properties.
*
* Properties:
*
* - user: Username to connect to the remote database as.
* - password: Password to use when authenticating.
* - httpProxyAddress: Http proxy address to use.
* - httpProxyPort: Http proxy Port to use.
* - httpProxyUsername: Http proxy credential username.
* - httpProxyPassword: Http proxy credential password.
* - httpProxyWorkstation: For NTLM proxy: the workstation.
* - httpProxyDomain: For NTLM proxy: the domain.
* - maxLengthForString: Int for the maximum authorized length for a
* string for upload or download Should be <= 2097152 (2Mb). Defaults to
* 2097152.
* - uploadBufferSize: Int for the buffer size when uploading a
* Blob/Clob. Defaults to 20480 (20 Kb).
* - downloadBufferSize: Int for the buffer size when downloading a
* Blob/Clob. Defaults to 20480 (20 Kb).
* - acceptAllSslCertificates: Boolean to say if client sides allows HTTPS
* call with all SSL Certificates, including "invalid" or self-signed Certificates.
* - 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.
*
- statelessMode: Boolean to say if the Driver is configured
* to contact the remote server in stateless mode.
*
*
*
* You may also pass any Jakarta HttpClient parameter as with
* {@link HttpProtocolParameters}:
*
*
* - The name of the HttpClient parameter must be in the format:
*
'http-client-<name>'
.
* - The value of HttpClient parameter:
*
* - The
String
value for String
parameters
* - The
String
:
* '[value.getClass().ClassName(), value.toString()]'
for
* parameters of other Java type.
*
*
*
*
* Examples:
*
*
* - To pass a
'http.socket.timeout'
of 10 seconds, pass the
* following property:
*
* - property name:
'http-client-http.socket.timeout'
.
* - property value:
'[java.lang.Integer, 10000]'
.
*
*
* - To pass a
'http.useragent'
of
* 'www.acme.org User-Agent'
, pass the following property:
*
* - property name:
'http-client-http.useragent'
.
* - property value:
'www.acme.org User-Agent'
.
*
*
* @author Nicolas de Pomereu
* @since 1.0
*
*/
public class AwakeDriver implements java.sql.Driver {
/** The debug flag */
private static boolean DEBUG = AwakeDebug.isSet(AwakeDriver.class);
/**
* Constructor.
*/
public AwakeDriver() {
}
/**
* Attempts to make a database connection to the given URL.
*
* The Awake 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 Awake 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;
}
String username = info.getProperty("user");
String password = info.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 httpProxyAddress = info.getProperty("httpProxyAddress");
String httpProxyPort = info.getProperty("httpProxyPort");
String httpProxyUsername = info.getProperty("httpProxyUsername");
String httpProxyPassword = info.getProperty("httpProxyPassword");
String httpProxyWorkstation = info.getProperty("httpProxyWorkstation");
String httpProxyDomain = info.getProperty("httpProxyDomain");
String statelessMode = info.getProperty("statelessMode");
int port = -1;
HttpProxy httpProxy = null;
if (httpProxyAddress != null) {
try {
port = Integer.parseInt(httpProxyPort);
} catch (NumberFormatException e) {
throw new SQLException(
"Invalid HttpProxy port. Port is not numeric: "
+ httpProxyPort);
}
if (httpProxyWorkstation != null) {
httpProxy = new HttpProxy(httpProxyAddress, port,
httpProxyUsername, httpProxyPassword,
httpProxyWorkstation, httpProxyDomain);
} else {
httpProxy = new HttpProxy(httpProxyAddress, port,
httpProxyUsername, httpProxyPassword);
}
}
boolean statelessModeBoolean = Boolean
.parseBoolean(statelessMode);
HttpProtocolParameters httpProtocolParameters = getHttpProtocolParameters(info);
debug(httpProtocolParameters.toString());
Connection connection = new AwakeConnection(url, username,
password.toCharArray(), httpProxy, httpProtocolParameters, statelessModeBoolean);
return connection;
}
/**
* Returns the HttpProtocolParameters from the passed Properties
*
* @param info
* the properties
* @return HttpProtocolParameters from the passed Properties
*/
private HttpProtocolParameters getHttpProtocolParameters(Properties info)
throws SQLException {
HttpProtocolParameters httpProtocolParameters = new HttpProtocolParameters();
String maxLengthForString = info.getProperty("maxLengthForString");
String uploadBufferSize = info.getProperty("uploadBufferSize");
String downloadBufferSize = info.getProperty("downloadBufferSize");
String encryptionPassword = info.getProperty("encryptionPassword");
String htmlEncoding = info.getProperty("htmlEncoding");
String acceptAllSslCertificates = info.getProperty("acceptAllSslCertificates");
// debug("");
// debug("maxLengthForString: " + maxLengthForString);
// debug("uploadBufferSize : " + uploadBufferSize);
// debug("downloadBufferSize: " + downloadBufferSize);
// debug("encryptionPassword: " + encryptionPassword);
if (maxLengthForString != null) {
int maxLengthForStringInteger;
try {
maxLengthForStringInteger = Integer
.parseInt(maxLengthForString);
httpProtocolParameters
.setMaxLengthForString(maxLengthForStringInteger);
} catch (NumberFormatException e) {
throw new SQLException(
"Invalid maxLengthForString. Not numeric: "
+ maxLengthForString);
}
}
if (uploadBufferSize != null) {
int uploadBufferSizeInteger;
try {
uploadBufferSizeInteger = Integer.parseInt(uploadBufferSize);
httpProtocolParameters
.setUploadBufferSize(uploadBufferSizeInteger);
} catch (NumberFormatException e) {
throw new SQLException(
"Invalid uploadBufferSize. Not numeric: "
+ uploadBufferSize);
}
}
if (downloadBufferSize != null) {
int downloadBufferSizeInteger;
try {
downloadBufferSizeInteger = Integer
.parseInt(downloadBufferSize);
httpProtocolParameters
.setDownloadBufferSize(downloadBufferSizeInteger);
} catch (NumberFormatException e) {
throw new SQLException(
"Invalid downloadBufferSize. Not numeric: "
+ downloadBufferSize);
}
}
if (encryptionPassword != null) {
httpProtocolParameters.setEncryptionPassword(encryptionPassword
.toCharArray());
}
if (htmlEncoding != null) {
httpProtocolParameters.setHtmlEncodingOn(Boolean
.parseBoolean(htmlEncoding));
}
if (acceptAllSslCertificates != null) {
httpProtocolParameters.setAcceptAllSslCertificates(Boolean
.parseBoolean(acceptAllSslCertificates));
}
// Set now the HttpClient parameters
setHttpClientParameters(info, httpProtocolParameters);
return httpProtocolParameters;
}
/**
* Sets the HttpClient parameters on httpProtocolParameters
*
* @param info
* the driver info
* @param httpProtocolParameters
* the HttpProtocolParameters to set
* @throws SQLException
* if any Exception occurs
*/
private void setHttpClientParameters(Properties info,
HttpProtocolParameters httpProtocolParameters) throws SQLException {
Map httpClientMap = new HashMap();
// First step: build the map httpClientParams
for (Enumeration> e = info.propertyNames(); e.hasMoreElements();) {
String prop = (String) e.nextElement();
if (prop.startsWith("http-client-")) {
String httpClientParam = StringUtils.substringAfter(prop,
"http-client-");
String httpClientValue = info.getProperty(prop);
httpClientMap.put(httpClientParam, httpClientValue);
}
}
// Get the HttpClient epured properties
Set keys = httpClientMap.keySet();
for (String key : keys) {
String value = httpClientMap.get(key);
if (value == null) {
throw new SQLException(
"HttpClient value is null for HttpClient parameter: http-client-"
+ key);
}
if (value.startsWith("[") && value.endsWith("]")) {
// We must analyse the object type from the string
// [value.getClass().getName(), value.toString()]
setObjectHttpParameter(httpProtocolParameters, key, value);
} else {
httpProtocolParameters.setHttpClientParameter(key, value);
}
}
}
/**
* Sets the HttpClient parameter value as an object value
*
* @param httpProtocolParameters
* the HttpProtocolParameters to set
* @param key
* the HttpClient parameter name
* @param value
* the HttpClient parameter value
* @throws SQLException
* if any Exception occurs
*/
private void setObjectHttpParameter(
HttpProtocolParameters httpProtocolParameters, String key,
String value) throws SQLException {
// We must analyse the object type from the string
// [value.getClass().getName(), value.toString()]
if (!value.contains(",")) {
throw new IllegalArgumentException(
"Invalid format for HttpClient parameter http-client-"
+ key + ". " + "Value does not contain a comma: "
+ value);
}
String originalValue = value;
value = StringUtils.substringAfter(value, "[");
value = StringUtils.substringBeforeLast(value, "]");
String className = StringUtils.substringBefore(value, ",");
String stringValue = StringUtils.substringAfter(value, ",");
className = className.trim();
stringValue = stringValue.trim();
Class> c = null;
try {
c = Class.forName(className);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException(
"Invalid format for HttpClient parameter http-client-"
+ key
+ ". "
+ "Value does not contain a valid Java class name: "
+ originalValue);
}
try {
Constructor> ctor = c.getDeclaredConstructor(String.class);
ctor.setAccessible(true);
Object objValue = ctor.newInstance(stringValue);
httpProtocolParameters.setHttpClientParameter(key, objValue);
} catch (Exception e) {
throw new IllegalArgumentException(
"Invalid format for HttpClient parameter http-client-"
+ key
+ ". "
+ "Java class name is not callable with the value: "
+ originalValue, e);
}
}
/**
* 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.
*
* Awake driver requires an URL which is an http url in the format:
* {@code http(s):///}
*
* Example:
* {@code http://www.acme.com/AwakeSqlManager}
*
* @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 {
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;
}
}
/**
* 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 {
DriverPropertyInfo[] arrayOfDriverPropertyInfo = new DriverPropertyInfo[15];
if (info != null) {
info.remove("RemarksReporting");
}
int i = 0;
DriverPropertyInfo driverPropertyInfo = null;
driverPropertyInfo = new DriverPropertyInfo("user",
info.getProperty("user"));
driverPropertyInfo.description = "Username to connect to the remote database as";
driverPropertyInfo.required = true;
arrayOfDriverPropertyInfo[i++] = driverPropertyInfo;
driverPropertyInfo = new DriverPropertyInfo("password", null);
driverPropertyInfo.description = "Password to use when authenticating";
driverPropertyInfo.required = true;
arrayOfDriverPropertyInfo[i++] = driverPropertyInfo;
driverPropertyInfo = new DriverPropertyInfo("httpProxyAddress",
info.getProperty("httpProxyAddress"));
driverPropertyInfo.description = "Http proxy address to use";
driverPropertyInfo.required = false;
arrayOfDriverPropertyInfo[i++] = driverPropertyInfo;
driverPropertyInfo = new DriverPropertyInfo("httpProxyPort",
info.getProperty("httpProxyPort"));
driverPropertyInfo.description = "Http proxy Port to use";
driverPropertyInfo.required = false;
arrayOfDriverPropertyInfo[i++] = driverPropertyInfo;
driverPropertyInfo = new DriverPropertyInfo("httpProxyUsername",
info.getProperty("httpProxyUsername"));
driverPropertyInfo.description = "Http proxy credential username";
driverPropertyInfo.required = false;
arrayOfDriverPropertyInfo[i++] = driverPropertyInfo;
driverPropertyInfo = new DriverPropertyInfo("httpProxyPassword",
info.getProperty("httpProxyPassword"));
driverPropertyInfo.description = "Http proxy credential password";
driverPropertyInfo.required = false;
arrayOfDriverPropertyInfo[i++] = driverPropertyInfo;
driverPropertyInfo = new DriverPropertyInfo("httpProxyWorkstation",
info.getProperty("httpProxyWorkstation"));
driverPropertyInfo.description = "For NTLM proxy: the workstation";
driverPropertyInfo.required = false;
arrayOfDriverPropertyInfo[i++] = driverPropertyInfo;
driverPropertyInfo = new DriverPropertyInfo("httpProxyDomain",
info.getProperty("httpProxyDomain"));
driverPropertyInfo.description = "For NTLM proxy: the domain";
driverPropertyInfo.required = false;
arrayOfDriverPropertyInfo[i++] = driverPropertyInfo;
driverPropertyInfo = new DriverPropertyInfo("maxLengthForString",
info.getProperty("maxLengthForString"));
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;
arrayOfDriverPropertyInfo[i++] = driverPropertyInfo;
driverPropertyInfo = new DriverPropertyInfo("uploadBufferSize",
info.getProperty("uploadBufferSize"));
driverPropertyInfo.description = "Int for the buffer size when uploading a Blob/Clob. Defaults to 20480 (20 Kb)";
driverPropertyInfo.required = false;
arrayOfDriverPropertyInfo[i++] = driverPropertyInfo;
driverPropertyInfo = new DriverPropertyInfo("downloadBufferSize",
info.getProperty("downloadBufferSize"));
driverPropertyInfo.description = "Int for the buffer size when downloading a Blob/Clob. Defaults to 20480 (20 Kb)";
driverPropertyInfo.required = false;
arrayOfDriverPropertyInfo[i++] = driverPropertyInfo;
driverPropertyInfo = new DriverPropertyInfo("encryptionPassword",
info.getProperty("encryptionPassword"));
driverPropertyInfo.description = "The password to use to encrypt all request parameter names and values. Defaults to null (no encryption).";
driverPropertyInfo.required = false;
arrayOfDriverPropertyInfo[i++] = driverPropertyInfo;
driverPropertyInfo = new DriverPropertyInfo("htmlEncoding",
info.getProperty("htmlEncoding"));
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;
arrayOfDriverPropertyInfo[i++] = driverPropertyInfo;
driverPropertyInfo = new DriverPropertyInfo("acceptAllSslCertificates",
info.getProperty("acceptAllSslCertificates"));
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;
arrayOfDriverPropertyInfo[i++] = driverPropertyInfo;
driverPropertyInfo = new DriverPropertyInfo("statelessMode",
info.getProperty("statelessMode"));
driverPropertyInfo.description = "Boolean that says if the Driver is configured to contact the remote server in stateless mode. Defaults to false.";
driverPropertyInfo.required = false;
arrayOfDriverPropertyInfo[i++] = driverPropertyInfo;
return arrayOfDriverPropertyInfo;
}
/**
* Retrieves this driver's major version number.
*
* @return this driver's major version number
*/
@Override
public int getMajorVersion() {
return 2;
}
/**
* Gets the driver's minor version number.
*
* @return this driver's minor version number
*/
@Override
public int getMinorVersion() {
return 0;
}
/**
* 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 Awake driver is not a 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) {
AwakeClientLogger.log(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();
}
}