Skip to content

Commit ca41df1

Browse files
committed
DEVEXP-169 Extracted method for building an OkHttpClient
This is intended for ml-app-deployer and is in the "extra" package, which is pseudo-public - it's public, but with a caveat that you'll need to bring your own OkHttp dependency to compile against it and it may not work in a future version.
1 parent 152b53d commit ca41df1

File tree

4 files changed

+142
-76
lines changed

4 files changed

+142
-76
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright (c) 2022 MarkLogic Corporation
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.marklogic.client.extra.okhttpclient;
17+
18+
import com.marklogic.client.DatabaseClientFactory;
19+
import com.marklogic.client.impl.okhttp.OkHttpUtil;
20+
import okhttp3.OkHttpClient;
21+
22+
/**
23+
* Exposes the mechanism for constructing an {@code OkHttpClient.Builder} in the same fashion as when a
24+
* {@code DatabaseClient} is constructed. Primarily intended for reuse in the ml-app-deployer library. If the
25+
* Java Client moves to a different HTTP client library, this will no longer work.
26+
*
27+
* @since 6.1.0
28+
*/
29+
public interface OkHttpClientBuilderFactory {
30+
31+
static OkHttpClient.Builder newOkHttpClientBuilder(String host, int port, DatabaseClientFactory.SecurityContext securityContext) {
32+
return OkHttpUtil.newOkHttpClientBuilder(host, port, securityContext);
33+
}
34+
}

marklogic-client-api/src/main/java/com/marklogic/client/impl/OkHttpServices.java

