CALCITE-2285 Support client cert keystore for Avatica Client

Closes #57

Signed-off-by: Josh Elser <els...@apache.org>


Project: http://git-wip-us.apache.org/repos/asf/calcite-avatica/repo
Commit: http://git-wip-us.apache.org/repos/asf/calcite-avatica/commit/97c8a161
Tree: http://git-wip-us.apache.org/repos/asf/calcite-avatica/tree/97c8a161
Diff: http://git-wip-us.apache.org/repos/asf/calcite-avatica/diff/97c8a161

Branch: refs/heads/branch-avatica-1.12
Commit: 97c8a1612220e46fe70fed3a92082e55248f868e
Parents: 9a37845
Author: Karan Mehta <k.me...@salesforce.com>
Authored: Thu Jun 14 12:24:38 2018 -0400
Committer: Josh Elser <els...@apache.org>
Committed: Thu Jun 14 12:39:40 2018 -0400

----------------------------------------------------------------------
 .../avatica/BuiltInConnectionProperty.java      |   9 ++
 .../calcite/avatica/ConnectionConfig.java       |   6 +
 .../calcite/avatica/ConnectionConfigImpl.java   |  17 +++
 .../remote/AvaticaCommonsHttpClientImpl.java    | 108 ++++++++++++-----
 .../remote/AvaticaHttpClientFactoryImpl.java    |  15 ++-
 .../avatica/remote/KeyStoreConfigurable.java    |  39 ++++++
 ...aCommonsHttpClientImplSocketFactoryTest.java | 120 +++++++++++++++++++
 core/src/test/resources/log4j.properties        |  24 ++++
 8 files changed, 310 insertions(+), 28 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/calcite-avatica/blob/97c8a161/core/src/main/java/org/apache/calcite/avatica/BuiltInConnectionProperty.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/avatica/BuiltInConnectionProperty.java 
