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