Client: added support for OkHttp
git-svn-id: https://svn.apache.org/repos/asf/chemistry/opencmis/trunk@1752796 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/chemistry-opencmis-android/chemistry-opencmis-android-client/pom.xml b/chemistry-opencmis-android/chemistry-opencmis-android-client/pom.xml
index e6be565..395cca6 100644
--- a/chemistry-opencmis-android/chemistry-opencmis-android-client/pom.xml
+++ b/chemistry-opencmis-android/chemistry-opencmis-android-client/pom.xml
@@ -38,6 +38,12 @@
<version>2.3.3</version>
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>com.squareup.okhttp3</groupId>
+ <artifactId>okhttp</artifactId>
+ <version>${okhttp.version}</version>
+ <scope>provided</scope>
+ </dependency>
</dependencies>
<build>
diff --git a/chemistry-opencmis-android/chemistry-opencmis-android-client/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/http/DefaultHttpInvoker.java b/chemistry-opencmis-android/chemistry-opencmis-android-client/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/http/DefaultHttpInvoker.java
index c687ebd..f7e89ab 100644
--- a/chemistry-opencmis-android/chemistry-opencmis-android-client/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/http/DefaultHttpInvoker.java
+++ b/chemistry-opencmis-android/chemistry-opencmis-android-client/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/http/DefaultHttpInvoker.java
@@ -212,7 +212,7 @@
// get stream, if present
respCode = conn.getResponseCode();
InputStream inputStream = null;
- if ((respCode == 200) || (respCode == 201) || (respCode == 203) || (respCode == 206)) {
+ if (respCode == 200 || respCode == 201 || respCode == 203 || respCode == 206) {
inputStream = conn.getInputStream();
}
diff --git a/chemistry-opencmis-client/chemistry-opencmis-client-bindings/pom.xml b/chemistry-opencmis-client/chemistry-opencmis-client-bindings/pom.xml
index c5277d2..41c0666 100644
--- a/chemistry-opencmis-client/chemistry-opencmis-client-bindings/pom.xml
+++ b/chemistry-opencmis-client/chemistry-opencmis-client-bindings/pom.xml
@@ -104,7 +104,13 @@
<version>${apacheclient.version}</version>
<scope>provided</scope>
</dependency>
- <dependency>
+ <dependency>
+ <groupId>com.squareup.okhttp3</groupId>
+ <artifactId>okhttp</artifactId>
+ <version>${okhttp.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxws</artifactId>
<version>${cxf.version}</version>
diff --git a/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/AbstractAuthenticationProvider.java b/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/AbstractAuthenticationProvider.java
index 9e26147..82a91cb 100644
--- a/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/AbstractAuthenticationProvider.java
+++ b/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/AbstractAuthenticationProvider.java
@@ -23,6 +23,7 @@
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.X509TrustManager;
import javax.xml.ws.handler.HandlerResolver;
import org.apache.chemistry.opencmis.commons.SessionParameter;
@@ -82,6 +83,15 @@
}
/**
+ * Gets the trust manager corresponding to the SSL socket factory.
+ *
+ * @return a {@link X509TrustManager} or {@code null}
+ */
+ public X509TrustManager getTrustManager() {
+ return null;
+ }
+
+ /**
* Gets the user name from the session.
*
* @return the user name or {@code null} if the user name is not set
diff --git a/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/http/AbstractApacheClientHttpInvoker.java b/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/http/AbstractApacheClientHttpInvoker.java
index db15f53..b062d7a 100644
--- a/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/http/AbstractApacheClientHttpInvoker.java
+++ b/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/http/AbstractApacheClientHttpInvoker.java
@@ -276,7 +276,7 @@
InputStream inputStream = null;
InputStream errorStream = null;
- if ((respCode == 200) || (respCode == 201) || (respCode == 203) || (respCode == 206)) {
+ if (respCode == 200 || respCode == 201 || respCode == 203 || respCode == 206) {
if (entity != null) {
inputStream = entity.getContent();
} else {
diff --git a/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/http/DefaultHttpInvoker.java b/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/http/DefaultHttpInvoker.java
index b5b7c80..6df6fde 100644
--- a/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/http/DefaultHttpInvoker.java
+++ b/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/http/DefaultHttpInvoker.java
@@ -157,7 +157,7 @@
}
// range
- if ((offset != null) || (length != null)) {
+ if (offset != null || length != null) {
StringBuilder sb = new StringBuilder("bytes=");
if ((offset == null) || (offset.signum() == -1)) {
@@ -167,7 +167,7 @@
sb.append(offset.toString());
sb.append('-');
- if ((length != null) && (length.signum() == 1)) {
+ if (length != null && length.signum() == 1) {
sb.append(offset.add(length.subtract(BigInteger.ONE)).toString());
}
@@ -176,7 +176,7 @@
// compression
Object compression = session.get(SessionParameter.COMPRESSION);
- if ((compression != null) && Boolean.parseBoolean(compression.toString())) {
+ if (compression != null && Boolean.parseBoolean(compression.toString())) {
conn.setRequestProperty("Accept-Encoding", "gzip,deflate");
}
@@ -210,7 +210,7 @@
// get stream, if present
respCode = conn.getResponseCode();
InputStream inputStream = null;
- if ((respCode == 200) || (respCode == 201) || (respCode == 203) || (respCode == 206)) {
+ if (respCode == 200 || respCode == 201 || respCode == 203 || respCode == 206) {
inputStream = conn.getInputStream();
}
diff --git a/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/http/OkHttpHttpInvoker.java b/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/http/OkHttpHttpInvoker.java
new file mode 100644
index 0000000..91de63d
--- /dev/null
+++ b/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/http/OkHttpHttpInvoker.java
@@ -0,0 +1,304 @@
+/*
+ * 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.chemistry.opencmis.client.bindings.spi.http;
+
+import static org.apache.chemistry.opencmis.commons.impl.CollectionsHelper.isNotEmpty;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigInteger;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.X509TrustManager;
+
+import okhttp3.MediaType;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okio.BufferedSink;
+
+import org.apache.chemistry.opencmis.client.bindings.impl.ClientVersion;
+import org.apache.chemistry.opencmis.client.bindings.impl.CmisBindingsHelper;
+import org.apache.chemistry.opencmis.client.bindings.spi.AbstractAuthenticationProvider;
+import org.apache.chemistry.opencmis.client.bindings.spi.BindingSession;
+import org.apache.chemistry.opencmis.commons.SessionParameter;
+import org.apache.chemistry.opencmis.commons.exceptions.CmisConnectionException;
+import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException;
+import org.apache.chemistry.opencmis.commons.impl.UrlBuilder;
+import org.apache.chemistry.opencmis.commons.spi.AuthenticationProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class OkHttpHttpInvoker implements HttpInvoker {
+
+ private static final Logger LOG = LoggerFactory.getLogger(OkHttpHttpInvoker.class);
+
+ protected static final String HTTP_CLIENT = "org.apache.chemistry.opencmis.client.bindings.spi.http.OkHttpHttpInvoker.httpClient";
+
+ public OkHttpHttpInvoker() {
+ }
+
+ @Override
+ public Response invokeGET(UrlBuilder url, BindingSession session) {
+ return invoke(url, "GET", null, null, null, session, null, null);
+ }
+
+ @Override
+ public Response invokeGET(UrlBuilder url, BindingSession session, BigInteger offset, BigInteger length) {
+ return invoke(url, "GET", null, null, null, session, offset, length);
+ }
+
+ @Override
+ public Response invokePOST(UrlBuilder url, String contentType, Output writer, BindingSession session) {
+ return invoke(url, "POST", contentType, null, writer, session, null, null);
+ }
+
+ @Override
+ public Response invokePUT(UrlBuilder url, String contentType, Map<String, String> headers, Output writer,
+ BindingSession session) {
+ return invoke(url, "PUT", contentType, headers, writer, session, null, null);
+ }
+
+ @Override
+ public Response invokeDELETE(UrlBuilder url, BindingSession session) {
+ return invoke(url, "DELETE", null, null, null, session, null, null);
+ }
+
+ private Response invoke(UrlBuilder url, String method, final String contentType, Map<String, String> headers,
+ final Output writer, BindingSession session, BigInteger offset, BigInteger length) {
+ int respCode = -1;
+
+ try {
+ // log before connect
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Session {}: {} {}", session.getSessionId(), method, url);
+ }
+
+ // get HTTP client object from session
+ OkHttpClient httpclient = (OkHttpClient) session.get(HTTP_CLIENT);
+ if (httpclient == null) {
+ session.writeLock();
+ try {
+ httpclient = (OkHttpClient) session.get(HTTP_CLIENT);
+ if (httpclient == null) {
+ httpclient = createClientBuilder(session).build();
+ session.put(HTTP_CLIENT, httpclient, true);
+ }
+ } finally {
+ session.writeUnlock();
+ }
+ }
+
+ // set up the request
+ Request.Builder requestBuilder = new Request.Builder().url(url.toString());
+
+ // prepare the request body
+ RequestBody body = null;
+ if (writer != null) {
+ body = new RequestBody() {
+
+ @Override
+ public void writeTo(BufferedSink sink) throws IOException {
+ try {
+ writer.write(sink.outputStream());
+ } catch (IOException ioe) {
+ throw ioe;
+ } catch (Exception e) {
+ throw new IOException("Could not send stream to server: " + e.toString(), e);
+ }
+ }
+
+ @Override
+ public MediaType contentType() {
+ if (contentType != null) {
+ return MediaType.parse(contentType);
+ } else {
+ return MediaType.parse("application/octet-stream");
+ }
+ }
+ };
+ }
+
+ if ("GET".equals(method)) {
+ requestBuilder.get();
+ } else if ("POST".equals(method)) {
+ requestBuilder.post(body);
+ } else if ("PUT".equals(method)) {
+ requestBuilder.put(body);
+ } else if ("DELETE".equals(method)) {
+ requestBuilder.delete();
+ } else {
+ throw new CmisRuntimeException("Invalid HTTP method!");
+ }
+
+ // set content type
+ if (contentType != null) {
+ requestBuilder.header("Content-Type", contentType);
+ }
+ // set other headers
+ if (headers != null) {
+ for (Map.Entry<String, String> header : headers.entrySet()) {
+ requestBuilder.addHeader(header.getKey(), header.getValue());
+ }
+ }
+
+ requestBuilder.header("User-Agent",
+ (String) session.get(SessionParameter.USER_AGENT, ClientVersion.OPENCMIS_USER_AGENT));
+
+ // authenticate
+ AuthenticationProvider authProvider = CmisBindingsHelper.getAuthenticationProvider(session);
+ if (authProvider != null) {
+ Map<String, List<String>> httpHeaders = authProvider.getHTTPHeaders(url.toString());
+ if (httpHeaders != null) {
+ for (Map.Entry<String, List<String>> header : httpHeaders.entrySet()) {
+ if (header.getKey() != null && isNotEmpty(header.getValue())) {
+ String key = header.getKey();
+ if (key.equalsIgnoreCase("user-agent")) {
+ requestBuilder.header("User-Agent", header.getValue().get(0));
+ } else {
+ for (String value : header.getValue()) {
+ if (value != null) {
+ requestBuilder.addHeader(key, value);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // range
+ if (offset != null || length != null) {
+ StringBuilder sb = new StringBuilder("bytes=");
+
+ if ((offset == null) || (offset.signum() == -1)) {
+ offset = BigInteger.ZERO;
+ }
+
+ sb.append(offset.toString());
+ sb.append('-');
+
+ if (length != null && length.signum() == 1) {
+ sb.append(offset.add(length.subtract(BigInteger.ONE)).toString());
+ }
+
+ requestBuilder.header("Range", sb.toString());
+ }
+
+ // compression
+ Object compression = session.get(SessionParameter.COMPRESSION);
+ if (compression != null && Boolean.parseBoolean(compression.toString())) {
+ requestBuilder.header("Accept-Encoding", "gzip,deflate");
+ }
+
+ // locale
+ if (session.get(CmisBindingsHelper.ACCEPT_LANGUAGE) instanceof String) {
+ requestBuilder.header("Accept-Language", session.get(CmisBindingsHelper.ACCEPT_LANGUAGE).toString());
+ }
+
+ okhttp3.Response okResponse = httpclient.newCall(requestBuilder.build()).execute();
+
+ // get stream, if present
+ respCode = okResponse.code();
+ InputStream inputStream = null;
+ InputStream errorStream = null;
+
+ if (respCode == 200 || respCode == 201 || respCode == 203 || respCode == 206) {
+ inputStream = okResponse.body().byteStream();
+ } else {
+ errorStream = okResponse.body().byteStream();
+ }
+
+ Map<String, List<String>> responseHeaders = okResponse.headers().toMultimap();
+
+ // log after connect
+ if (LOG.isTraceEnabled()) {
+ LOG.trace("Session {}: {} {} > Headers: {}", session.getSessionId(), method, url,
+ responseHeaders.toString());
+ }
+
+ // forward response HTTP headers
+ if (authProvider != null) {
+ authProvider.putResponseHeaders(url.toString(), respCode, responseHeaders);
+ }
+
+ // get the response
+ return new Response(respCode, okResponse.message(), responseHeaders, inputStream, errorStream);
+ } catch (Exception e) {
+ String status = (respCode > 0 ? " (HTTP status code " + respCode + ")" : "");
+ throw new CmisConnectionException("Cannot access \"" + url + "\"" + status + ": " + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Creates a OkHttpClient.Builder and configures it.
+ *
+ * Subclasses can override this method to make use of OkHttp specific
+ * features.
+ *
+ * @param session
+ * the binding session
+ *
+ * @return the builder
+ */
+ @SuppressWarnings("deprecation")
+ protected OkHttpClient.Builder createClientBuilder(BindingSession session) {
+ OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
+
+ // timeouts
+ int connectTimeout = session.get(SessionParameter.CONNECT_TIMEOUT, -1);
+ if (connectTimeout >= 0) {
+ clientBuilder.connectTimeout(connectTimeout, TimeUnit.MILLISECONDS);
+ }
+
+ int readTimeout = session.get(SessionParameter.READ_TIMEOUT, -1);
+ if (readTimeout >= 0) {
+ clientBuilder.readTimeout(readTimeout, TimeUnit.MILLISECONDS);
+ }
+
+ AuthenticationProvider authProvider = CmisBindingsHelper.getAuthenticationProvider(session);
+ if (authProvider != null) {
+ SSLSocketFactory sf = authProvider.getSSLSocketFactory();
+ if (sf != null) {
+ X509TrustManager tm = null;
+
+ if (authProvider instanceof AbstractAuthenticationProvider) {
+ tm = ((AbstractAuthenticationProvider) authProvider).getTrustManager();
+ }
+
+ if (tm == null) {
+ clientBuilder.sslSocketFactory(sf);
+ } else {
+ clientBuilder.sslSocketFactory(sf, tm);
+ }
+ }
+
+ HostnameVerifier hv = authProvider.getHostnameVerifier();
+ if (hv != null) {
+ clientBuilder.hostnameVerifier(hv);
+ }
+ }
+
+ return clientBuilder;
+ }
+}
diff --git a/chemistry-opencmis-commons/chemistry-opencmis-commons-api/src/main/java/org/apache/chemistry/opencmis/commons/spi/AuthenticationProvider.java b/chemistry-opencmis-commons/chemistry-opencmis-commons-api/src/main/java/org/apache/chemistry/opencmis/commons/spi/AuthenticationProvider.java
index 1eb04e3..78022e3 100644
--- a/chemistry-opencmis-commons/chemistry-opencmis-commons-api/src/main/java/org/apache/chemistry/opencmis/commons/spi/AuthenticationProvider.java
+++ b/chemistry-opencmis-commons/chemistry-opencmis-commons-api/src/main/java/org/apache/chemistry/opencmis/commons/spi/AuthenticationProvider.java
@@ -41,7 +41,7 @@
* @param url
* the URL of the HTTP call
*
- * @return the HTTP headers or <code>null</code> if no additional headers
+ * @return the HTTP headers or {@code null} if no additional headers
* should be set
*/
Map<String, List<String>> getHTTPHeaders(String url);
@@ -52,7 +52,7 @@
* @param portObject
* the port object
*
- * @return the SOAP headers or <code>null</code> if no additional headers
+ * @return the SOAP headers or {@code null} if no additional headers
* should be set
*/
Element getSOAPHeaders(Object portObject);
@@ -61,7 +61,7 @@
* Returns a {@link HandlerResolver} object that provides a list of SOAP
* handlers.
*
- * @return the HandlerResolver or <code>null</code> if no handlers should be
+ * @return the HandlerResolver or {@code null} if no handlers should be
* set
*/
HandlerResolver getHandlerResolver();
@@ -70,14 +70,14 @@
* Returns the SSL Socket Factory to be used when creating sockets for HTTPS
* connections.
*
- * @return a {@link SSLSocketFactory} or <code>null</code>
+ * @return a {@link SSLSocketFactory} or {@code null}
*/
SSLSocketFactory getSSLSocketFactory();
/**
* Returns the Hostname Verifier for HTTPS connections.
*
- * @return a {@link HostnameVerifier} or <code>null</code>
+ * @return a {@link HostnameVerifier} or {@code null}
*/
HostnameVerifier getHostnameVerifier();
diff --git a/chemistry-opencmis-workbench/chemistry-opencmis-workbench/pom.xml b/chemistry-opencmis-workbench/chemistry-opencmis-workbench/pom.xml
index b6e8278..47cc247 100644
--- a/chemistry-opencmis-workbench/chemistry-opencmis-workbench/pom.xml
+++ b/chemistry-opencmis-workbench/chemistry-opencmis-workbench/pom.xml
@@ -278,6 +278,12 @@
<artifactId>cxf-rt-ws-policy</artifactId>
<version>${cxf.version}</version>
</dependency>
+ <dependency>
+ <groupId>com.squareup.okhttp3</groupId>
+ <artifactId>okhttp</artifactId>
+ <version>${okhttp.version}</version>
+ <scope>provided</scope>
+ </dependency>
</dependencies>
diff --git a/pom.xml b/pom.xml
index 14b504d..8a83cea 100644
--- a/pom.xml
+++ b/pom.xml
@@ -238,6 +238,7 @@
<log4j.version>2.5</log4j.version>
<apacheclient.version>4.2.6</apacheclient.version>
<cxf.version>3.0.9</cxf.version>
+ <okhttp.version>3.4.1</okhttp.version>
</properties>
<build>