b/core/src/main/java/org/apache/calcite/avatica/BuiltInConnectionProperty.java
index 1da7025..a1babb3 100644
--- 
a/core/src/main/java/org/apache/calcite/avatica/BuiltInConnectionProperty.java
+++ 
b/core/src/main/java/org/apache/calcite/avatica/BuiltInConnectionProperty.java
@@ -76,6 +76,15 @@ public enum BuiltInConnectionProperty implements 
ConnectionProperty {
   /** Password for the truststore */
   TRUSTSTORE_PASSWORD("truststore_password", Type.STRING, null, false),
 
+  /** Keystore for MTLS authentication */
+  KEYSTORE("keystore", Type.STRING, null, false),
+
+  /** Password for the keystore */
+  KEYSTORE_PASSWORD("keystore_password", Type.STRING, null, false),
+
+  /** Password for the key inside keystore */
+  KEY_PASSWORD("key_password", Type.STRING, null, false),
+
   HOSTNAME_VERIFICATION("hostname_verification", Type.ENUM, 
HostnameVerification.STRICT,
       HostnameVerification.class, false);
 

http://git-wip-us.apache.org/repos/asf/calcite-avatica/blob/97c8a161/core/src/main/java/org/apache/calcite/avatica/ConnectionConfig.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/avatica/ConnectionConfig.java 
b/core/src/main/java/org/apache/calcite/avatica/ConnectionConfig.java
index cd18ec5..bbbfa87 100644
--- a/core/src/main/java/org/apache/calcite/avatica/ConnectionConfig.java
+++ b/core/src/main/java/org/apache/calcite/avatica/ConnectionConfig.java
@@ -54,6 +54,12 @@ public interface ConnectionConfig {
   File truststore();
   /** @see BuiltInConnectionProperty#TRUSTSTORE_PASSWORD */
   String truststorePassword();
+  /** @see BuiltInConnectionProperty#KEYSTORE */
+  File keystore();
+  /** @see BuiltInConnectionProperty#KEYSTORE_PASSWORD */
+  String keystorePassword();
+  /** @see BuiltInConnectionProperty#KEY_PASSWORD */
+  String keyPassword();
   /** @see BuiltInConnectionProperty#HOSTNAME_VERIFICATION */
   HostnameVerification hostnameVerification();
 }

http://git-wip-us.apache.org/repos/asf/calcite-avatica/blob/97c8a161/core/src/main/java/org/apache/calcite/avatica/ConnectionConfigImpl.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/avatica/ConnectionConfigImpl.java 
b/core/src/main/java/org/apache/calcite/avatica/ConnectionConfigImpl.java
index 94cdc51..36cdf61 100644
--- a/core/src/main/java/org/apache/calcite/avatica/ConnectionConfigImpl.java
+++ b/core/src/main/java/org/apache/calcite/avatica/ConnectionConfigImpl.java
@@ -106,6 +106,23 @@ public class ConnectionConfigImpl implements 
ConnectionConfig {
     return 
BuiltInConnectionProperty.TRUSTSTORE_PASSWORD.wrap(properties).getString();
   }
 
+  public File keystore() {
+    String filename = 
BuiltInConnectionProperty.KEYSTORE.wrap(properties).getString();
+    if (null == filename) {
+      return null;
+    }
+    return new File(filename);
+  }
+
+  public String keystorePassword() {
+    return 
BuiltInConnectionProperty.KEYSTORE_PASSWORD.wrap(properties).getString();
+  }
+
+  public String keyPassword() {
+    return BuiltInConnectionProperty.KEY_PASSWORD.wrap(properties).getString();
+
+  }
+
   public HostnameVerification hostnameVerification() {
     return BuiltInConnectionProperty.HOSTNAME_VERIFICATION.wrap(properties)
         .getEnum(HostnameVerification.class);

http://git-wip-us.apache.org/repos/asf/calcite-avatica/blob/97c8a161/core/src/main/java/org/apache/calcite/avatica/remote/AvaticaCommonsHttpClientImpl.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/avatica/remote/AvaticaCommonsHttpClientImpl.java
 
b/core/src/main/java/org/apache/calcite/avatica/remote/AvaticaCommonsHttpClientImpl.java
index 5576847..e7e6aa0 100644
--- 
a/core/src/main/java/org/apache/calcite/avatica/remote/AvaticaCommonsHttpClientImpl.java
+++ 
b/core/src/main/java/org/apache/calcite/avatica/remote/AvaticaCommonsHttpClientImpl.java
@@ -28,6 +28,7 @@ import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpPost;
 import org.apache.http.client.protocol.HttpClientContext;
 import org.apache.http.config.Lookup;
+import org.apache.http.config.Registry;
 import org.apache.http.config.RegistryBuilder;
 import org.apache.http.conn.socket.ConnectionSocketFactory;
 import org.apache.http.conn.socket.PlainConnectionSocketFactory;
@@ -42,6 +43,7 @@ import org.apache.http.impl.client.BasicCredentialsProvider;
 import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.http.impl.client.HttpClients;
 import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.apache.http.ssl.SSLContextBuilder;
 import org.apache.http.ssl.SSLContexts;
 import org.apache.http.util.EntityUtils;
 
@@ -64,7 +66,8 @@ import javax.net.ssl.SSLContext;
  * sent and received across the wire.
  */
 public class AvaticaCommonsHttpClientImpl implements AvaticaHttpClient,
-    UsernamePasswordAuthenticateable, TrustStoreConfigurable, 
HostnameVerificationConfigurable {
+    UsernamePasswordAuthenticateable, TrustStoreConfigurable,
+        KeyStoreConfigurable, HostnameVerificationConfigurable {
   private static final Logger LOG = 
LoggerFactory.getLogger(AvaticaCommonsHttpClientImpl.class);
 
   // Some basic exposed configurations
@@ -78,14 +81,19 @@ public class AvaticaCommonsHttpClientImpl implements 
AvaticaHttpClient,
   protected final URI uri;
   protected BasicAuthCache authCache;
   protected CloseableHttpClient client;
-  PoolingHttpClientConnectionManager pool;
+  protected Registry<ConnectionSocketFactory> socketFactoryRegistry;
+  protected PoolingHttpClientConnectionManager pool;
 
   protected UsernamePasswordCredentials credentials = null;
   protected CredentialsProvider credentialsProvider = null;
   protected Lookup<AuthSchemeProvider> authRegistry = null;
 
+  protected boolean configureHttpsSocket = false;
   protected File truststore = null;
+  protected File keystore = null;
   protected String truststorePassword = null;
+  protected String keystorePassword = null;
+  protected String keyPassword = null;
   protected HostnameVerification hostnameVerification = null;
 
   public AvaticaCommonsHttpClientImpl(URL url) {
@@ -95,29 +103,15 @@ public class AvaticaCommonsHttpClientImpl implements 
AvaticaHttpClient,
   }
 
   private void initializeClient() {
-    SSLConnectionSocketFactory sslFactory = null;
-    if (null != truststore && null != truststorePassword) {
-      try {
-        SSLContext sslcontext = SSLContexts.custom().loadTrustMaterial(
-            truststore, truststorePassword.toCharArray()).build();
-
-        final HostnameVerifier verifier = 
getHostnameVerifier(hostnameVerification);
-
-        sslFactory = new SSLConnectionSocketFactory(sslcontext, verifier);
-      } catch (Exception e) {
-        throw new RuntimeException(e);
-      }
-    } else {
-      LOG.debug("Not configuring HTTPS because of missing 
truststore/password");
-    }
+    socketFactoryRegistry = this.configureSocketFactories();
+    configureConnectionPool(socketFactoryRegistry);
+    this.authCache = new BasicAuthCache();
+    // A single thread-safe HttpClient, pooling connections via the 
ConnectionManager
+    this.client = HttpClients.custom().setConnectionManager(pool).build();
+  }
 
-    RegistryBuilder<ConnectionSocketFactory> registryBuilder = 
RegistryBuilder.create();
-    registryBuilder.register("http", 
PlainConnectionSocketFactory.getSocketFactory());
-    // Only register the SSL factory when provided
-    if (null != sslFactory) {
-      registryBuilder.register("https", sslFactory);
-    }
-    pool = new PoolingHttpClientConnectionManager(registryBuilder.build());
+  protected void configureConnectionPool(Registry<ConnectionSocketFactory> 
registry) {
+    pool = new PoolingHttpClientConnectionManager(registry);
     // Increase max total connection to 100
     final String maxCnxns =
         System.getProperty(MAX_POOLED_CONNECTIONS_KEY,
@@ -127,11 +121,57 @@ public class AvaticaCommonsHttpClientImpl implements 
AvaticaHttpClient,
     final String maxCnxnsPerRoute = 
System.getProperty(MAX_POOLED_CONNECTION_PER_ROUTE_KEY,
         MAX_POOLED_CONNECTION_PER_ROUTE_DEFAULT);
     pool.setDefaultMaxPerRoute(Integer.parseInt(maxCnxnsPerRoute));
+  }
 
-    this.authCache = new BasicAuthCache();
+  protected Registry<ConnectionSocketFactory> configureSocketFactories() {
+    RegistryBuilder<ConnectionSocketFactory> registryBuilder = 
RegistryBuilder.create();
+    if (host.getSchemeName().equalsIgnoreCase("https")) {
+      configureHttpsRegistry(registryBuilder);
+    } else {
+      configureHttpRegistry(registryBuilder);
+    }
+    return registryBuilder.build();
+  }
 
-    // A single thread-safe HttpClient, pooling connections via the 
ConnectionManager
-    this.client = HttpClients.custom().setConnectionManager(pool).build();
+  protected void 
configureHttpsRegistry(RegistryBuilder<ConnectionSocketFactory> 
registryBuilder) {
+    if (!configureHttpsSocket) {
+      LOG.debug("HTTPS Socket not being configured because no 
truststore/keystore provided");
+      return;
+    }
+
+    try {
+      SSLContext sslContext = getSSLContext();
+      final HostnameVerifier verifier = 
getHostnameVerifier(hostnameVerification);
+      SSLConnectionSocketFactory sslFactory = new 
SSLConnectionSocketFactory(sslContext, verifier);
+      registryBuilder.register("https", sslFactory);
+    } catch (Exception e) {
+      LOG.error("HTTPS registry configuration failed");
+      throw new RuntimeException(e);
+    }
+  }
+
+  private SSLContext getSSLContext() throws Exception {
+    SSLContextBuilder sslContextBuilder = SSLContexts.custom();
+    if (null != truststore && null != truststorePassword) {
+      loadTrustStore(sslContextBuilder);
+    }
+    if (null != keystore && null != keystorePassword && null != keyPassword) {
+      loadKeyStore(sslContextBuilder);
+    }
+    return sslContextBuilder.build();
+  }
+
+  protected void loadKeyStore(SSLContextBuilder sslContextBuilder) throws 
Exception {
+    sslContextBuilder.loadKeyMaterial(keystore,
+            keystorePassword.toCharArray(), keyPassword.toCharArray());
+  }
+
+  protected void loadTrustStore(SSLContextBuilder sslContextBuilder) throws 
Exception {
+    sslContextBuilder.loadTrustMaterial(truststore, 
truststorePassword.toCharArray());
+  }
+
+  protected void 
configureHttpRegistry(RegistryBuilder<ConnectionSocketFactory> registryBuilder) 
{
+    registryBuilder.register("http", 
PlainConnectionSocketFactory.getSocketFactory());
   }
 
   /**
@@ -244,11 +284,25 @@ public class AvaticaCommonsHttpClientImpl implements 
AvaticaHttpClient,
           "Truststore is must be an existing, regular file: " + truststore);
     }
     this.truststorePassword = Objects.requireNonNull(password);
+    configureHttpsSocket = true;
+    initializeClient();
+  }
+
+  @Override public void setKeyStore(File keystore, String keystorepassword, 
String keypassword) {
+    this.keystore = Objects.requireNonNull(keystore);
+    if (!keystore.exists() || !keystore.isFile()) {
+      throw new IllegalArgumentException(
+              "Keystore is must be an existing, regular file: " + keystore);
+    }
+    this.keystorePassword = Objects.requireNonNull(keystorepassword);
+    this.keyPassword = Objects.requireNonNull(keypassword);
+    configureHttpsSocket = true;
     initializeClient();
   }
 
   @Override public void setHostnameVerification(HostnameVerification 
verification) {
     this.hostnameVerification = Objects.requireNonNull(verification);
+    configureHttpsSocket = true;
     initializeClient();
   }
 }

http://git-wip-us.apache.org/repos/asf/calcite-avatica/blob/97c8a161/core/src/main/java/org/apache/calcite/avatica/remote/AvaticaHttpClientFactoryImpl.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/avatica/remote/AvaticaHttpClientFactoryImpl.java
 
b/core/src/main/java/org/apache/calcite/avatica/remote/AvaticaHttpClientFactoryImpl.java
index f352ac1..8f305dc 100644
--- 
a/core/src/main/java/org/apache/calcite/avatica/remote/AvaticaHttpClientFactoryImpl.java
+++ 
b/core/src/main/java/org/apache/calcite/avatica/remote/AvaticaHttpClientFactoryImpl.java
@@ -74,12 +74,25 @@ public class AvaticaHttpClientFactoryImpl implements 
AvaticaHttpClientFactory {
       File truststore = config.truststore();
       String truststorePassword = config.truststorePassword();
       if (null != truststore && null != truststorePassword) {
-        ((TrustStoreConfigurable) client).setTrustStore(truststore, 
truststorePassword);
+        ((TrustStoreConfigurable) client)
+                .setTrustStore(truststore, truststorePassword);
       }
     } else {
       LOG.debug("{} is not capable of SSL/TLS communication", 
client.getClass().getName());
     }
 
+    if (client instanceof KeyStoreConfigurable) {
+      File keystore = config.keystore();
+      String keystorePassword = config.keystorePassword();
+      String keyPassword = config.keyPassword();
+      if (null != keystore && null != keystorePassword && null != keyPassword) 
{
+        ((KeyStoreConfigurable) client)
+                .setKeyStore(keystore, keystorePassword, keyPassword);
+      }
+    } else {
+      LOG.debug("{} is not capable of Mutual authentication", 
client.getClass().getName());
+    }
+
     // Set the SSL hostname verification if the client supports it
     if (client instanceof HostnameVerificationConfigurable) {
       ((HostnameVerificationConfigurable) client)

http://git-wip-us.apache.org/repos/asf/calcite-avatica/blob/97c8a161/core/src/main/java/org/apache/calcite/avatica/remote/KeyStoreConfigurable.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/avatica/remote/KeyStoreConfigurable.java
 
b/core/src/main/java/org/apache/calcite/avatica/remote/KeyStoreConfigurable.java
new file mode 100644
index 0000000..eaffd2a
--- /dev/null
+++ 
b/core/src/main/java/org/apache/calcite/avatica/remote/KeyStoreConfigurable.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.avatica.remote;
+
+import java.io.File;
+
+/**
+ * Allows a keystore (and keystorepassword, keypassword) to be
+ * provided to enable MTLS authentication
+ */
+public interface KeyStoreConfigurable {
+
+    /**
+     * Sets a keystore containing the collection of client side certificates
+     * to use for HTTPS mutual authentication along with
+     * password for keystore and password for key
+     *
+     * @param keystore The keystore on the local filesystem
+     * @param keystorepassword The keystore's password
+     * @param keypassword The key's password
+     */
+  void setKeyStore(File keystore, String keystorepassword, String keypassword);
+}
+
+// End KeyStoreConfigurable.java

http://git-wip-us.apache.org/repos/asf/calcite-avatica/blob/97c8a161/core/src/test/java/org/apache/calcite/avatica/remote/AvaticaCommonsHttpClientImplSocketFactoryTest.java
----------------------------------------------------------------------
diff --git 
a/core/src/test/java/org/apache/calcite/avatica/remote/AvaticaCommonsHttpClientImplSocketFactoryTest.java
 
b/core/src/test/java/org/apache/calcite/avatica/remote/AvaticaCommonsHttpClientImplSocketFactoryTest.java
new file mode 100644
index 0000000..a75222a
--- /dev/null
+++ 
b/core/src/test/java/org/apache/calcite/avatica/remote/AvaticaCommonsHttpClientImplSocketFactoryTest.java
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.avatica.remote;
+
+import org.apache.http.conn.socket.ConnectionSocketFactory;
+import org.apache.http.conn.socket.PlainConnectionSocketFactory;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.ssl.SSLContextBuilder;
+
+import org.junit.Test;
+
+import java.io.File;
+import java.net.URL;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests to verify loading of truststore/keystore in 
AvaticaCommonsHttpClientImpl
+ */
+public class AvaticaCommonsHttpClientImplSocketFactoryTest {
+
+  private static final String HTTP_REGISTRY = "http";
+  private static final String HTTPS_REGISTRY = "https";
+
+  private URL url;
+  private AvaticaCommonsHttpClientImpl client;
+  private File storeFile;
+  private String password;
+
+  @Test public void testPlainSocketFactory() throws Exception {
+    configureHttpClient();
+    assertFalse("Https socket should not be configured"
+            + " without truststore/keystore", client.configureHttpsSocket);
+    verifyFactoryInstance(client, HTTP_REGISTRY, 
PlainConnectionSocketFactory.class);
+    verifyFactoryInstance(client, HTTPS_REGISTRY, null);
+    verify(client, times(0)).loadTrustStore(any(SSLContextBuilder.class));
+    verify(client, times(0)).loadKeyStore(any(SSLContextBuilder.class));
+  }
+
+  @Test public void testTrustStoreLoadedInFactory() throws Exception {
+    configureHttpsClient();
+    client.setTrustStore(storeFile, password);
+    assertTrue("Https socket should be configured"
+            + " with truststore", client.configureHttpsSocket);
+    verifyFactoryInstance(client, HTTP_REGISTRY, null);
+    verifyFactoryInstance(client, HTTPS_REGISTRY, 
SSLConnectionSocketFactory.class);
+    verify(client, times(1)).configureSocketFactories();
+    verify(client, times(1)).loadTrustStore(any(SSLContextBuilder.class));
+    verify(client, times(0)).loadKeyStore(any(SSLContextBuilder.class));
+  }
+
+  @Test public void testKeyStoreLoadedInFactory() throws Exception {
+    configureHttpsClient();
+    client.setKeyStore(storeFile, password, password);
+    assertTrue("Https socket should be configured"
+            + " with keystore", client.configureHttpsSocket);
+    verifyFactoryInstance(client, HTTP_REGISTRY, null);
+    verifyFactoryInstance(client, HTTPS_REGISTRY, 
SSLConnectionSocketFactory.class);
+    verify(client, times(1)).configureSocketFactories();
+    verify(client, times(0)).loadTrustStore(any(SSLContextBuilder.class));
+    verify(client, times(1)).loadKeyStore(any(SSLContextBuilder.class));
+  }
+
+  private void configureHttpClient() throws Exception {
+    url = new URL("http://fake_url.com";);
+    configureClient();
+  }
+
+  private void configureHttpsClient() throws Exception {
+    url = new URL("https://fake_url.com";);
+    configureClient();
+  }
+
+  private void configureClient() throws Exception {
+    client = spy(new AvaticaCommonsHttpClientImpl(url));
+    // storeFile can be used as either Keystore/Truststore
+    storeFile = mock(File.class);
+    when(storeFile.exists()).thenReturn(true);
+    when(storeFile.isFile()).thenReturn(true);
+    password = "";
+
+    doNothing().when(client).loadTrustStore(any(SSLContextBuilder.class));
+    doNothing().when(client).loadKeyStore(any(SSLContextBuilder.class));
+  }
+
+  <T> void verifyFactoryInstance(AvaticaCommonsHttpClientImpl client,
+      String registry, Class<T> expected) {
+    ConnectionSocketFactory factory = 
client.socketFactoryRegistry.lookup(registry);
+    if (expected == null) {
+      assertTrue("Factory for registry " + registry + " expected as null", 
factory == null);
+    } else {
+      assertTrue("Factory for registry " + registry + " expected of type " + 
expected.getName(),
+              expected.equals(factory.getClass()));
+    }
+  }
+}
+
+// End AvaticaCommonsHttpClientImplSocketFactoryTest.java

http://git-wip-us.apache.org/repos/asf/calcite-avatica/blob/97c8a161/core/src/test/resources/log4j.properties
----------------------------------------------------------------------
diff --git a/core/src/test/resources/log4j.properties 
b/core/src/test/resources/log4j.properties
new file mode 100644
index 0000000..834e2db
--- /dev/null
+++ b/core/src/test/resources/log4j.properties
@@ -0,0 +1,24 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to you under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Root logger is configured at INFO and is sent to A1
+log4j.rootLogger=INFO, A1
+
+# A1 goes to the console
+log4j.appender.A1=org.apache.log4j.ConsoleAppender
+
+# Set the pattern for each log message
+log4j.appender.A1.layout=org.apache.log4j.PatternLayout
+log4j.appender.A1.layout.ConversionPattern=%d [%t] %-5p - %m%n

Reply via email to