Josh, Please remember to use brackets about the JIRA case number in the commit. Not "CALCITE-2285" but "[CALCITE-2285]".
You write great code and great commit comments, but I'd just like to keep the commit format consistent across all committers. Julian On Thu, Jun 14, 2018 at 9:41 AM, <els...@apache.org> wrote: > Repository: calcite-avatica > Updated Branches: > refs/heads/master 9a3784529 -> 97c8a1612 > > > 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/master > 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 >