1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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.BufferedOutputStream;
24 import java.io.ByteArrayInputStream;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.io.OutputStream;
28 import java.math.BigInteger;
29 import java.net.Socket;
30 import java.util.ArrayList;
31 import java.util.HashMap;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.zip.GZIPOutputStream;
35
36 import javax.net.ssl.HostnameVerifier;
37 import javax.net.ssl.SSLException;
38 import javax.net.ssl.SSLSocket;
39
40 import org.apache.chemistry.opencmis.client.bindings.impl.ClientVersion;
41 import org.apache.chemistry.opencmis.client.bindings.impl.CmisBindingsHelper;
42 import org.apache.chemistry.opencmis.client.bindings.spi.BindingSession;
43 import org.apache.chemistry.opencmis.commons.SessionParameter;
44 import org.apache.chemistry.opencmis.commons.exceptions.CmisConnectionException;
45 import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException;
46 import org.apache.chemistry.opencmis.commons.impl.IOUtils;
47 import org.apache.chemistry.opencmis.commons.impl.UrlBuilder;
48 import org.apache.chemistry.opencmis.commons.spi.AuthenticationProvider;
49 import org.apache.http.Header;
50 import org.apache.http.HttpEntity;
51 import org.apache.http.HttpResponse;
52 import org.apache.http.HttpVersion;
53 import org.apache.http.client.HttpClient;
54 import org.apache.http.client.methods.HttpDelete;
55 import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
56 import org.apache.http.client.methods.HttpGet;
57 import org.apache.http.client.methods.HttpPost;
58 import org.apache.http.client.methods.HttpPut;
59 import org.apache.http.client.methods.HttpRequestBase;
60 import org.apache.http.conn.ssl.X509HostnameVerifier;
61 import org.apache.http.entity.AbstractHttpEntity;
62 import org.apache.http.impl.client.DefaultHttpClient;
63 import org.apache.http.params.BasicHttpParams;
64 import org.apache.http.params.HttpConnectionParams;
65 import org.apache.http.params.HttpParams;
66 import org.apache.http.params.HttpProtocolParams;
67 import org.slf4j.Logger;
68 import org.slf4j.LoggerFactory;
69
70
71
72
73 public abstract class AbstractApacheClientHttpInvoker implements HttpInvoker {
74
75 protected static final Logger LOG = LoggerFactory.getLogger(AbstractApacheClientHttpInvoker.class);
76
77 protected static final String HTTP_CLIENT = "org.apache.chemistry.opencmis.client.bindings.spi.http.ApacheClientHttpInvoker.httpClient";
78 protected static final int BUFFER_SIZE = 2 * 1024 * 1024;
79
80 @Override
81 public Response invokeGET(UrlBuilder url, BindingSession session) {
82 return invoke(url, "GET", null, null, null, session, null, null);
83 }
84
85 @Override
86 public Response invokeGET(UrlBuilder url, BindingSession session, BigInteger offset, BigInteger length) {
87 return invoke(url, "GET", null, null, null, session, offset, length);
88 }
89
90 @Override
91 public Response invokePOST(UrlBuilder url, String contentType, Output writer, BindingSession session) {
92 return invoke(url, "POST", contentType, null, writer, session, null, null);
93 }
94
95 @Override
96 public Response invokePUT(UrlBuilder url, String contentType, Map<String, String> headers, Output writer,
97 BindingSession session) {
98 return invoke(url, "PUT", contentType, headers, writer, session, null, null);
99 }
100
101 @Override
102 public Response invokeDELETE(UrlBuilder url, BindingSession session) {
103 return invoke(url, "DELETE", null, null, null, session, null, null);
104 }
105
106 protected Response invoke(UrlBuilder url, String method, String contentType, Map<String, String> headers,
107 final Output writer, final BindingSession session, BigInteger offset, BigInteger length) {
108 int respCode = -1;
109
110 try {
111
112 if (LOG.isDebugEnabled()) {
113 LOG.debug("Session {}: {} {}", session.getSessionId(), method, url);
114 }
115
116
117 DefaultHttpClient httpclient = (DefaultHttpClient) session.get(HTTP_CLIENT);
118 if (httpclient == null) {
119 session.writeLock();
120 try {
121 httpclient = (DefaultHttpClient) session.get(HTTP_CLIENT);
122 if (httpclient == null) {
123 httpclient = createHttpClient(url, session);
124 session.put(HTTP_CLIENT, httpclient, true);
125 }
126 } finally {
127 session.writeUnlock();
128 }
129 }
130
131 HttpRequestBase request = null;
132
133 if ("GET".equals(method)) {
134 request = new HttpGet(url.toString());
135 } else if ("POST".equals(method)) {
136 request = new HttpPost(url.toString());
137 } else if ("PUT".equals(method)) {
138 request = new HttpPut(url.toString());
139 } else if ("DELETE".equals(method)) {
140 request = new HttpDelete(url.toString());
141 } else {
142 throw new CmisRuntimeException("Invalid HTTP method!");
143 }
144
145
146 if (contentType != null) {
147 request.setHeader("Content-Type", contentType);
148 }
149
150 if (headers != null) {
151 for (Map.Entry<String, String> header : headers.entrySet()) {
152 request.addHeader(header.getKey(), header.getValue());
153 }
154 }
155
156
157 AuthenticationProvider authProvider = CmisBindingsHelper.getAuthenticationProvider(session);
158 if (authProvider != null) {
159 Map<String, List<String>> httpHeaders = authProvider.getHTTPHeaders(url.toString());
160 if (httpHeaders != null) {
161 for (Map.Entry<String, List<String>> header : httpHeaders.entrySet()) {
162 if (header.getKey() != null && isNotEmpty(header.getValue())) {
163 String key = header.getKey();
164 if (key.equalsIgnoreCase("user-agent")) {
165 request.setHeader("User-Agent", header.getValue().get(0));
166 } else {
167 for (String value : header.getValue()) {
168 if (value != null) {
169 request.addHeader(key, value);
170 }
171 }
172 }
173 }
174 }
175 }
176 }
177
178
179 if ((offset != null) || (length != null)) {
180 StringBuilder sb = new StringBuilder("bytes=");
181
182 if ((offset == null) || (offset.signum() == -1)) {
183 offset = BigInteger.ZERO;
184 }
185
186 sb.append(offset.toString());
187 sb.append('-');
188
189 if ((length != null) && (length.signum() == 1)) {
190 sb.append(offset.add(length.subtract(BigInteger.ONE)).toString());
191 }
192
193 request.setHeader("Range", sb.toString());
194 }
195
196
197 Object compression = session.get(SessionParameter.COMPRESSION);
198 if ((compression != null) && Boolean.parseBoolean(compression.toString())) {
199 request.setHeader("Accept-Encoding", "gzip,deflate");
200 }
201
202
203 if (session.get(CmisBindingsHelper.ACCEPT_LANGUAGE) instanceof String) {
204 request.setHeader("Accept-Language", session.get(CmisBindingsHelper.ACCEPT_LANGUAGE).toString());
205 }
206
207
208 if (writer != null) {
209 Object clientCompression = session.get(SessionParameter.CLIENT_COMPRESSION);
210 final boolean clientCompressionFlag = (clientCompression != null)
211 && Boolean.parseBoolean(clientCompression.toString());
212 if (clientCompressionFlag) {
213 request.setHeader("Content-Encoding", "gzip");
214 }
215
216 AbstractHttpEntity streamEntity = new AbstractHttpEntity() {
217 @Override
218 public boolean isChunked() {
219 return true;
220 }
221
222 @Override
223 public boolean isRepeatable() {
224 return false;
225 }
226
227 @Override
228 public long getContentLength() {
229 return -1;
230 }
231
232 @Override
233 public boolean isStreaming() {
234 return false;
235 }
236
237 @Override
238 public InputStream getContent() throws IOException {
239 throw new UnsupportedOperationException();
240 }
241
242 @Override
243 public void writeTo(final OutputStream outstream) throws IOException {
244 OutputStream connOut = null;
245
246 if (clientCompressionFlag) {
247 connOut = new GZIPOutputStream(outstream, 4096);
248 } else {
249 connOut = outstream;
250 }
251
252 OutputStream out = new BufferedOutputStream(connOut, BUFFER_SIZE);
253 try {
254 writer.write(out);
255 } catch (IOException ioe) {
256 throw ioe;
257 } catch (Exception e) {
258 throw new IOException(e);
259 }
260 out.flush();
261
262 if (connOut instanceof GZIPOutputStream) {
263 ((GZIPOutputStream) connOut).finish();
264 }
265 }
266 };
267 ((HttpEntityEnclosingRequestBase) request).setEntity(streamEntity);
268 }
269
270
271 HttpResponse response = httpclient.execute(request);
272 HttpEntity entity = response.getEntity();
273
274
275 respCode = response.getStatusLine().getStatusCode();
276 InputStream inputStream = null;
277 InputStream errorStream = null;
278
279 if (respCode == 200 || respCode == 201 || respCode == 203 || respCode == 206) {
280 if (entity != null) {
281 inputStream = entity.getContent();
282 } else {
283 inputStream = new ByteArrayInputStream(new byte[0]);
284 }
285 } else {
286 if (entity != null) {
287 errorStream = entity.getContent();
288 } else {
289 errorStream = new ByteArrayInputStream(new byte[0]);
290 }
291 }
292
293
294 Map<String, List<String>> responseHeaders = new HashMap<String, List<String>>();
295 for (Header header : response.getAllHeaders()) {
296 List<String> values = responseHeaders.get(header.getName());
297 if (values == null) {
298 values = new ArrayList<String>();
299 responseHeaders.put(header.getName(), values);
300 }
301 values.add(header.getValue());
302 }
303
304
305 if (LOG.isTraceEnabled()) {
306 LOG.trace("Session {}: {} {} > Headers: {}", session.getSessionId(), method, url,
307 responseHeaders.toString());
308 }
309
310
311 if (authProvider != null) {
312 authProvider.putResponseHeaders(url.toString(), respCode, responseHeaders);
313 }
314
315
316 return new Response(respCode, response.getStatusLine().getReasonPhrase(), responseHeaders, inputStream,
317 errorStream);
318 } catch (Exception e) {
319 throw new CmisConnectionException(url.toString(), respCode, e);
320 }
321 }
322
323
324
325
326 protected HttpParams createDefaultHttpParams(BindingSession session) {
327 HttpParams params = new BasicHttpParams();
328
329 HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
330 HttpProtocolParams.setUserAgent(params,
331 (String) session.get(SessionParameter.USER_AGENT, ClientVersion.OPENCMIS_USER_AGENT));
332 HttpProtocolParams.setContentCharset(params, IOUtils.UTF8);
333 HttpProtocolParams.setUseExpectContinue(params, true);
334
335 HttpConnectionParams.setStaleCheckingEnabled(params, true);
336
337 int connectTimeout = session.get(SessionParameter.CONNECT_TIMEOUT, -1);
338 if (connectTimeout >= 0) {
339 HttpConnectionParams.setConnectionTimeout(params, connectTimeout);
340 }
341
342 int readTimeout = session.get(SessionParameter.READ_TIMEOUT, -1);
343 if (readTimeout >= 0) {
344 HttpConnectionParams.setSoTimeout(params, readTimeout);
345 }
346
347 return params;
348 }
349
350
351
352
353 protected void verify(HostnameVerifier verifier, String host, SSLSocket sslSocket) throws IOException {
354 try {
355 if (verifier instanceof X509HostnameVerifier) {
356 ((X509HostnameVerifier) verifier).verify(host, sslSocket);
357 } else {
358 if (!verifier.verify(host, sslSocket.getSession())) {
359 throw new SSLException("Hostname in certificate didn't match: <" + host + ">");
360 }
361 }
362 } catch (IOException ioe) {
363 closeSocket(sslSocket);
364 throw ioe;
365 }
366 }
367
368
369
370
371 protected void closeSocket(Socket socket) {
372 try {
373 socket.close();
374 } catch (IOException ioe) {
375
376 }
377 }
378
379
380
381
382 protected abstract DefaultHttpClient createHttpClient(UrlBuilder url, BindingSession session);
383 }