[NO ISSUE][JDBC] Move ADBProtocol into asterix-jdbc-driver

- user model changes: no
- storage format changes: no
- interface changes: no

Details:
- Move ADBProtocol from asterix-jdbc-core into asterix-jdbc-driver
- Remove httpclient depenedency from asterix-jdbc-core

Change-Id: Ic075b43248c076cb5c9c76092c60e110038df8e7
Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb-clients/+/13666
Reviewed-by: Dmitry Lychagin <dmitry.lychagin@couchbase.com>
Reviewed-by: Ian Maxon <imaxon@uci.edu>
Tested-by: Ian Maxon <imaxon@uci.edu>
diff --git a/asterixdb-jdbc/asterix-jdbc-core/pom.xml b/asterixdb-jdbc/asterix-jdbc-core/pom.xml
index 143ce8e..59f4b11 100644
--- a/asterixdb-jdbc/asterix-jdbc-core/pom.xml
+++ b/asterixdb-jdbc/asterix-jdbc-core/pom.xml
@@ -42,14 +42,6 @@
 
   <dependencies>
     <dependency>
-      <groupId>org.apache.httpcomponents</groupId>
-      <artifactId>httpclient</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>org.apache.httpcomponents</groupId>
-      <artifactId>httpcore</artifactId>
-    </dependency>
-    <dependency>
       <groupId>com.fasterxml.jackson.core</groupId>
       <artifactId>jackson-core</artifactId>
     </dependency>
diff --git a/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBDriverBase.java b/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBDriverBase.java
index 904c18b..9bd8641 100644
--- a/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBDriverBase.java
+++ b/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBDriverBase.java
@@ -21,7 +21,6 @@
 
 import java.net.URI;
 import java.net.URISyntaxException;
-import java.nio.charset.StandardCharsets;
 import java.sql.Connection;
 import java.sql.DriverManager;
 import java.sql.DriverPropertyInfo;
@@ -40,9 +39,6 @@
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
-import org.apache.http.NameValuePair;
-import org.apache.http.client.utils.URLEncodedUtils;
-
 public abstract class ADBDriverBase {
 
     static final int JDBC_MAJOR_VERSION = 4;
@@ -72,18 +68,12 @@
         }
     }
 
