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 java.util.Collections.emptyList; 020import static java.util.stream.Collectors.toList; 021import static org.fcrepo.client.FedoraHeaderConstants.CONTENT_DISPOSITION; 022import static org.fcrepo.client.FedoraHeaderConstants.CONTENT_TYPE; 023import static org.fcrepo.client.FedoraHeaderConstants.DESCRIBED_BY; 024import static org.fcrepo.client.FedoraHeaderConstants.LINK; 025import static org.fcrepo.client.FedoraHeaderConstants.LOCATION; 026 027import java.io.Closeable; 028import java.io.IOException; 029import java.io.InputStream; 030import java.net.URI; 031import java.util.HashMap; 032import java.util.List; 033import java.util.Map; 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 188 */ 189 public List<String> getHeaderValues(final String name) { 190 return headers == null ? emptyList() : headers.getOrDefault(name, emptyList()); 191 } 192 193 /** 194 * Get the first value for the specified header 195 * 196 * @param name name of the header to retrieve 197 * @return First value of the header, or null if not present 198 */ 199 public String getHeaderValue(final String name) { 200 final List<String> values = getHeaderValues(name); 201 if (values == null || values.size() == 0) { 202 return null; 203 } 204 205 return values.get(0); 206 } 207 208 /** 209 * headers setter 210 * 211 * @param headers headers from the response 212 */ 213 public void setHeaders(final Map<String, List<String>> headers) { 214 this.headers = headers; 215 } 216 217 /** 218 * Retrieve link header values matching the given relationship 219 * 220 * @param relationship the relationship of links to return 221 * @return list of link header URIs matching the given relationship 222 */ 223 public List<URI> getLinkHeaders(final String relationship) { 224 return getHeaderValues(LINK).stream().map(FcrepoLink::new).filter(link -> link.getRel().equals(relationship)) 225 .map(FcrepoLink::getUri).collect(toList()); 226 } 227 228 /** 229 * location getter 230 * 231 * @return the location of a related resource 232 */ 233 public URI getLocation() { 234 if (location == null && headers != null) { 235 // Retrieve the value from the location header if available 236 final String value = getHeaderValue(LOCATION); 237 if (value != null) { 238 location = URI.create(getHeaderValue(LOCATION)); 239 } 240 // Fall back to retrieving from the described by link 241 if (location == null) { 242 final List<URI> links = getLinkHeaders(DESCRIBED_BY); 243 if (links != null && links.size() == 1) { 244 location = links.get(0); 245 } 246 } 247 } 248 249 return location; 250 } 251 252 /** 253 * location setter 254 * 255 * @param location the value of a related resource 256 */ 257 public void setLocation(final URI location) { 258 this.location = location; 259 } 260 261 /** 262 * contentType getter 263 * 264 * @return the mime-type of response 265 */ 266 public String getContentType() { 267 if (contentType == null && headers != null) { 268 contentType = getHeaderValue(CONTENT_TYPE); 269 } 270 return contentType; 271 } 272 273 /** 274 * contentType setter 275 * 276 * @param contentType the mime-type of the response 277 */ 278 public void setContentType(final String contentType) { 279 this.contentType = contentType; 280 } 281 282 /** 283 * Get a map of parameters from the Content-Disposition header if present 284 * 285 * @return map of Content-Disposition parameters or null 286 */ 287 public Map<String, String> getContentDisposition() { 288 if (contentDisposition == null && headers.containsKey(CONTENT_DISPOSITION)) { 289 final List<String> values = headers.get(CONTENT_DISPOSITION); 290 if (values.isEmpty()) { 291 return null; 292 } 293 294 contentDisposition = new HashMap<>(); 295 final String value = values.get(0); 296 final BasicHeader header = new BasicHeader(CONTENT_DISPOSITION, value); 297 for (final HeaderElement headEl : header.getElements()) { 298 for (final NameValuePair pair : headEl.getParameters()) { 299 contentDisposition.put(pair.getName(), pair.getValue()); 300 } 301 } 302 } 303 return contentDisposition; 304 } 305}