Java tutorial
/* * 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 <a href=http://www.aceql.com>Driver</a> class in order to access remote * SQL databases through HTTP from Android or Java desktop programs.<br> * <br> * <b>user</b> & <b>password</b> are the only required properties. <br> * <br> * Properties: * <ul> * <li><b>user</b>: username to connect to the remote database as.</li> * <li><b>password</b>: password to use when authenticating.</li> * <li><b>proxyType</b>: java.net.Proxy Type to use: HTTP or SOCKS. Defaults to HTTP.</li> * <li><b>proxyHostname</b>: java.net.Proxy hostname to use.</li> * <li><b>proxyPort</b>: java.net.Proxy Port to use.</li> * <li><b>proxyUsername</b>: Proxy credential username.</li> * <li><b>proxyPassword</b>: Proxy credential password.</li> * <li><b>maxLengthForString</b>: int for the maximum authorized length for a * string for upload or download Should be <= 2097152 (2Mb). Defaults to * 2097152.</li> * <li><b>acceptAllSslCertificates</b>: boolean to say if client sides allows * HTTPS call with all SSL Certificates, including "invalid" or self-signed * Certificates. Defaults to false.</li> * <li><b>encryptionPassword</b>: the password to use to encrypt all request * parameter names and values. Defaults to <code>null</code>.</li> * <li><b>htmlEncodingOn</b>: boolean to say if the upload/download of Clob * using character stream or ASCII stream must be html encoded. Defaults to * true.</li> * <li><b>compression</b>: boolean to say if the Driver is configured to * contact the remote server using http compression. Defaults to <code>true</code>.</li> * <li><b>statelessMode</b>: boolean to say if the Driver is configured to * contact the remote server in stateless mode. Defaults to false.</li> * <li><b>uploadChunkLength</b>: 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.</li> * <li><b>downloadChunkLength</b>: 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.</li> * <li><b>joinResultSetMetaData</b>: boolean to tell server to download ResultSet MetaData * along with ResultSet. Defaults to {@code false}.</li> * <li><b>zipResultSet</b>: boolean to tell server to GZIP ResultSet before download. * Defaults to {@code true}.</li> * </ul> * <p> * * @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. * * <P> * The driver will throw<code>SQLException</code> if it is the right driver * to connect to the given URL but has trouble connecting to the database. * * <P> * The <code>java.util.Properties</code> 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 * <code>Properties</code> 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 <code>Connection</code> 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<String, String> mapProps = RemoteDriverUtil.getQueryMap(query); Set<String> 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"); String zipResultSet = info2.getProperty("zipResultSet"); 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()); } boolean doZipResultSet = true; if (zipResultSet != null) { doZipResultSet = Boolean.parseBoolean(zipResultSet); debug("zipResultSet: " + doZipResultSet); } Connection connection = new RemoteConnection(url, username, password.toCharArray(), proxy, passwordAuthentication, sessionParameters, statelessModeBoolean, doJoinResultSetMetaData, doZipResultSet); 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 <code>true</code> if they * understand the subprotocol specified in the URL and <code>false</code> if * they do not. <br> * <br> * The AceQL driver requires an URL which is an http url in the format: <br> * {@code jdbc:aceql:http(s)://<server-name:port>/<Server SQL Manager Servlet path>} * <br> * <br> * Example: <br> * {@code jdbc:aceql:https://www.aceql.com:9443/ServerSqlManager} <br> * <br> * 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 <code>true</code> if this driver understands the given URL; * <code>false</code> 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. * <P> * The <code>getPropertyInfo</code> 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 <code>getPropertyInfo</code> 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 <code>DriverPropertyInfo</code> objects describing * possible properties. * @exception SQLException * if a database access error occurs */ @Override public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException { List<DriverPropertyInfo> driverPropertyInfoList = new ArrayList<DriverPropertyInfo>(); 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 = getNewDriverPropertyInfo("zipResultSet", info); driverPropertyInfo.description = "Boolean to tell server to GZIP ResultSet before download. Defaults to true."; 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 Compliant<sup><font * size=-2>TM</font></sup> driver. A driver may only report * <code>true</code> here if it passes the JDBC compliance tests; otherwise * it is required to return <code>false</code>. * <P> * JDBC compliance requires full support for the JDBC API and full support * for SQL 92 Entry Level. * <P> * Because the driver is not a genuine JDBC Compliant<sup><font * size=-2>TM</font></sup> driver, method returns <code>false</code> * * @return <code>false</code> */ @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 <code>java.util.logging</code>. * @since 1.7 */ // @Override do not not override for Java 6 compatibility public Logger getParentLogger() throws SQLFeatureNotSupportedException { throw new SQLFeatureNotSupportedException(); } }