View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   * http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.chemistry.opencmis.client.bindings.spi.http;
20  
21  import static org.apache.chemistry.opencmis.commons.impl.CollectionsHelper.isNotEmpty;
22  
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.io.OutputStream;
26  import java.math.BigInteger;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.concurrent.TimeUnit;
30  
31  import javax.net.ssl.HostnameVerifier;
32  import javax.net.ssl.SSLSocketFactory;
33  import javax.net.ssl.X509TrustManager;
34  
35  import org.apache.chemistry.opencmis.client.bindings.impl.ClientVersion;
36  import org.apache.chemistry.opencmis.client.bindings.impl.CmisBindingsHelper;
37  import org.apache.chemistry.opencmis.client.bindings.spi.AbstractAuthenticationProvider;
38  import org.apache.chemistry.opencmis.client.bindings.spi.BindingSession;
39  import org.apache.chemistry.opencmis.commons.SessionParameter;
40  import org.apache.chemistry.opencmis.commons.exceptions.CmisConnectionException;
41  import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException;
42  import org.apache.chemistry.opencmis.commons.impl.UrlBuilder;
43  import org.apache.chemistry.opencmis.commons.spi.AuthenticationProvider;
44  import org.slf4j.Logger;
45  import org.slf4j.LoggerFactory;
46  
47  import okhttp3.MediaType;
48  import okhttp3.OkHttpClient;
49  import okhttp3.Request;
50  import okhttp3.RequestBody;
51  import okio.BufferedSink;
52  
53  public class OkHttpHttpInvoker implements HttpInvoker {
54  
55      private static final Logger LOG = LoggerFactory.getLogger(OkHttpHttpInvoker.class);
56  
57      protected static final String HTTP_CLIENT = "org.apache.chemistry.opencmis.client.bindings.spi.http.OkHttpHttpInvoker.httpClient";
58  
59      public OkHttpHttpInvoker() {
60      }
61  
62      @Override
63      public Response invokeGET(UrlBuilder url, BindingSession session) {
64          return invoke(url, "GET", null, null, null, session, null, null);
65      }
66  
67      @Override
68      public Response invokeGET(UrlBuilder url, BindingSession session, BigInteger offset, BigInteger length) {
69          return invoke(url, "GET", null, null, null, session, offset, length);
70      }
71  
72      @Override
73      public Response invokePOST(UrlBuilder url, String contentType, Output writer, BindingSession session) {
74          return invoke(url, "POST", contentType, null, writer, session, null, null);
75      }
76  
77      @Override
78      public Response invokePUT(UrlBuilder url, String contentType, Map<String, String> headers, Output writer,
79              BindingSession session) {
80          return invoke(url, "PUT", contentType, headers, writer, session, null, null);
81      }
82  
83      @Override
84      public Response invokeDELETE(UrlBuilder url, BindingSession session) {
85          return invoke(url, "DELETE", null, null, null, session, null, null);
86      }
87  
88      private Response invoke(UrlBuilder url, String method, final String contentType, Map<String, String> headers,
89              final Output writer, BindingSession session, BigInteger offset, BigInteger length) {
90          int respCode = -1;
91  
92          try {
93              // log before connect
94              if (LOG.isDebugEnabled()) {
95                  LOG.debug("Session {}: {} {}", session.getSessionId(), method, url);
96              }
97  
98              // get HTTP client object from session
99              OkHttpClient httpclient = (OkHttpClient) session.get(HTTP_CLIENT);
100             if (httpclient == null) {
101                 session.writeLock();
102                 try {
103                     httpclient = (OkHttpClient) session.get(HTTP_CLIENT);
104                     if (httpclient == null) {
105                         httpclient = createClientBuilder(session).build();
106                         session.put(HTTP_CLIENT, httpclient, true);
107                     }
108                 } finally {
109                     session.writeUnlock();
110                 }
111             }
112 
113             // set up the request
114             Request.Builder requestBuilder = new Request.Builder().url(url.toString());
115 
116             // prepare the request body
117             RequestBody body = null;
118             if (writer != null) {
119                 body = new RequestBody() {
120 
121                     @Override
122                     public void writeTo(BufferedSink sink) throws IOException {
123                         try {
124                             OutputStream out = sink.outputStream();
125                             writer.write(out);
126                             out.flush();
127                         } catch (IOException ioe) {
128                             throw ioe;
129                         } catch (Exception e) {
130                             throw new IOException("Could not send stream to server: " + e.toString(), e);
131                         }
132                     }
133 
134                     @Override
135                     public MediaType contentType() {
136                         if (contentType != null) {
137                             return MediaType.parse(contentType);
138                         } else {
139                             return MediaType.parse("application/octet-stream");
140                         }
141                     }
142                 };
143             }
144 
145             if ("GET".equals(method)) {
146                 requestBuilder.get();
147             } else if ("POST".equals(method)) {
148                 requestBuilder.post(body);
149             } else if ("PUT".equals(method)) {
150                 requestBuilder.put(body);
151             } else if ("DELETE".equals(method)) {
152                 requestBuilder.delete();
153             } else {
154                 throw new CmisRuntimeException("Invalid HTTP method!");
155             }
156 
157             // set content type
158             if (contentType != null) {
159                 requestBuilder.header("Content-Type", contentType);
160             }
161             // set other headers
162             if (headers != null) {
163                 for (Map.Entry<String, String> header : headers.entrySet()) {
164                     requestBuilder.addHeader(header.getKey(), header.getValue());
165                 }
166             }
167 
168             requestBuilder.header("User-Agent",
169                     (String) session.get(SessionParameter.USER_AGENT, ClientVersion.OPENCMIS_USER_AGENT));
170 
171             // authenticate
172             AuthenticationProvider authProvider = CmisBindingsHelper.getAuthenticationProvider(session);
173             if (authProvider != null) {
174                 Map<String, List<String>> httpHeaders = authProvider.getHTTPHeaders(url.toString());
175                 if (httpHeaders != null) {
176                     for (Map.Entry<String, List<String>> header : httpHeaders.entrySet()) {
177                         if (header.getKey() != null && isNotEmpty(header.getValue())) {
178                             String key = header.getKey();
179                             if (key.equalsIgnoreCase("user-agent")) {
180                                 requestBuilder.header("User-Agent", header.getValue().get(0));
181                             } else {
182                                 for (String value : header.getValue()) {
183                                     if (value != null) {
184                                         requestBuilder.addHeader(key, value);
185                                     }
186                                 }
187                             }
188                         }
189                     }
190                 }
191             }
192 
193             // range
194             if (offset != null || length != null) {
195                 StringBuilder sb = new StringBuilder("bytes=");
196 
197                 if ((offset == null) || (offset.signum() == -1)) {
198                     offset = BigInteger.ZERO;
199                 }
200 
201                 sb.append(offset.toString());
202                 sb.append('-');
203 
204                 if (length != null && length.signum() == 1) {
205                     sb.append(offset.add(length.subtract(BigInteger.ONE)).toString());
206                 }
207 
208                 requestBuilder.header("Range", sb.toString());
209             }
210 
211             // compression
212             Object compression = session.get(SessionParameter.COMPRESSION);
213             if (compression != null && Boolean.parseBoolean(compression.toString())) {
214                 requestBuilder.header("Accept-Encoding", "gzip,deflate");
215             }
216 
217             // locale
218             if (session.get(CmisBindingsHelper.ACCEPT_LANGUAGE) instanceof String) {
219                 requestBuilder.header("Accept-Language", session.get(CmisBindingsHelper.ACCEPT_LANGUAGE).toString());
220             }
221 
222             okhttp3.Response okResponse = httpclient.newCall(requestBuilder.build()).execute();
223 
224             // get stream, if present
225             respCode = okResponse.code();
226             InputStream inputStream = null;
227             InputStream errorStream = null;
228 
229             if (respCode == 200 || respCode == 201 || respCode == 203 || respCode == 206) {
230                 inputStream = okResponse.body().byteStream();
231             } else {
232                 errorStream = okResponse.body().byteStream();
233             }
234 
235             Map<String, List<String>> responseHeaders = okResponse.headers().toMultimap();
236 
237             // log after connect
238             if (LOG.isTraceEnabled()) {
239                 LOG.trace("Session {}: {} {} > Headers: {}", session.getSessionId(), method, url,
240                         responseHeaders.toString());
241             }
242 
243             // forward response HTTP headers
244             if (authProvider != null) {
245                 authProvider.putResponseHeaders(url.toString(), respCode, responseHeaders);
246             }
247 
248             // get the response
249             return new Response(respCode, okResponse.message(), responseHeaders, inputStream, errorStream);
250         } catch (Exception e) {
251             throw new CmisConnectionException(url.toString(), respCode, e);
252         }
253     }
254 
255     /**
256      * Creates a OkHttpClient.Builder and configures it.
257      * 
258      * Subclasses can override this method to make use of OkHttp specific
259      * features.
260      * 
261      * @param session
262      *            the binding session
263      * 
264      * @return the builder
265      */
266     @SuppressWarnings("deprecation")
267     protected OkHttpClient.Builder createClientBuilder(BindingSession session) {
268         OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
269 
270         // timeouts
271         int connectTimeout = session.get(SessionParameter.CONNECT_TIMEOUT, -1);
272         if (connectTimeout >= 0) {
273             clientBuilder.connectTimeout(connectTimeout, TimeUnit.MILLISECONDS);
274         }
275 
276         int readTimeout = session.get(SessionParameter.READ_TIMEOUT, -1);
277         if (readTimeout >= 0) {
278             clientBuilder.readTimeout(readTimeout, TimeUnit.MILLISECONDS);
279         }
280 
281         AuthenticationProvider authProvider = CmisBindingsHelper.getAuthenticationProvider(session);
282         if (authProvider != null) {
283             SSLSocketFactory sf = authProvider.getSSLSocketFactory();
284             if (sf != null) {
285                 X509TrustManager tm = null;
286 
287                 if (authProvider instanceof AbstractAuthenticationProvider) {
288                     tm = ((AbstractAuthenticationProvider) authProvider).getTrustManager();
289                 }
290 
291                 if (tm == null) {
292                     clientBuilder.sslSocketFactory(sf);
293                 } else {
294                     clientBuilder.sslSocketFactory(sf, tm);
295                 }
296             }
297 
298             HostnameVerifier hv = authProvider.getHostnameVerifier();
299             if (hv != null) {
300                 clientBuilder.hostnameVerifier(hv);
301             }
302         }
303 
304         return clientBuilder;
305     }
306 }