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