-    private static void parseConnectionProperties(List<NameValuePair> inProps1, Properties inProps2,
-            ADBDriverContext driverContext, Map<ADBDriverProperty, Object> outProperties, SQLWarning outWarning)
-            throws SQLException {
-        for (NameValuePair pair : inProps1) {
-            String name = pair.getName();
-            String value = pair.getValue();
-            parseConnectionProperty(name, value, driverContext, outProperties, outWarning);
-        }
-        if (inProps2 != null) {
-            for (Enumeration<?> en = inProps2.propertyNames(); en.hasMoreElements();) {
+    private static void parseConnectionProperties(Properties inProps, ADBDriverContext driverContext,
+            Map<ADBDriverProperty, Object> outProperties, SQLWarning outWarning) throws SQLException {
+        if (inProps != null) {
+            for (Enumeration<?> en = inProps.propertyNames(); en.hasMoreElements();) {
                 String name = en.nextElement().toString();
-                String value = inProps2.getProperty(name);
+                String value = inProps.getProperty(name);
                 parseConnectionProperty(name, value, driverContext, outProperties, outWarning);
             }
         }
@@ -156,12 +146,14 @@
             port = defaultApiPort;
         }
 
-        List<NameValuePair> urlParams = URLEncodedUtils.parse(subUri, StandardCharsets.UTF_8);
+        Properties uriParams = getURIParameters(subUri);
 
         ADBDriverContext driverContext = getOrCreateDriverContext();
-        Map<ADBDriverProperty, Object> properties = new HashMap<>();
         SQLWarning warning = new SQLWarning();
-        parseConnectionProperties(urlParams, info, driverContext, properties, warning);
+        Map<ADBDriverProperty, Object> properties = new HashMap<>();
+        parseConnectionProperties(uriParams, driverContext, properties, warning);
+        parseConnectionProperties(info, driverContext, properties, warning);
+
         warning = warning.getNextWarning() != null ? warning.getNextWarning() : null;
 
         String path = subUri.getPath();
@@ -247,4 +239,6 @@
             throws SQLException {
         return new ADBConnection(protocol, url, databaseVersion, dataverseCanonicalName, properties, connectWarning);
     }
+
+    protected abstract Properties getURIParameters(URI uri) throws SQLException;
 }
diff --git a/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBErrorReporter.java b/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBErrorReporter.java
index 4f409b7..8ae76f3 100644
--- a/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBErrorReporter.java
+++ b/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBErrorReporter.java
@@ -28,161 +28,162 @@
 import java.sql.SQLNonTransientConnectionException;
 import java.sql.SQLTimeoutException;
 import java.sql.SQLTransientConnectionException;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 
-import org.apache.http.conn.ConnectTimeoutException;
-
 import com.fasterxml.jackson.core.JsonProcessingException;
 
-final class ADBErrorReporter {
+public class ADBErrorReporter {
 
-    private static final List<Class<? extends IOException>> TRANSIENT_CONNECTION_ERRORS =
-            Arrays.asList(java.net.NoRouteToHostException.class, org.apache.http.NoHttpResponseException.class,
-                    org.apache.http.conn.HttpHostConnectException.class);
-
-    protected SQLException errorObjectClosed(Class<?> jdbcInterface) {
+    public SQLException errorObjectClosed(Class<?> jdbcInterface) {
         return new SQLException(String.format("%s is closed", jdbcInterface.getSimpleName()));
     }
 
-    protected SQLFeatureNotSupportedException errorMethodNotSupported(Class<?> jdbcInterface, String methodName) {
+    public SQLFeatureNotSupportedException errorMethodNotSupported(Class<?> jdbcInterface, String methodName) {
         return new SQLFeatureNotSupportedException(
                 String.format("Method %s.%s() is not supported", jdbcInterface.getName(), methodName));
     }
 
-    protected SQLClientInfoException errorClientInfoMethodNotSupported(Class<?> jdbcInterface, String methodName) {
+    public SQLClientInfoException errorClientInfoMethodNotSupported(Class<?> jdbcInterface, String methodName) {
         return new SQLClientInfoException(
                 String.format("Method %s.%s() is not supported", jdbcInterface.getName(), methodName),
                 Collections.emptyMap());
     }
 
-    protected SQLException errorParameterNotSupported(String parameterName) {
+    public SQLException errorParameterNotSupported(String parameterName) {
         return new SQLException(String.format("Unsupported parameter %s", parameterName));
     }
 
-    protected String warningParameterNotSupported(String parameterName) {
+    public String warningParameterNotSupported(String parameterName) {
         return String.format("Unsupported parameter %s", parameterName);
     }
 
-    protected SQLException errorParameterValueNotSupported(String parameterName) {
+    public SQLException errorParameterValueNotSupported(String parameterName) {
         return new SQLException(String.format("Unsupported or invalid value of %s parameter", parameterName));
     }
 
-    protected String warningParameterValueNotSupported(String parameterName) {
+    public String warningParameterValueNotSupported(String parameterName) {
         return String.format("Ignored unsupported or invalid value of %s parameter", parameterName);
     }
 
-    protected SQLException errorIncompatibleMode(String mode) {
+    public SQLException errorIncompatibleMode(String mode) {
         return new SQLException(String.format("Operation cannot be performed in %s mode", mode));
     }
 
-    protected SQLException errorInProtocol() {
+    public SQLException errorInProtocol() {
         return new SQLNonTransientConnectionException("Protocol error", SQLState.CONNECTION_FAILURE.code);
     }
 
-    protected SQLException errorInProtocol(String badValue) {
+    public SQLException errorInProtocol(String badValue) {
         return new SQLNonTransientConnectionException(String.format("Protocol error. Unexpected %s", badValue),
                 SQLState.CONNECTION_FAILURE.code);
     }
 
-    protected SQLException errorInProtocol(JsonProcessingException e) {
+    public SQLException errorInProtocol(JsonProcessingException e) {
         return new SQLNonTransientConnectionException(String.format("Protocol error. %s", getMessage(e)),
                 SQLState.CONNECTION_FAILURE.code, e);
     }
 
-    protected SQLException errorInConnection(String badValue) {
+    public SQLException errorInConnection(String badValue) {
         return new SQLNonTransientConnectionException(String.format("Connection error. Unexpected %s", badValue),
                 SQLState.CONNECTION_FAILURE.code);
     }
 
-    protected SQLException errorInConnection(IOException e) {
+    public SQLException errorInConnection(IOException e) {
         String message = String.format("Connection error. %s", getMessage(e));
-        return e instanceof ConnectTimeoutException ? errorTimeout(message, e)
-                : couldBeTransientConnectionError(e)
+        return isTimeoutConnectionError(e) ? errorTimeout(message, e)
+                : isTransientConnectionError(e)
                         ? new SQLTransientConnectionException(message, SQLState.CONNECTION_FAILURE.code, e)
                         : new SQLNonTransientConnectionException(message, SQLState.CONNECTION_FAILURE.code, e);
     }
 
-    protected SQLException errorClosingResource(IOException e) {
+    public SQLException errorClosingResource(IOException e) {
         return new SQLException(String.format("Error closing resources. %s", getMessage(e)), e);
     }
 
-    protected SQLInvalidAuthorizationSpecException errorAuth() {
+    public SQLInvalidAuthorizationSpecException errorAuth() {
         return new SQLInvalidAuthorizationSpecException("Authentication/authorization error",
                 SQLState.INVALID_AUTH_SPEC.code);
     }
 
-    protected SQLException errorColumnNotFound(String columnNameOrNumber) {
+    public SQLException errorColumnNotFound(String columnNameOrNumber) {
         return new SQLException(String.format("Column %s was not found", columnNameOrNumber));
     }
 
-    protected SQLException errorUnexpectedColumnValue(ADBDatatype type, String columnName) {
+    public SQLException errorUnexpectedColumnValue(ADBDatatype type, String columnName) {
         return new SQLException(
                 String.format("Unexpected value of type %s for column %s", type.getTypeName(), columnName));
     }
 
-    protected SQLException errorUnwrapTypeMismatch(Class<?> iface) {
+    public SQLException errorUnwrapTypeMismatch(Class<?> iface) {
         return new SQLException(String.format("Cannot unwrap to %s", iface.getName()));
     }
 
-    protected SQLException errorInvalidStatementCategory() {
+    public SQLException errorInvalidStatementCategory() {
         return new SQLException("Invalid statement category");
     }
 
-    protected SQLException errorUnexpectedType(Class<?> type) {
+    public SQLException errorUnexpectedType(Class<?> type) {
         return new SQLException(String.format("Unexpected type %s", type.getName()), SQLState.INVALID_DATE_TYPE.code);
     }
 
-    protected SQLException errorUnexpectedType(byte typeTag) {
+    public SQLException errorUnexpectedType(byte typeTag) {
         return new SQLException(String.format("Unexpected type %s", typeTag), SQLState.INVALID_DATE_TYPE.code);
     }
 
-    protected SQLException errorUnexpectedType(ADBDatatype type) {
+    public SQLException errorUnexpectedType(ADBDatatype type) {
         return new SQLException(String.format("Unexpected type %s", type.getTypeName()),
                 SQLState.INVALID_DATE_TYPE.code);
     }
 
-    protected SQLException errorInvalidValueOfType(ADBDatatype type) {
+    public SQLException errorInvalidValueOfType(ADBDatatype type) {
         return new SQLException(String.format("Invalid value of type %s", type), SQLState.INVALID_DATE_TYPE.code);
     }
 
-    protected SQLException errorNoResult() {
+    public SQLException errorNoResult() {
         return new SQLException("Result is unavailable");
     }
 
-    protected SQLException errorBadResultSignature() {
+    public SQLException errorBadResultSignature() {
         return new SQLException("Cannot infer result columns");
     }
 
-    protected SQLException errorNoCurrentRow() {
+    public SQLException errorNoCurrentRow() {
         return new SQLException("No current row", SQLState.INVALID_CURSOR_POSITION.code);
     }
 
-    protected SQLException errorInRequestGeneration(IOException e) {
+    public SQLException errorInRequestGeneration(IOException e) {
         return new SQLException(String.format("Cannot create request. %s", getMessage(e)), e);
     }
 
-    protected SQLException errorInRequestURIGeneration(URISyntaxException e) {
+    public SQLException errorInRequestURIGeneration(URISyntaxException e) {
         return new SQLException(String.format("Cannot create request URI. %s", getMessage(e)), e);
     }
 
-    protected SQLException errorInResultHandling(IOException e) {
+    public SQLException errorInResultHandling(IOException e) {
         return new SQLException(String.format("Cannot reading result. %s", getMessage(e)), e);
     }
 
-    protected SQLTimeoutException errorTimeout() {
+    public SQLTimeoutException errorTimeout() {
         return new SQLTimeoutException();
     }
 
-    protected SQLTimeoutException errorTimeout(String message, IOException cause) {
+    public SQLTimeoutException errorTimeout(String message, IOException cause) {
         return new SQLTimeoutException(message, cause);
     }
 
-    protected boolean couldBeTransientConnectionError(IOException e) {
+    protected boolean isTimeoutConnectionError(IOException e) {
+        return false;
+    }
+
+    protected boolean isTransientConnectionError(IOException e) {
+        return false;
+    }
+
+    protected boolean isInstanceOf(IOException e, List<Class<? extends IOException>> classList) {
         if (e != null) {
-            for (Class<? extends IOException> c : TRANSIENT_CONNECTION_ERRORS) {
+            for (Class<? extends IOException> c : classList) {
                 if (c.isInstance(e)) {
                     return true;
                 }
@@ -192,7 +193,7 @@
         return false;
     }
 
-    protected String getMessage(Exception e) {
+    public String getMessage(Exception e) {
         String message = e != null ? e.getMessage() : null;
         return message != null ? message : "";
     }
diff --git a/asterixdb-jdbc/asterix-jdbc-driver/pom.xml b/asterixdb-jdbc/asterix-jdbc-driver/pom.xml
index 3a68e8c..8bbf17a 100644
--- a/asterixdb-jdbc/asterix-jdbc-driver/pom.xml
+++ b/asterixdb-jdbc/asterix-jdbc-driver/pom.xml
@@ -48,6 +48,22 @@
       <artifactId>asterix-jdbc-core</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>com.fasterxml.jackson.core</groupId>
+      <artifactId>jackson-core</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>com.fasterxml.jackson.core</groupId>
+      <artifactId>jackson-databind</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.httpcomponents</groupId>
+      <artifactId>httpclient</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.httpcomponents</groupId>
+      <artifactId>httpcore</artifactId>
+    </dependency>
   </dependencies>
 
   <build>
diff --git a/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBProtocol.java b/asterixdb-jdbc/asterix-jdbc-driver/src/main/java/org/apache/asterix/jdbc/ADBProtocol.java
similarity index 95%
rename from asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBProtocol.java
rename to asterixdb-jdbc/asterix-jdbc-driver/src/main/java/org/apache/asterix/jdbc/ADBProtocol.java
index 38afb84..5bc0d9e 100644
--- a/asterixdb-jdbc/asterix-jdbc-core/src/main/java/org/apache/asterix/jdbc/core/ADBProtocol.java
+++ b/asterixdb-jdbc/asterix-jdbc-driver/src/main/java/org/apache/asterix/jdbc/ADBProtocol.java
@@ -17,16 +17,19 @@
  * under the License.
  */
 
-package org.apache.asterix.jdbc.core;
+package org.apache.asterix.jdbc;
 
 import java.io.ByteArrayOutputStream;
 import java.io.Closeable;
 import java.io.FilterInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.net.NoRouteToHostException;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.sql.SQLException;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -34,11 +37,16 @@
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
+import org.apache.asterix.jdbc.core.ADBDriverContext;
+import org.apache.asterix.jdbc.core.ADBDriverProperty;
+import org.apache.asterix.jdbc.core.ADBErrorReporter;
+import org.apache.asterix.jdbc.core.ADBProtocolBase;
 import org.apache.http.Header;
 import org.apache.http.HttpEntity;
 import org.apache.http.HttpHeaders;
 import org.apache.http.HttpHost;
 import org.apache.http.HttpStatus;
+import org.apache.http.NoHttpResponseException;
 import org.apache.http.auth.AuthScope;
 import org.apache.http.auth.UsernamePasswordCredentials;
 import org.apache.http.client.AuthCache;
@@ -52,7 +60,9 @@
 import org.apache.http.client.protocol.HttpClientContext;
 import org.apache.http.client.utils.URIBuilder;
 import org.apache.http.config.SocketConfig;
+import org.apache.http.conn.ConnectTimeoutException;
 import org.apache.http.conn.HttpClientConnectionManager;
+import org.apache.http.conn.HttpHostConnectException;
 import org.apache.http.entity.ContentProducer;
 import org.apache.http.entity.ContentType;
 import org.apache.http.entity.EntityTemplate;
@@ -71,12 +81,18 @@
 import com.fasterxml.jackson.core.JsonToken;
 import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
 
-public class ADBProtocol extends ADBProtocolBase {
+final class ADBProtocol extends ADBProtocolBase {
 
     private static final String QUERY_SERVICE_ENDPOINT_PATH = "/query/service";
     private static final String QUERY_RESULT_ENDPOINT_PATH = "/query/service/result";
     private static final String ACTIVE_REQUESTS_ENDPOINT_PATH = "/admin/requests/running";
 
+    static final List<Class<? extends IOException>> TIMEOUT_CONNECTION_ERRORS =
+            Collections.singletonList(ConnectTimeoutException.class);
+
+    static final List<Class<? extends IOException>> TRANSIENT_CONNECTION_ERRORS =
+            Arrays.asList(NoRouteToHostException.class, NoHttpResponseException.class, HttpHostConnectException.class);
+
     final URI queryEndpoint;
     final URI queryResultEndpoint;
     final URI activeRequestsEndpoint;
diff --git a/asterixdb-jdbc/asterix-jdbc-driver/src/main/java/org/apache/asterix/jdbc/Driver.java b/asterixdb-jdbc/asterix-jdbc-driver/src/main/java/org/apache/asterix/jdbc/Driver.java
index f13ac05..97fb256 100644
--- a/asterixdb-jdbc/asterix-jdbc-driver/src/main/java/org/apache/asterix/jdbc/Driver.java
+++ b/asterixdb-jdbc/asterix-jdbc-driver/src/main/java/org/apache/asterix/jdbc/Driver.java
@@ -19,14 +19,20 @@
 
 package org.apache.asterix.jdbc;
 
+import java.io.IOException;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
 import java.sql.SQLException;
+import java.util.List;
 import java.util.Map;
+import java.util.Properties;
 
 import org.apache.asterix.jdbc.core.ADBDriverBase;
 import org.apache.asterix.jdbc.core.ADBDriverContext;
 import org.apache.asterix.jdbc.core.ADBDriverProperty;
-import org.apache.asterix.jdbc.core.ADBProtocol;
-import org.apache.asterix.jdbc.core.ADBProtocolBase;
+import org.apache.asterix.jdbc.core.ADBErrorReporter;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.utils.URLEncodedUtils;
 
 public class Driver extends ADBDriverBase implements java.sql.Driver {
 
@@ -44,8 +50,36 @@
     }
 
     @Override
-    protected ADBProtocolBase createProtocol(String host, int port, Map<ADBDriverProperty, Object> properties,
+    protected ADBProtocol createProtocol(String host, int port, Map<ADBDriverProperty, Object> properties,
             ADBDriverContext driverContext) throws SQLException {
         return new ADBProtocol(host, port, properties, driverContext);
     }
+
+    @Override
+    protected Properties getURIParameters(URI uri) {
+        List<NameValuePair> params = URLEncodedUtils.parse(uri, StandardCharsets.UTF_8);
+        if (params.isEmpty()) {
+            return null;
+        }
+        Properties properties = new Properties();
+        for (NameValuePair pair : params) {
+            properties.setProperty(pair.getName(), pair.getValue());
+        }
+        return properties;
+    }
+
+    @Override
+    protected ADBErrorReporter createErrorReporter() {
+        return new ADBErrorReporter() {
+            @Override
+            protected boolean isTimeoutConnectionError(IOException e) {
+                return isInstanceOf(e, ADBProtocol.TIMEOUT_CONNECTION_ERRORS);
+            }
+
+            @Override
+            protected boolean isTransientConnectionError(IOException e) {
+                return isInstanceOf(e, ADBProtocol.TRANSIENT_CONNECTION_ERRORS);
+            }
+        };
+    }
 }