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