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.net.URI.create; 021import static java.nio.charset.StandardCharsets.UTF_8; 022import static org.fcrepo.client.FedoraHeaderConstants.CONTENT_DISPOSITION; 023import static org.fcrepo.client.FedoraHeaderConstants.CONTENT_DISPOSITION_FILENAME; 024import static org.fcrepo.client.FedoraHeaderConstants.CONTENT_DISPOSITION_MODIFICATION_DATE; 025import static org.fcrepo.client.FedoraHeaderConstants.CONTENT_DISPOSITION_SIZE; 026import static org.fcrepo.client.FedoraHeaderConstants.CONTENT_TYPE; 027import static org.fcrepo.client.FedoraHeaderConstants.DESCRIBED_BY; 028import static org.fcrepo.client.FedoraHeaderConstants.LINK; 029import static org.fcrepo.client.FedoraHeaderConstants.LOCATION; 030import static org.junit.Assert.assertEquals; 031import static org.junit.Assert.assertFalse; 032import static org.junit.Assert.assertSame; 033import static org.junit.Assert.assertTrue; 034import static org.junit.Assert.fail; 035import static org.mockito.Matchers.any; 036import static org.mockito.Mockito.doThrow; 037import static org.mockito.Mockito.mock; 038import static org.mockito.Mockito.times; 039import static org.mockito.Mockito.verify; 040import static org.mockito.Mockito.when; 041 042import java.io.ByteArrayInputStream; 043import java.io.IOException; 044import java.io.InputStream; 045import java.net.URI; 046import java.util.Arrays; 047import java.util.HashMap; 048import java.util.List; 049import java.util.Map; 050 051import org.apache.commons.io.IOUtils; 052import org.apache.commons.io.output.NullOutputStream; 053import org.junit.Test; 054import org.junit.runner.RunWith; 055import org.mockito.runners.MockitoJUnitRunner; 056 057import com.google.common.io.ByteStreams; 058 059/** 060 * @author ajs6f 061 */ 062@RunWith(MockitoJUnitRunner.class) 063public class FcrepoResponseTest { 064 065 @Test 066 public void testResponse() throws IOException { 067 final URI uri = create("http://localhost/path/a/b"); 068 final int status = 200; 069 final Map<String, List<String>> headers = new HashMap<>(); 070 final String contentType = "text/plain"; 071 headers.put(CONTENT_TYPE, Arrays.asList(contentType)); 072 final String location = "http://localhost/path/a/b/c"; 073 headers.put(LOCATION, Arrays.asList(location)); 074 final String body = "Text response"; 075 final InputStream bodyStream = new ByteArrayInputStream(body.getBytes(UTF_8)); 076 final FcrepoResponse response = new FcrepoResponse(uri, status, headers, bodyStream); 077 078 assertEquals(response.getUrl(), uri); 079 assertEquals(response.getStatusCode(), status); 080 assertEquals(response.getContentType(), contentType); 081 assertEquals(response.getLocation(), create(location)); 082 assertEquals(IOUtils.toString(response.getBody(), UTF_8), body); 083 084 response.setUrl(create("http://example.org/path/a/b")); 085 assertEquals(response.getUrl(), create("http://example.org/path/a/b")); 086 087 response.setStatusCode(301); 088 assertEquals(response.getStatusCode(), 301); 089 090 response.setContentType("application/n-triples"); 091 assertEquals(response.getContentType(), "application/n-triples"); 092 093 response.setLocation(create("http://example.org/path/a/b/c")); 094 assertEquals(response.getLocation(), create("http://example.org/path/a/b/c")); 095 096 response.setBody(new ByteArrayInputStream( 097 "<http://example.org/book/3> <dc:title> \"Title\" .".getBytes(UTF_8))); 098 assertEquals(IOUtils.toString(response.getBody(), UTF_8), 099 "<http://example.org/book/3> <dc:title> \"Title\" ."); 100 response.close(); 101 } 102 103 /** 104 * Demonstrates that response objects are <em>not</em> {@code close()}ed by default, that the state of 105 * {@link FcrepoResponse#closed} is set appropriately when {@link FcrepoResponse#close()} is invoked under normal 106 * (i.e. no exception thrown during {@code close()}) conditions, and that {@link InputStream#close()} is not 107 * invoked repeatedly after the {@code FcrepoResponse} has been {@code close()}ed. 108 * 109 * @throws IOException if something exceptional happens 110 */ 111 @Test 112 public void testClosableReleasesResources() throws IOException { 113 final InputStream mockBody = mock(InputStream.class); 114 final Map<String, List<String>> headers = new HashMap<>(); 115 final String contentType = "text/plain"; 116 headers.put(CONTENT_TYPE, Arrays.asList(contentType)); 117 final String location = "http://localhost/bar"; 118 headers.put(LOCATION, Arrays.asList(location)); 119 final FcrepoResponse underTest = new FcrepoResponse( 120 URI.create("http://localhost/foo"), 201, headers, mockBody); 121 122 assertFalse("FcrepoResponse objects should not be closed until close() is invoked.", underTest.isClosed()); 123 124 underTest.close(); 125 assertTrue(underTest.isClosed()); 126 verify(mockBody, times(1)).close(); 127 128 underTest.close(); 129 assertTrue(underTest.isClosed()); 130 verify(mockBody, times(1)).close(); 131 } 132 133 /** 134 * Demonstrates that if an {@code IOException} is thrown by {@link FcrepoResponse#close()}, <em>and</em> an 135 * exception is thrown inside of a client's {@code try} block, the {@code IOException} from the {@code close()} 136 * method is properly appended as a suppressed exception. 137 * 138 * @throws IOException if something exceptional happens 139 */ 140 @Test 141 public void testClosableSuppressedExceptions() throws IOException { 142 final InputStream mockBody = mock(InputStream.class); 143 final IOException notSuppressed = new IOException("Not suppressed."); 144 final IOException suppressed = new IOException("Suppressed"); 145 doThrow(suppressed).when(mockBody).close(); 146 147 final Map<String, List<String>> headers = new HashMap<>(); 148 final String contentType = "text/plain"; 149 headers.put(CONTENT_TYPE, Arrays.asList(contentType)); 150 final String location = "http://localhost/bar"; 151 headers.put(LOCATION, Arrays.asList(location)); 152 153 try (FcrepoResponse underTest = new FcrepoResponse(URI.create("http://localhost/foo"), 201, 154 headers, mockBody)) { 155 assertFalse(underTest.isClosed()); 156 157 throw notSuppressed; 158 159 } catch (Exception e) { 160 assertSame(notSuppressed, e); 161 assertTrue(e.getSuppressed() != null && e.getSuppressed().length == 1); 162 assertSame(suppressed, e.getSuppressed()[0]); 163 } 164 165 verify(mockBody).close(); 166 } 167 168 /** 169 * Demonstrates a successful idiomatic usage with try-with-resources 170 * 171 * @throws FcrepoOperationFailedException if something exceptional happens 172 */ 173 @Test 174 public void testIdiomaticInvokation() throws FcrepoOperationFailedException { 175 final String content = "Hello World!"; 176 final ByteArrayInputStream entityBody = new ByteArrayInputStream(content.getBytes()); 177 final FcrepoClient client = mock(FcrepoClient.class); 178 final GetBuilder getBuilder = mock(GetBuilder.class); 179 180 when(client.get(any(URI.class))).thenReturn(getBuilder); 181 when(getBuilder.perform()).thenReturn(new FcrepoResponse(null, 200, null, entityBody)); 182 183 try (FcrepoResponse res = client.get(URI.create("foo")).perform()) { 184 assertEquals(content, IOUtils.toString(res.getBody())); 185 } catch (IOException e) { 186 fail("Unexpected exception: " + e); 187 } 188 } 189 190 /** 191 * Demonstrates idiomatic exception handling with try-with-resources 192 * 193 * @throws Exception if something exceptional happens 194 */ 195 @Test 196 public void testIdiomaticInvokationThrowsException() throws Exception { 197 final InputStream mockBody = mock(InputStream.class); 198 final IOException ioe = new IOException("Mocked IOE"); 199 when(mockBody.read(any(byte[].class))).thenThrow(ioe); 200 201 final FcrepoClient client = mock(FcrepoClient.class); 202 final GetBuilder getBuilder = mock(GetBuilder.class); 203 204 when(client.get(any(URI.class))).thenReturn(getBuilder); 205 when(getBuilder.perform()).thenReturn(new FcrepoResponse(null, 200, null, mockBody)); 206 207 try (FcrepoResponse res = client.get(URI.create("foo")).perform()) { 208 ByteStreams.copy(res.getBody(), NullOutputStream.NULL_OUTPUT_STREAM); 209 fail("Expected an IOException to be thrown."); 210 } catch (IOException e) { 211 assertSame(ioe, e); 212 } 213 214 verify(mockBody).close(); 215 } 216 217 @Test 218 public void testLocationFromDescribedBy() throws Exception { 219 final Map<String, List<String>> headers = new HashMap<>(); 220 final String contentType = "text/plain"; 221 headers.put(CONTENT_TYPE, Arrays.asList(contentType)); 222 final String describedBy = "http://localhost/bar/file/fcr:metadata"; 223 headers.put(LINK, Arrays.asList( 224 "<http://www.w3.org/ns/ldp#Resource>;rel=\"type\"", 225 "<" + describedBy + ">; rel=\"describedby\"")); 226 227 try (FcrepoResponse response = new FcrepoResponse(URI.create("http://localhost/foo"), 201, 228 headers, null)) { 229 assertEquals(create(describedBy), response.getLocation()); 230 } 231 } 232 233 @Test 234 public void testLocationOverDescribedBy() throws Exception { 235 final Map<String, List<String>> headers = new HashMap<>(); 236 final String contentType = "text/plain"; 237 headers.put(CONTENT_TYPE, Arrays.asList(contentType)); 238 final String location = "http://localhost/bar/file"; 239 headers.put(LOCATION, Arrays.asList(location)); 240 final String describedBy = "http://localhost/bar/file/fcr:metadata"; 241 headers.put(LINK, Arrays.asList( 242 "<http://www.w3.org/ns/ldp#Resource>;rel=\"type\"", 243 "<" + describedBy + ">; rel=\"describedby\"")); 244 245 try (FcrepoResponse response = new FcrepoResponse(URI.create("http://localhost/foo"), 201, 246 headers, null)) { 247 assertEquals(create(location), response.getLocation()); 248 assertEquals(describedBy, response.getLinkHeaders(DESCRIBED_BY).get(0).toString()); 249 } 250 } 251 252 @Test 253 public void testContentDisposition() throws Exception { 254 final Map<String, List<String>> headers = new HashMap<>(); 255 final String filename = "file.txt"; 256 final String createDate = "Fri, 10 Jun 2016 14:52:46 GMT"; 257 final String modDate = "Fri, 10 Jun 2016 18:52:46 GMT"; 258 final long size = 5320; 259 260 headers.put(CONTENT_DISPOSITION, Arrays.asList("attachment; filename=\"" + filename + "\";" + 261 " creation-date=\"" + createDate + "\";" + 262 " modification-date=\"" + modDate + "\";" + 263 " size=" + size)); 264 265 try (FcrepoResponse response = new FcrepoResponse(URI.create("http://localhost/foo"), 201, 266 headers, null)) { 267 final Map<String, String> disp = response.getContentDisposition(); 268 assertEquals(disp.get(CONTENT_DISPOSITION_FILENAME), filename); 269 assertEquals(disp.get(CONTENT_DISPOSITION_MODIFICATION_DATE), modDate); 270 assertEquals(Long.parseLong(disp.get(CONTENT_DISPOSITION_SIZE)), size); 271 } 272 } 273}