001/** 002 * Copyright 2015 DuraSpace, Inc. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.fcrepo.client; 018 019import static org.fcrepo.client.FedoraHeaderConstants.CONTENT_DISPOSITION; 020import static org.fcrepo.client.FedoraHeaderConstants.CONTENT_TYPE; 021import static org.fcrepo.client.FedoraHeaderConstants.DESCRIBED_BY; 022import static org.fcrepo.client.FedoraHeaderConstants.LINK; 023import static org.fcrepo.client.FedoraHeaderConstants.LOCATION; 024 025import java.io.Closeable; 026import java.io.IOException; 027import java.io.InputStream; 028import java.net.URI; 029import java.util.ArrayList; 030import java.util.HashMap; 031import java.util.List; 032import java.util.Map; 033 034import org.apache.http.HeaderElement; 035import org.apache.http.NameValuePair; 036import org.apache.http.message.BasicHeader; 037 038/** 039 * Represents a response from a fedora repository using a {@link FcrepoClient}. 040 * <p> 041 * This class implements {@link Closeable}. Suggested usage is to create the {@code FcrepoResponse} within a 042 * try-with-resources block, insuring that any resources held by the response are freed automatically. 043 * </p> 044 * <pre> 045 * FcrepoClient client = ...; 046 * try (FcrepoResponse res = client.get(...)) { 047 * // do something with the response 048 * } catch (FcrepoOperationFailedException|IOException e) { 049 * // handle any exceptions 050 * } 051 * </pre> Closed responses have no obligation to provide access to released resources. 052 * 053 * @author Aaron Coburn 054 * @since October 20, 2014 055 */ 056public class FcrepoResponse implements Closeable { 057 058 private URI url; 059 060 private int statusCode; 061 062 private URI location; 063 064 private Map<String, List<String>> headers; 065 066 private Map<String, String> contentDisposition; 067 068 private InputStream body; 069 070 private String contentType; 071 072 private boolean closed = false; 073 074 /** 075 * Create a FcrepoResponse object from the http response 076 * 077 * @param url the requested URL 078 * @param statusCode the HTTP status code 079 * @param headers a map of all response header names and values 080 * @param body the response body stream 081 */ 082 public FcrepoResponse(final URI url, final int statusCode, final Map<String, List<String>> headers, 083 final InputStream body) { 084 this.setUrl(url); 085 this.setStatusCode(statusCode); 086 this.setHeaders(headers); 087 this.setBody(body); 088 } 089 090 /** 091 * {@inheritDoc} 092 * <p> 093 * Implementation note: Invoking this method will close the underlying {@code InputStream} containing the entity 094 * body of the HTTP response. 095 * </p> 096 * 097 * @throws IOException if there is an error closing the underlying HTTP response stream. 098 */ 099 @Override 100 public void close() throws IOException { 101 if (!this.closed && this.body != null) { 102 try { 103 this.body.close(); 104 } finally { 105 this.closed = true; 106 } 107 } 108 } 109 110 /** 111 * Whether or not the resources have been freed from this response. There should be no expectation that a closed 112 * response provides access to the {@link #getBody() entity body}. 113 * 114 * @return {@code true} if resources have been freed, otherwise {@code false} 115 */ 116 public boolean isClosed() { 117 return closed; 118 } 119 120 /** 121 * url getter 122 * 123 * @return the requested URL 124 */ 125 public URI getUrl() { 126 return url; 127 } 128 129 /** 130 * url setter 131 * 132 * @param url the requested URL 133 */ 134 public void setUrl(final URI url) { 135 this.url = url; 136 } 137 138 /** 139 * statusCode getter 140 * 141 * @return the HTTP status code 142 */ 143 public int getStatusCode() { 144 return statusCode; 145 } 146 147 /** 148 * statusCode setter 149 * 150 * @param statusCode the HTTP status code 151 */ 152 public void setStatusCode(final int statusCode) { 153 this.statusCode = statusCode; 154 } 155 156 /** 157 * body getter 158 * 159 * @return the response body as a stream 160 */ 161 public InputStream getBody() { 162 return body; 163 } 164 165 /** 166 * body setter 167 * 168 * @param body the contents of the response body 169 */ 170 public void setBody(final InputStream body) { 171 this.body = body; 172 } 173 174 /** 175 * headers getter 176 * 177 * @return headers from the response 178 */ 179 public Map<String, List<String>> getHeaders() { 180 return this.headers; 181 } 182 183 /** 184 * Get all values for the specified header 185 * 186 * @param name name of the header to retrieve 187 * @return All values of the specified header, or null if not present 188 */ 189 public List<String> getHeaderValues(final String name) { 190 if (headers == null) { 191 return null; 192 } 193 194 return headers.get(name); 195 } 196 197 /** 198 * Get the first value for the specified header 199 * 200 * @param name name of the header to retrieve 201 * @return First value of the header, or null if not present 202 */ 203 public String getHeaderValue(final String name) { 204 final List<String> values = getHeaderValues(name); 205 if (values == null || values.size() == 0) { 206 return null; 207 } 208 209 return values.get(0); 210 } 211 212 /** 213 * headers setter 214 * 215 * @param headers headers from the response 216 */ 217 public void setHeaders(final Map<String, List<String>> headers) { 218 this.headers = headers; 219 } 220 221 /** 222 * Retrieve link header values matching the given relationship 223 * 224 * @param relationship the relationship of links to return 225 * @return list of link header URIs matching the given relationship 226 */ 227 public List<URI> getLinkHeaders(final String relationship) { 228 final List<URI> uris = new ArrayList<URI>(); 229 final List<String> linkStrings = getHeaderValues(LINK); 230 if (linkStrings == null) { 231 return null; 232 } 233 234 for (String linkString : linkStrings) { 235 final FcrepoLink link = new FcrepoLink(linkString); 236 if (link.getRel().equals(relationship)) { 237 uris.add(link.getUri()); 238 } 239 } 240 241 return uris; 242 } 243 244 /** 245 * location getter 246 * 247 * @return the location of a related resource 248 */ 249 public URI getLocation() { 250 if (location == null && headers != null) { 251 // Retrieve the value from the location header if available 252 final String value = getHeaderValue(LOCATION); 253 if (value != null) { 254 location = URI.create(getHeaderValue(LOCATION)); 255 } 256 // Fall back to retrieving from the described by link 257 if (location == null) { 258 final List<URI> links = getLinkHeaders(DESCRIBED_BY); 259 if (links != null && links.size() == 1) { 260 location = links.get(0); 261 } 262 } 263 } 264 265 return location; 266 } 267 268 /** 269 * location setter 270 * 271 * @param location the value of a related resource 272 */ 273 public void setLocation(final URI location) { 274 this.location = location; 275 } 276 277 /** 278 * contentType getter 279 * 280 * @return the mime-type of response 281 */ 282 public String getContentType() { 283 if (contentType == null && headers != null) { 284 contentType = getHeaderValue(CONTENT_TYPE); 285 } 286 return contentType; 287 } 288 289 /** 290 * contentType setter 291 * 292 * @param contentType the mime-type of the response 293 */ 294 public void setContentType(final String contentType) { 295 this.contentType = contentType; 296 } 297 298 /** 299 * Get a map of parameters from the Content-Disposition header if present 300 * 301 * @return map of Content-Disposition parameters or null 302 */ 303 public Map<String, String> getContentDisposition() { 304 if (contentDisposition == null && headers.containsKey(CONTENT_DISPOSITION)) { 305 final List<String> values = headers.get(CONTENT_DISPOSITION); 306 if (values.isEmpty()) { 307 return null; 308 } 309 310 contentDisposition = new HashMap<>(); 311 final String value = values.get(0); 312 final BasicHeader header = new BasicHeader(CONTENT_DISPOSITION, value); 313 for (final HeaderElement headEl : header.getElements()) { 314 for (final NameValuePair pair : headEl.getParameters()) { 315 contentDisposition.put(pair.getName(), pair.getValue()); 316 } 317 } 318 } 319 return contentDisposition; 320 } 321}