8585import com .fasterxml .jackson .databind .node .ArrayNode ;
8686import com .fasterxml .jackson .databind .node .ObjectNode ;
8787
88+ import okhttp3 .ConnectionPool ;
8889import okhttp3 .Cookie ;
90+ import okhttp3 .CookieJar ;
8991import okhttp3 .Headers ;
9092import okhttp3 .HttpUrl ;
9193import okhttp3 .MediaType ;
145147import java .util .List ;
146148import java .util .Map ;
147149import java .util .NoSuchElementException ;
150+ import java .util .Properties ;
148151import java .util .Random ;
149152import java .util .Set ;
150153import java .util .UUID ;
156159public class OkHttpServices implements RESTServices {
157160 static final private Logger logger = LoggerFactory .getLogger (OkHttpServices .class );
158161
162+ static final public String OKHTTP_LOGGINGINTERCEPTOR_LEVEL = "com.marklogic.client.okhttp.httplogginginterceptor.level" ;
163+ static final public String OKHTTP_LOGGINGINTERCEPTOR_OUTPUT = "com.marklogic.client.okhttp.httplogginginterceptor.output" ;
164+
159165 static final private String DOCUMENT_URI_PREFIX = "/documents?uri=" ;
160166
161167 static final private int DELAY_FLOOR = 125 ;
@@ -188,6 +194,8 @@ public void verify(String hostname, X509Certificate cert) throws SSLException {
188194 }
189195 }
190196
197+ static final private ConnectionPool connectionPool = new ConnectionPool ();
198+
191199 private DatabaseClient databaseClient ;
192200 private String database = null ;
193201 private HttpUrl baseUri ;
@@ -208,7 +216,6 @@ static protected class ThreadState {
208216 }
209217 }
210218
211- // workaround: Jersey keeps the DIGEST nonce in a thread-local variable
212219 private final ThreadLocal <ThreadState > threadState = new ThreadLocal <ThreadState >() {
213220 @ Override
214221 protected ThreadState initialValue () {
@@ -244,14 +251,10 @@ private FailedRequest extractErrorFields(Response response) {
244251
245252 @ Override
246253 public void connect (String host , int port , String database , String user , String password ,
247- Authentication authenType , SSLContext context ,
254+ Authentication authenType , SSLContext sslContext ,
248255 SSLHostnameVerifier verifier ) {
249256 HostnameVerifier hostnameVerifier = null ;
250- if (verifier == null ) {
251- if (context != null ) {
252- hostnameVerifier = null ;
253- }
254- } else if (verifier == SSLHostnameVerifier .ANY ) {
257+ if (verifier == SSLHostnameVerifier .ANY ) {
255258 hostnameVerifier = new HostnameVerifier () {
256259 @ Override
257260 public boolean verify (String hostname , SSLSession session ) {
@@ -261,42 +264,27 @@ public boolean verify(String hostname, SSLSession session) {
261264 } else if (verifier == SSLHostnameVerifier .COMMON ) {
262265 hostnameVerifier = null ;
263266 } else if (verifier == SSLHostnameVerifier .STRICT ) {
264- // TODO: implement SSLHostnameVerifier.STRICT for OkHttp
265- throw new IllegalStateException ("Not yet implemented" );
266- } else if (context != null ) {
267+ hostnameVerifier = null ;
268+ } else if (verifier != null ) {
267269 hostnameVerifier = new HostnameVerifierAdapter (verifier );
268- } else {
269- throw new IllegalArgumentException (
270- "Null SSLContext but non-null SSLHostnameVerifier for client" );
271- }
272- connect (host , port , database , user , password , authenType , context , hostnameVerifier );
270+ }// else {
271+ // throw new IllegalArgumentException(
272+ // "Null SSLContext but non-null SSLHostnameVerifier for client");
273+ // }
274+ connect (host , port , database , user , password , authenType , sslContext , hostnameVerifier );
273275 }
274276
275277 private void connect (String host , int port , String database , String user , String password ,
276- Authentication authenType , SSLContext context ,
278+ Authentication authenType , SSLContext sslContext ,
277279 HostnameVerifier verifier ) {
278280 logger .debug ("Connecting to {} at {} as {}" , new Object []{host , port , user });
279281
280282 if (host == null ) throw new IllegalArgumentException ("No host provided" );
281283
282- if (authenType == null ) {
283- if (context != null ) {
284- authenType = Authentication .BASIC ;
285- }
286- }
287-
288- if (authenType != null ) {
289- if (user == null ) throw new IllegalArgumentException ("No user provided" );
290- if (password == null ) throw new IllegalArgumentException ("No password provided" );
291- if (authenType == Authentication .BASIC ) {
292- checkFirstRequest = false ;
293- }
294- }
295-
296284 this .database = database ;
297285
298286 this .baseUri = new HttpUrl .Builder ()
299- .scheme (context == null ? "http" : "https" )
287+ .scheme (sslContext == null ? "http" : "https" )
300288 .host (host )
301289 .port (port )
302290 .encodedPath ("/v1/ping" )
@@ -307,46 +295,72 @@ private void connect(String host, int port, String database, String user, String
307295 final BasicAuthenticator basicAuthenticator = new BasicAuthenticator (credentials );
308296 final DigestAuthenticator digestAuthenticator = new DigestAuthenticator (credentials );
309297
298+ if ( authenType == null && sslContext != null ) {
299+ authenType = Authentication .BASIC ;
300+ }
301+
310302 DispatchingAuthenticator .Builder authenticator = new DispatchingAuthenticator .Builder ();
311- if (authenType == Authentication .BASIC ) {
312- authenticator = authenticator .with ("basic" , basicAuthenticator );
313- } else if (authenType == Authentication .DIGEST ) {
314- authenticator = authenticator .with ("digest" , digestAuthenticator );
303+ if (authenType == null ) {
304+ checkFirstRequest = false ;
305+ } else {
306+ if (user == null ) throw new IllegalArgumentException ("No user provided" );
307+ if (password == null ) throw new IllegalArgumentException ("No password provided" );
308+ if (authenType == Authentication .BASIC ) {
309+ authenticator = authenticator .with ("basic" , basicAuthenticator );
310+ checkFirstRequest = false ;
311+ } else if (authenType == Authentication .DIGEST ) {
312+ authenticator = authenticator .with ("digest" , digestAuthenticator );
313+ checkFirstRequest = true ;
314+ } else {
315+ throw new MarkLogicInternalException (
316+ "Internal error - unknown authentication type: " + authenType .name ());
317+ }
315318 }
316319
317- this . client = new OkHttpClient .Builder ()
320+ OkHttpClient . Builder clientBldr = new OkHttpClient .Builder ()
318321 .authenticator (new CachingAuthenticatorDecorator (authenticator .build (), authCache ))
319322 .addInterceptor (new AuthenticationCacheInterceptor (authCache ))
320- /*
321- .addNetworkInterceptor(
322- new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
323- public void log(String message) {
324- logger.debug(message);
325- }
326- })
327- .setLevel(HttpLoggingInterceptor.Level.HEADERS)
328- )
329- */
330323 .followRedirects (false )
331324 .followSslRedirects (false )
332- .build ();
325+ // all clients share a single connection pool
326+ .connectionPool (connectionPool )
327+ // cookies are ignored (except when a Transaction is being used)
328+ .cookieJar (CookieJar .NO_COOKIES );
333329
334- /*
335- if (connection != null) connection = null;
336- if (connMgr != null) {
337- connMgr.shutdown();
338- connMgr = null;
339- }
340- if (client != null) {
341- client.destroy();
342- client = null;
330+ if ( verifier != null ) {
331+ clientBldr = clientBldr .hostnameVerifier (verifier );
343332 }
344333
345-
346- String baseUri = ((context == null) ? "http" : "https") + "://" + host + ":" + port + "/v1/";
347-
348334 Properties props = System .getProperties ();
349335
336+ if (props .containsKey (OKHTTP_LOGGINGINTERCEPTOR_LEVEL )) {
337+ final boolean useLogger = "LOGGER" .equalsIgnoreCase (props .getProperty (OKHTTP_LOGGINGINTERCEPTOR_OUTPUT ));
338+ final boolean useStdErr = "STDERR" .equalsIgnoreCase (props .getProperty (OKHTTP_LOGGINGINTERCEPTOR_OUTPUT ));
339+ HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor (
340+ new HttpLoggingInterceptor .Logger () {
341+ public void log (String message ) {
342+ if ( useLogger == true ) {
343+ logger .debug (message );
344+ } else if ( useStdErr == true ) {
345+ System .err .println (message );
346+ } else {
347+ System .out .println (message );
348+ }
349+ }
350+ }
351+ );
352+ if ( "BASIC" .equalsIgnoreCase (props .getProperty (OKHTTP_LOGGINGINTERCEPTOR_LEVEL )) ) {
353+ interceptor = interceptor .setLevel (HttpLoggingInterceptor .Level .BASIC );
354+ } else if ( "BODY" .equalsIgnoreCase (props .getProperty (OKHTTP_LOGGINGINTERCEPTOR_LEVEL )) ) {
355+ interceptor = interceptor .setLevel (HttpLoggingInterceptor .Level .BODY );
356+ } else if ( "HEADERS" .equalsIgnoreCase (props .getProperty (OKHTTP_LOGGINGINTERCEPTOR_LEVEL )) ) {
357+ interceptor = interceptor .setLevel (HttpLoggingInterceptor .Level .HEADERS );
358+ } else if ( "NONE" .equalsIgnoreCase (props .getProperty (OKHTTP_LOGGINGINTERCEPTOR_LEVEL )) ) {
359+ interceptor = interceptor .setLevel (HttpLoggingInterceptor .Level .NONE );
360+ }
361+ clientBldr = clientBldr .addNetworkInterceptor (interceptor );
362+ }
363+
350364 if (props .containsKey (MAX_DELAY_PROP )) {
351365 String maxDelayStr = props .getProperty (MAX_DELAY_PROP );
352366 if (maxDelayStr != null && maxDelayStr .length () > 0 ) {
@@ -366,123 +380,12 @@ public void log(String message) {
366380 }
367381 }
368382
369- // TODO: integrated control of HTTP Client and Jersey Client logging
370- if (!props.containsKey("org.apache.commons.logging.Log")) {
371- System.setProperty("org.apache.commons.logging.Log",
372- "org.apache.commons.logging.impl.SimpleLog");
373- }
374- if (!props.containsKey("org.apache.commons.logging.simplelog.log.org.apache.http")) {
375- System.setProperty(
376- "org.apache.commons.logging.simplelog.log.org.apache.http",
377- "warn");
378- }
379- if (!props.containsKey("org.apache.commons.logging.simplelog.log.org.apache.http.wire")) {
380- System.setProperty(
381- "org.apache.commons.logging.simplelog.log.org.apache.http.wire",
382- "warn");
383- }
384-
385- Scheme scheme = null;
386- if (context == null) {
387- SchemeSocketFactory socketFactory = PlainSocketFactory
388- .getSocketFactory();
389- scheme = new Scheme("http", port, socketFactory);
390- } else {
391- SSLSocketFactory socketFactory = new SSLSocketFactory(context,
392- verifier);
393- scheme = new Scheme("https", port, socketFactory);
394- }
395- SchemeRegistry schemeRegistry = new SchemeRegistry();
396- schemeRegistry.register(scheme);
397-
398- int maxRouteConnections = 100;
399- int maxTotalConnections = 2 * maxRouteConnections;
400-
401- // start 4.1
402- connMgr = new ThreadSafeClientConnManager(
403- schemeRegistry);
404- connMgr.setMaxTotal(maxTotalConnections);
405- connMgr.setDefaultMaxPerRoute(maxRouteConnections);
406- connMgr.setMaxForRoute(new HttpRoute(new HttpHost(baseUri)),
407- maxRouteConnections);
408- // end 4.1
409-
410- // CredentialsProvider credentialsProvider = new
411- // BasicCredentialsProvider();
412- // credentialsProvider.setCredentials(new AuthScope(host, port),
413- // new UsernamePasswordCredentials(user, password));
414-
415- HttpParams httpParams = new BasicHttpParams();
416-
417- if (authenType != null) {
418- List<String> authpref = new ArrayList<String>();
419- if (authenType == Authentication.BASIC)
420- authpref.add(AuthPolicy.BASIC);
421- else if (authenType == Authentication.DIGEST)
422- authpref.add(AuthPolicy.DIGEST);
423- else
424- throw new MarkLogicInternalException(
425- "Internal error - unknown authentication type: "
426- + authenType.name());
427- httpParams.setParameter(AuthPNames.PROXY_AUTH_PREF, authpref);
428- }
429-
430- httpParams.setBooleanParameter(ClientPNames.HANDLE_REDIRECTS, false);
431-
432- HttpProtocolParams.setVersion(httpParams, HttpVersion.HTTP_1_1);
433-
434- // HttpConnectionParams.setStaleCheckingEnabled(httpParams, false);
435-
383+ this .client = clientBldr .build ();
384+ // System.setProperty("javax.net.debug", "all"); // all or ssl
385+ /*
436386 // long-term alternative to isFirstRequest alive
437387 // HttpProtocolParams.setUseExpectContinue(httpParams, false);
438388 // httpParams.setIntParameter(CoreProtocolPNames.WAIT_FOR_CONTINUE, 1000);
439-
440- DefaultApacheHttpClient4Config config = new DefaultApacheHttpClient4Config();
441- Map<String, Object> configProps = config.getProperties();
442- configProps
443- .put(ApacheHttpClient4Config.PROPERTY_PREEMPTIVE_BASIC_AUTHENTICATION,
444- false);
445- configProps.put(ApacheHttpClient4Config.PROPERTY_DISABLE_COOKIES, true);
446- configProps.put(ApacheHttpClient4Config.PROPERTY_CONNECTION_MANAGER,
447- connMgr);
448- // ignored?
449- configProps.put(ApacheHttpClient4Config.PROPERTY_FOLLOW_REDIRECTS,
450- false);
451- // configProps.put(ApacheHttpClient4Config.PROPERTY_CREDENTIALS_PROVIDER,
452- // credentialsProvider);
453- configProps.put(ApacheHttpClient4Config.PROPERTY_HTTP_PARAMS,
454- httpParams);
455- // switches from buffered to streamed in Jersey Client
456- configProps.put(ApacheHttpClient4Config.PROPERTY_CHUNKED_ENCODING_SIZE,
457- 32 * 1024);
458-
459- client = ApacheHttpClient4.create(config);
460-
461- // System.setProperty("javax.net.debug", "all"); // all or ssl
462-
463- if (authenType == null) {
464- checkFirstRequest = false;
465- } else if (authenType == Authentication.BASIC) {
466- checkFirstRequest = false;
467-
468- client.addFilter(new HTTPBasicAuthFilter(user, password));
469- } else if (authenType == Authentication.DIGEST) {
470- checkFirstRequest = true;
471-
472- // workaround for JerseyClient bug 1445
473- client.addFilter(new DigestChallengeFilter());
474-
475- client.addFilter(new HTTPDigestAuthFilter(user, password));
476- }
477- else {
478- throw new MarkLogicInternalException(
479- "Internal error - unknown authentication type: "
480- + authenType.name());
481- }
482-
483- // client.addFilter(new LoggingFilter(System.err));
484-
485- connection = client.resource(baseUri);
486389 */
487390 }
488391
@@ -500,7 +403,7 @@ private OkHttpClient getConnection() {
500403 return client ;
501404 } else if ( released ) {
502405 throw new IllegalStateException (
503- "You cannot use this connected object anymore--connection has already been released" );
406+ "You cannot use this connected object anymore--connection has already been released" );
504407 } else {
505408 throw new MarkLogicInternalException ("Cannot proceed--connection is null for unknown reason" );
506409 }
@@ -510,19 +413,15 @@ private OkHttpClient getConnection() {
510413 public void release () {
511414 try {
512415 released = true ;
513- client .dispatcher ().executorService ().shutdown ();
416+ client .dispatcher ().executorService ().shutdownNow ();
514417 } finally {
515418 try {
516- client .connectionPool ().evictAll ();
419+ if ( client .cache () != null ) client .cache ().close ();
420+ } catch (IOException e ) {
421+ throw new MarkLogicIOException (e );
517422 } finally {
518- try {
519- if ( client .cache () != null ) client .cache ().close ();
520- } catch (IOException e ) {
521- throw new MarkLogicIOException (e );
522- } finally {
523- client = null ;
524- logger .debug ("Releasing connection" );
525- }
423+ client = null ;
424+ logger .debug ("Releasing connection" );
526425 }
527426 }
528427 }
0 commit comments