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