Lines changed: 9 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -188,67 +188,24 @@ private FailedRequest extractErrorFields(Response response) {
188188

189189
@Override
190190
public void connect(String host, int port, String basePath, String database, SecurityContext securityContext){
191-
192191
if (host == null)
193192
throw new IllegalArgumentException("No host provided");
194193
if (securityContext == null)
195194
throw new IllegalArgumentException("No security context provided");
196195

197-
OkHttpClient.Builder clientBuilder = OkHttpUtil.newClientBuilder();
198-
AuthenticationConfigurer authenticationConfigurer = null;
199-
200-
// As of 6.1.0, kerberos/saml/certificate are still coded within this class to avoid potential breaks from
201-
// refactoring. Once the tests for these auth methods are running properly, the code for each can be
202-
// safely refactored.
203-
if (securityContext instanceof BasicAuthContext) {
204-
authenticationConfigurer = new BasicAuthenticationConfigurer();
205-
checkFirstRequest = false;
206-
} else if (securityContext instanceof DigestAuthContext) {
207-
authenticationConfigurer = new DigestAuthenticationConfigurer();
208-
checkFirstRequest = true;
209-
} else if (securityContext instanceof KerberosAuthContext) {
210-
configureKerberosAuth((KerberosAuthContext) securityContext, host, clientBuilder);
211-
checkFirstRequest = false;
212-
} else if (securityContext instanceof CertificateAuthContext) {
213-
checkFirstRequest = false;
214-
} else if (securityContext instanceof SAMLAuthContext) {
215-
configureSAMLAuth((SAMLAuthContext) securityContext, clientBuilder);
216-
checkFirstRequest = false;
217-
} else if (securityContext instanceof MarkLogicCloudAuthContext) {
218-
authenticationConfigurer = new MarkLogicCloudAuthenticationConfigurer(host, port);
219-
checkFirstRequest = false;
220-
}
221-
else {
222-
throw new IllegalArgumentException("Unsupported security context: " + securityContext.getClass());
223-
}
196+
this.checkFirstRequest = securityContext instanceof DigestAuthContext;
197+
this.database = database;
198+
this.baseUri = HttpUrlBuilder.newBaseUrl(host, port, basePath, securityContext.getSSLContext());
224199

225-
if (authenticationConfigurer != null) {
226-
authenticationConfigurer.configureAuthentication(clientBuilder, securityContext);
227-
}
228-
229-
SSLContext sslContext = securityContext.getSSLContext();
230-
X509TrustManager trustManager = securityContext.getTrustManager();
200+
OkHttpClient.Builder clientBuilder = OkHttpUtil.newOkHttpClientBuilder(host, port, securityContext);
231201

232-
SSLHostnameVerifier sslVerifier = null;
233-
if (sslContext != null || securityContext instanceof CertificateAuthContext) {
234-
sslVerifier = securityContext.getSSLHostnameVerifier() != null ?
235-
securityContext.getSSLHostnameVerifier() :
236-
SSLHostnameVerifier.COMMON;
202+
Properties props = System.getProperties();
203+
if (props.containsKey(OKHTTP_LOGGINGINTERCEPTOR_LEVEL)) {
204+
configureOkHttpLogging(clientBuilder, props);
237205
}
206+
this.configureDelayAndRetry(props);
238207

239-
OkHttpUtil.configureSocketFactory(clientBuilder, sslContext, trustManager);
240-
OkHttpUtil.configureHostnameVerifier(clientBuilder, sslVerifier);
241-
242-
this.database = database;
243-
this.baseUri = HttpUrlBuilder.newBaseUrl(host, port, basePath, sslContext);
244-
245-
Properties props = System.getProperties();
246-
if (props.containsKey(OKHTTP_LOGGINGINTERCEPTOR_LEVEL)) {
247-
configureOkHttpLogging(clientBuilder, props);
248-
}
249-
this.configureDelayAndRetry(props);
250-
251-
this.client = clientBuilder.build();
208+
this.client = clientBuilder.build();
252209
}
253210

254211
/**
@@ -297,26 +254,6 @@ private void configureDelayAndRetry(Properties props) {
297254
}
298255
}
299256

300-
private void configureKerberosAuth(KerberosAuthContext keberosAuthContext, String host, OkHttpClient.Builder clientBuilder) {
301-
Map<String, String> kerberosOptions = keberosAuthContext.getKrbOptions();
302-
Interceptor interceptor = new HTTPKerberosAuthInterceptor(host, kerberosOptions);
303-
clientBuilder.addInterceptor(interceptor);
304-
}
305-
306-
private void configureSAMLAuth(SAMLAuthContext samlAuthContext, OkHttpClient.Builder clientBuilder) {
307-
Interceptor interceptor;
308-
String authorizationTokenValue = samlAuthContext.getToken();
309-
if(authorizationTokenValue != null && authorizationTokenValue.length() > 0) {
310-
interceptor = new HTTPSamlAuthInterceptor(authorizationTokenValue);
311-
} else if(samlAuthContext.getAuthorizer()!=null) {
312-
interceptor = new HTTPSamlAuthInterceptor(samlAuthContext.getAuthorizer());
313-
} else if(samlAuthContext.getRenewer()!=null) {
314-
interceptor = new HTTPSamlAuthInterceptor(samlAuthContext.getAuthorization(),samlAuthContext.getRenewer());
315-
} else
316-
throw new IllegalArgumentException("Either a call back or renewer expected.");
317-
clientBuilder.addInterceptor(interceptor);
318-
}
319-
320257
@Override
321258
public DatabaseClient getDatabaseClient() {
322259
return databaseClient;

marklogic-client-api/src/main/java/com/marklogic/client/impl/okhttp/OkHttpUtil.java

Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
package com.marklogic.client.impl.okhttp;
22

33
import com.marklogic.client.DatabaseClientFactory;
4+
import com.marklogic.client.impl.HTTPKerberosAuthInterceptor;
5+
import com.marklogic.client.impl.HTTPSamlAuthInterceptor;
46
import okhttp3.ConnectionPool;
57
import okhttp3.CookieJar;
68
import okhttp3.Dns;
9+
import okhttp3.Interceptor;
710
import okhttp3.OkHttpClient;
811

912
import javax.net.SocketFactory;
10-
import javax.net.ssl.*;
13+
import javax.net.ssl.HostnameVerifier;
14+
import javax.net.ssl.SSLContext;
15+
import javax.net.ssl.TrustManager;
16+
import javax.net.ssl.TrustManagerFactory;
17+
import javax.net.ssl.X509TrustManager;
1118
import java.net.Inet4Address;
1219
import java.net.InetAddress;
1320
import java.net.UnknownHostException;
@@ -17,6 +24,7 @@
1724
import java.security.NoSuchAlgorithmException;
1825
import java.util.ArrayList;
1926
import java.util.List;
27+
import java.util.Map;
2028
import java.util.concurrent.TimeUnit;
2129

2230
/**
@@ -28,11 +36,53 @@ public abstract class OkHttpUtil {
2836

2937
final private static ConnectionPool connectionPool = new ConnectionPool();
3038

39+
public static OkHttpClient.Builder newOkHttpClientBuilder(String host, int port, DatabaseClientFactory.SecurityContext securityContext) {
40+
OkHttpClient.Builder clientBuilder = OkHttpUtil.newClientBuilder();
41+
AuthenticationConfigurer authenticationConfigurer = null;
42+
43+
// As of 6.1.0, kerberos/saml/certificate are still coded within this class to avoid potential breaks from
44+
// refactoring. Once the tests for these auth methods are running properly, the code for each can be
45+
// safely refactored.
46+
if (securityContext instanceof DatabaseClientFactory.BasicAuthContext) {
47+
authenticationConfigurer = new BasicAuthenticationConfigurer();
48+
} else if (securityContext instanceof DatabaseClientFactory.DigestAuthContext) {
49+
authenticationConfigurer = new DigestAuthenticationConfigurer();
50+
} else if (securityContext instanceof DatabaseClientFactory.KerberosAuthContext) {
51+
configureKerberosAuth((DatabaseClientFactory.KerberosAuthContext) securityContext, host, clientBuilder);
52+
} else if (securityContext instanceof DatabaseClientFactory.CertificateAuthContext) {
53+
} else if (securityContext instanceof DatabaseClientFactory.SAMLAuthContext) {
54+
configureSAMLAuth((DatabaseClientFactory.SAMLAuthContext) securityContext, clientBuilder);
55+
} else if (securityContext instanceof DatabaseClientFactory.MarkLogicCloudAuthContext) {
56+
authenticationConfigurer = new MarkLogicCloudAuthenticationConfigurer(host, port);
57+
} else {
58+
throw new IllegalArgumentException("Unsupported security context: " + securityContext.getClass());
59+
}
60+
61+
if (authenticationConfigurer != null) {
62+
authenticationConfigurer.configureAuthentication(clientBuilder, securityContext);
63+
}
64+
65+
SSLContext sslContext = securityContext.getSSLContext();
66+
X509TrustManager trustManager = securityContext.getTrustManager();
67+
68+
DatabaseClientFactory.SSLHostnameVerifier sslVerifier = null;
69+
if (sslContext != null || securityContext instanceof DatabaseClientFactory.CertificateAuthContext) {
70+
sslVerifier = securityContext.getSSLHostnameVerifier() != null ?
71+
securityContext.getSSLHostnameVerifier() :
72+
DatabaseClientFactory.SSLHostnameVerifier.COMMON;
73+
}
74+
75+
OkHttpUtil.configureSocketFactory(clientBuilder, sslContext, trustManager);
76+
OkHttpUtil.configureHostnameVerifier(clientBuilder, sslVerifier);
77+
78+
return clientBuilder;
79+
}
80+
3181
/**
3282
* @return an OkHttpClient.Builder initialized with a sensible set of defaults that can then have authentication
3383
* configured
3484
*/
35-
public static OkHttpClient.Builder newClientBuilder() {
85+
static OkHttpClient.Builder newClientBuilder() {
3686
return new OkHttpClient.Builder()
3787
.followRedirects(false)
3888
.followSslRedirects(false)
@@ -47,13 +97,33 @@ public static OkHttpClient.Builder newClientBuilder() {
4797
.dns(new DnsImpl());
4898
}
4999

100+
private static void configureKerberosAuth(DatabaseClientFactory.KerberosAuthContext keberosAuthContext, String host, OkHttpClient.Builder clientBuilder) {
101+
Map<String, String> kerberosOptions = keberosAuthContext.getKrbOptions();
102+
Interceptor interceptor = new HTTPKerberosAuthInterceptor(host, kerberosOptions);
103+
clientBuilder.addInterceptor(interceptor);
104+
}
105+
106+
private static void configureSAMLAuth(DatabaseClientFactory.SAMLAuthContext samlAuthContext, OkHttpClient.Builder clientBuilder) {
107+
Interceptor interceptor;
108+
String authorizationTokenValue = samlAuthContext.getToken();
109+
if (authorizationTokenValue != null && authorizationTokenValue.length() > 0) {
110+
interceptor = new HTTPSamlAuthInterceptor(authorizationTokenValue);
111+
} else if (samlAuthContext.getAuthorizer() != null) {
112+
interceptor = new HTTPSamlAuthInterceptor(samlAuthContext.getAuthorizer());
113+
} else if (samlAuthContext.getRenewer() != null) {
114+
interceptor = new HTTPSamlAuthInterceptor(samlAuthContext.getAuthorization(), samlAuthContext.getRenewer());
115+
} else
116+
throw new IllegalArgumentException("Either a call back or renewer expected.");
117+
clientBuilder.addInterceptor(interceptor);
118+
}
119+
50120
/**
51121
* Configure the hostname verifier for the given OkHttpClient.Builder based on the given SSLHostnameVerifier.
52122
*
53123
* @param clientBuilder
54124
* @param sslVerifier
55125
*/
56-
public static void configureHostnameVerifier(OkHttpClient.Builder clientBuilder, DatabaseClientFactory.SSLHostnameVerifier sslVerifier) {
126+
private static void configureHostnameVerifier(OkHttpClient.Builder clientBuilder, DatabaseClientFactory.SSLHostnameVerifier sslVerifier) {
57127
HostnameVerifier hostnameVerifier = null;
58128
if (DatabaseClientFactory.SSLHostnameVerifier.ANY.equals(sslVerifier)) {
59129
hostnameVerifier = (hostname, session) -> true;
@@ -75,7 +145,7 @@ public static void configureHostnameVerifier(OkHttpClient.Builder clientBuilder,
75145
* @param sslContext
76146
* @param trustManager
77147
*/
78-
public static void configureSocketFactory(OkHttpClient.Builder clientBuilder, SSLContext sslContext, X509TrustManager trustManager) {
148+
private static void configureSocketFactory(OkHttpClient.Builder clientBuilder, SSLContext sslContext, X509TrustManager trustManager) {
79149
/**
80150
* Per https://square.github.io/okhttp/3.x/okhttp/okhttp3/OkHttpClient.Builder.html#sslSocketFactory-javax.net.ssl.SSLSocketFactory- ,
81151
* OkHttp requires a TrustManager to be specified so that it can build a clean certificate chain. If trustManager
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.marklogic.client.extra.okhttpclient;
2+
3+
import com.marklogic.client.DatabaseClientFactory;
4+
import com.marklogic.client.test.Common;
5+
import okhttp3.OkHttpClient;
6+
import org.junit.jupiter.api.Test;
7+
8+
import static org.junit.jupiter.api.Assertions.assertNotNull;
9+
10+
public class OkHttpClientBuilderFactoryTest {
11+
12+
@Test
13+
void smokeTest() {
14+
DatabaseClientFactory.Bean bean = Common.newClientBuilder().buildBean();
15+
OkHttpClient.Builder builder = OkHttpClientBuilderFactory.newOkHttpClientBuilder(
16+
bean.getHost(), bean.getPort(), bean.getSecurityContext());
17+
assertNotNull(builder);
18+
19+
OkHttpClient client = builder.build();
20+
assertNotNull(client, "This is simply verifying that the public/extra method doesn't throw an error. It's " +
21+
"expected to reuse the same approach that constructing a DatabaseClient does. And it's expected that " +
22+
"with ml-app-deployer 4.5.0 depending on this method for any calls to MarkLogic, tests will fail in that " +
23+
"project if there's ever an issue with this method");
24+
}
25+
}

0 commit comments

Comments
 (0)