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.isNullOrEmpty;
22  
23  import java.io.BufferedInputStream;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.io.InputStreamReader;
27  import java.math.BigInteger;
28  import java.util.HashMap;
29  import java.util.List;
30  import java.util.Locale;
31  import java.util.Map;
32  import java.util.zip.GZIPInputStream;
33  import java.util.zip.Inflater;
34  import java.util.zip.InflaterInputStream;
35  
36  import org.apache.chemistry.opencmis.commons.exceptions.CmisConnectionException;
37  import org.apache.chemistry.opencmis.commons.impl.Base64;
38  import org.apache.chemistry.opencmis.commons.impl.IOUtils;
39  
40  /**
41   * HTTP Response.
42   */
43  public class Response {
44      private static final int MAX_ERROR_LENGTH = 128 * 1024;
45  
46      private final int responseCode;
47      private final String responseMessage;
48      private final Map<String, List<String>> headers;
49      private InputStream stream;
50      private String errorContent;
51      private BigInteger length;
52      private String charset;
53      private boolean hasResponseStream;
54  
55      public Response(int responseCode, String responseMessage, Map<String, List<String>> headers,
56              InputStream responseStream, InputStream errorStream) {
57          this.responseCode = responseCode;
58          this.responseMessage = responseMessage;
59          this.stream = responseStream;
60          this.hasResponseStream = stream != null;
61          boolean isGZIP = responseStream instanceof GZIPInputStream;
62  
63          this.headers = new HashMap<String, List<String>>();
64          if (headers != null) {
65              for (Map.Entry<String, List<String>> e : headers.entrySet()) {
66                  this.headers.put(e.getKey() == null ? null : e.getKey().toLowerCase(Locale.ENGLISH), e.getValue());
67              }
68          }
69  
70          // determine charset
71          charset = IOUtils.UTF8;
72          String contentType = getContentTypeHeader();
73          if (contentType != null) {
74              String[] parts = contentType.split(";");
75              for (int i = 1; i < parts.length; i++) {
76                  String part = parts[i].trim().toLowerCase(Locale.ENGLISH);
77                  if (part.startsWith("charset")) {
78                      int x = part.indexOf('=');
79                      charset = part.substring(x + 1).trim();
80                      break;
81                  }
82              }
83          }
84  
85          // if there is an error page, get it
86          if (errorStream != null) {
87              if (contentType != null) {
88                  String contentTypeLower = contentType.toLowerCase(Locale.ENGLISH).split(";")[0];
89                  if (contentTypeLower.startsWith("text/") || contentTypeLower.endsWith("+xml")
90                          || contentTypeLower.startsWith("application/xml")
91                          || contentTypeLower.startsWith("application/json")) {
92                      errorStream = new BufferedInputStream(errorStream, 64 * 1024);
93                      StringBuilder sb = new StringBuilder(4096);
94  
95                      try {
96                          String encoding = getContentEncoding();
97                          if (encoding != null) {
98                              String encLower = encoding.trim().toLowerCase(Locale.ENGLISH);
99                              if (encLower.equals("gzip") && !isGZIP) {
100                                 errorStream = new GZIPInputStream(errorStream, 64 * 1024);
101                             } else if (encLower.equals("deflate")) {
102                                 errorStream = new InflaterInputStream(errorStream, new Inflater(true), 64 * 1024);
103                             }
104                         }
105 
106                         InputStreamReader reader = new InputStreamReader(errorStream, charset);
107                         char[] buffer = new char[4096];
108                         int b;
109                         while ((b = reader.read(buffer)) > -1) {
110                             sb.append(buffer, 0, b);
111                             if (sb.length() >= MAX_ERROR_LENGTH) {
112                                 break;
113                             }
114                         }
115                         reader.close();
116 
117                         errorContent = sb.toString();
118                     } catch (IOException e) {
119                         errorContent = "Unable to retrieve content: " + e.getMessage();
120                     }
121                 }
122             } else {
123                 IOUtils.closeQuietly(errorStream);
124             }
125 
126             IOUtils.closeQuietly(responseStream);
127 
128             return;
129         }
130 
131         // get the stream length
132         length = null;
133         String lengthStr = getHeader("Content-Length");
134         if (lengthStr != null && !isGZIP) {
135             try {
136                 length = new BigInteger(lengthStr);
137             } catch (NumberFormatException e) {
138                 // content-length is not a number -> ignore
139             }
140         }
141 
142         if (stream == null || BigInteger.ZERO.equals(length) || responseCode == 204) {
143             hasResponseStream = false;
144         } else {
145             stream = new BufferedInputStream(stream, 64 * 1024);
146             try {
147                 hasResponseStream = IOUtils.checkForBytes(stream);
148             } catch (IOException ioe) {
149                 throw new CmisConnectionException("IO exception!", ioe);
150             }
151 
152             if (hasResponseStream) {
153                 String encoding = getContentEncoding();
154                 if (encoding != null) {
155                     String encLower = encoding.trim().toLowerCase(Locale.ENGLISH);
156                     if (encLower.equals("gzip") && !isGZIP) {
157                         // if the stream is gzip encoded, decode it
158                         length = null;
159                         try {
160                             stream = new GZIPInputStream(stream, 64 * 1024);
161                         } catch (IOException e) {
162                             errorContent = e.getMessage();
163                             stream = null;
164                             IOUtils.closeQuietly(responseStream);
165                         }
166                     } else if (encLower.equals("deflate")) {
167                         // if the stream is deflate encoded, decode it
168                         length = null;
169                         stream = new InflaterInputStream(stream, new Inflater(true), 64 * 1024);
170                     }
171                 }
172 
173                 String transferEncoding = getContentTransferEncoding();
174                 if (transferEncoding != null && transferEncoding.trim().toLowerCase(Locale.ENGLISH).equals("base64")) {
175                     // if the stream is base64 encoded, decode it
176                     length = null;
177                     stream = new Base64.InputStream(stream);
178                 }
179             }
180         }
181     }
182 
183     public int getResponseCode() {
184         return responseCode;
185     }
186 
187     public String getResponseMessage() {
188         return responseMessage;
189     }
190 
191     public Map<String, List<String>> getHeaders() {
192         return headers;
193     }
194 
195     public String getHeader(String name) {
196         List<String> list = headers.get(name.toLowerCase(Locale.US));
197         if (isNullOrEmpty(list)) {
198             return null;
199         }
200 
201         return list.get(0);
202     }
203 
204     public String getContentTypeHeader() {
205         return getHeader("Content-Type");
206     }
207 
208     public BigInteger getContentLengthHeader() {
209         String lengthStr = getHeader("Content-Length");
210         if (lengthStr == null) {
211             return null;
212         }
213 
214         try {
215             return new BigInteger(lengthStr);
216         } catch (NumberFormatException e) {
217             return null;
218         }
219     }
220 
221     public String getLocactionHeader() {
222         return getHeader("Location");
223     }
224 
225     public String getContentLocactionHeader() {
226         return getHeader("Content-Location");
227     }
228 
229     public String getContentTransferEncoding() {
230         return getHeader("Content-Transfer-Encoding");
231     }
232 
233     public String getContentEncoding() {
234         return getHeader("Content-Encoding");
235     }
236 
237     public String getContentDisposition() {
238         return getHeader("Content-Disposition");
239     }
240 
241     public String getCharset() {
242         return charset;
243     }
244 
245     public BigInteger getContentLength() {
246         return length;
247     }
248 
249     public boolean hasResponseStream() {
250         return hasResponseStream;
251     }
252 
253     public InputStream getStream() {
254         return stream;
255     }
256 
257     public String getErrorContent() {
258         return errorContent;
259     }
260 }