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.integration; 019 020import static javax.ws.rs.core.Response.Status.BAD_REQUEST; 021import static javax.ws.rs.core.Response.Status.CONFLICT; 022import static javax.ws.rs.core.Response.Status.CREATED; 023import static javax.ws.rs.core.Response.Status.GONE; 024import static javax.ws.rs.core.Response.Status.NOT_FOUND; 025import static javax.ws.rs.core.Response.Status.NOT_MODIFIED; 026import static javax.ws.rs.core.Response.Status.NO_CONTENT; 027import static javax.ws.rs.core.Response.Status.OK; 028import static javax.ws.rs.core.Response.Status.PARTIAL_CONTENT; 029import static javax.ws.rs.core.Response.Status.PRECONDITION_FAILED; 030import static org.fcrepo.client.FedoraHeaderConstants.CONTENT_DISPOSITION_FILENAME; 031import static org.fcrepo.client.FedoraHeaderConstants.CONTENT_TYPE; 032import static org.fcrepo.client.FedoraHeaderConstants.ETAG; 033import static org.fcrepo.client.FedoraHeaderConstants.LAST_MODIFIED; 034import static org.fcrepo.client.TestUtils.TEXT_TURTLE; 035import static org.fcrepo.client.TestUtils.sparqlUpdate; 036import static org.junit.Assert.assertEquals; 037import static org.junit.Assert.assertNotEquals; 038import static org.junit.Assert.assertNotNull; 039import static org.junit.Assert.assertNull; 040import static org.junit.Assert.assertTrue; 041 042import java.io.ByteArrayInputStream; 043import java.io.InputStream; 044import java.net.URI; 045import java.util.Calendar; 046import java.util.Date; 047import java.util.Map; 048 049import javax.ws.rs.core.EntityTag; 050 051import org.apache.commons.io.IOUtils; 052import org.apache.http.client.utils.DateUtils; 053import org.fcrepo.client.FcrepoClient; 054import org.fcrepo.client.FcrepoOperationFailedException; 055import org.fcrepo.client.FcrepoResponse; 056import org.jgroups.util.UUID; 057import org.junit.Before; 058import org.junit.Test; 059import org.junit.runner.RunWith; 060import org.springframework.test.context.ContextConfiguration; 061import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 062 063/** 064 * @author bbpennel 065 */ 066@RunWith(SpringJUnit4ClassRunner.class) 067@ContextConfiguration("/spring-test/test-container.xml") 068public class FcrepoClientIT extends AbstractResourceIT { 069 070 protected URI url; 071 072 final private String UNMODIFIED_DATE = "Mon, 1 Jan 2001 00:00:00 GMT"; 073 074 public FcrepoClientIT() throws Exception { 075 super(); 076 077 client = FcrepoClient.client() 078 .credentials("fedoraAdmin", "password") 079 .authScope("localhost") 080 .build(); 081 } 082 083 @Before 084 public void before() { 085 url = URI.create(serverAddress + UUID.randomUUID().toString()); 086 } 087 088 @Test 089 public void testPost() throws Exception { 090 final FcrepoResponse response = client.post(new URI(serverAddress)) 091 .perform(); 092 093 assertEquals(CREATED.getStatusCode(), response.getStatusCode()); 094 } 095 096 @Test 097 public void testPostBinary() throws Exception { 098 final String slug = "hello1"; 099 final String filename = "hello.txt"; 100 final String mimetype = "text/plain"; 101 final String bodyContent = "Hello world"; 102 final FcrepoResponse response = client.post(new URI(serverAddress)) 103 .body(new ByteArrayInputStream(bodyContent.getBytes()), mimetype) 104 .filename(filename) 105 .slug(slug) 106 .perform(); 107 108 final String content = IOUtils.toString(response.getBody(), "UTF-8"); 109 final int status = response.getStatusCode(); 110 111 assertEquals("Didn't get a CREATED response! Got content:\n" + content, 112 CREATED.getStatusCode(), status); 113 assertEquals("Location did not match slug", serverAddress + slug, response.getLocation().toString()); 114 115 assertNotNull("Didn't find linked description!", response.getLinkHeaders("describedby").get(0)); 116 117 final FcrepoResponse getResponse = client.get(response.getLocation()).perform(); 118 final Map<String, String> contentDisp = getResponse.getContentDisposition(); 119 assertEquals(filename, contentDisp.get(CONTENT_DISPOSITION_FILENAME)); 120 121 assertEquals(mimetype, getResponse.getContentType()); 122 123 final String getContent = IOUtils.toString(getResponse.getBody(), "UTF-8"); 124 assertEquals(bodyContent, getContent); 125 } 126 127 @Test 128 public void testPostDigestMismatch() throws Exception { 129 final String bodyContent = "Hello world"; 130 final String invalidDigest = "adc83b19e793491b1c6ea0fd8b46cd9f32e592fc"; 131 132 final FcrepoResponse response = client.post(new URI(serverAddress)) 133 .body(new ByteArrayInputStream(bodyContent.getBytes()), "text/plain") 134 .digestSha1(invalidDigest) 135 .perform(); 136 137 assertEquals("Invalid checksum was not rejected", CONFLICT.getStatusCode(), response.getStatusCode()); 138 } 139 140 @Test 141 public void testPostDigestMultipleChecksums() throws Exception { 142 final String bodyContent = "Hello world"; 143 144 final FcrepoResponse response = client.post(new URI(serverAddress)) 145 .body(new ByteArrayInputStream(bodyContent.getBytes()), "text/plain") 146 .digestMd5("3e25960a79dbc69b674cd4ec67a72c62") 147 .digestSha1("7b502c3a1f48c8609ae212cdfb639dee39673f5e") 148 .digestSha256("64ec88ca00b268e5ba1a35678a1b5316d212f4f366b2477232534a8aeca37f3c") 149 .perform(); 150 151 assertEquals("Checksums rejected", CREATED.getStatusCode(), response.getStatusCode()); 152 } 153 154 @Test 155 public void testPostDigestMultipleChecksumsOneMismatch() throws Exception { 156 final String bodyContent = "Hello world"; 157 158 final FcrepoResponse response = client.post(new URI(serverAddress)) 159 .body(new ByteArrayInputStream(bodyContent.getBytes()), "text/plain") 160 .digestMd5("3e25960a79dbc69b674cd4ec67a72c62") 161 .digestSha1("7b502c3a1f48c8609ae212cdfb639dee39673f5e") 162 // Incorrect sha256 163 .digestSha256("123488ca00b268e5ba1a35678a1b5316d212f4f366b2477232534a8aeca37f3c") 164 .perform(); 165 166 assertEquals("Invalid checksum was not rejected", CONFLICT.getStatusCode(), response.getStatusCode()); 167 } 168 169 @Test 170 public void testPut() throws Exception { 171 final FcrepoResponse response = create(); 172 173 assertEquals(CREATED.getStatusCode(), response.getStatusCode()); 174 assertEquals(url, response.getLocation()); 175 } 176 177 @Test 178 public void testPutEtag() throws Exception { 179 // Create object 180 final FcrepoResponse response = create(); 181 182 // Get the etag of the nearly created object 183 final EntityTag etag = EntityTag.valueOf(response.getHeaderValue(ETAG)); 184 185 // Retrieve the body of the resource so we can modify it 186 String body = getTurtle(url); 187 body += "\n<> dc:title \"some-title\""; 188 189 // Check that etag is making it through and being rejected 190 final FcrepoResponse updateResp = client.put(url) 191 .body(new ByteArrayInputStream(body.getBytes()), TEXT_TURTLE) 192 .ifMatch("\"bad-etag\"") 193 .perform(); 194 195 assertEquals(PRECONDITION_FAILED.getStatusCode(), updateResp.getStatusCode()); 196 197 // Verify that etag is retrieved and resubmitted correctly 198 final FcrepoResponse validResp = client.put(url) 199 .body(new ByteArrayInputStream(body.getBytes()), TEXT_TURTLE) 200 .ifMatch("\"" + etag.getValue() + "\"") 201 .perform(); 202 assertEquals(NO_CONTENT.getStatusCode(), validResp.getStatusCode()); 203 } 204 205 @Test 206 public void testPutUnmodifiedSince() throws Exception { 207 // Create object 208 final FcrepoResponse response = create(); 209 210 // Retrieve the body of the resource so we can modify it 211 String body = getTurtle(url); 212 body += "\n<> dc:title \"some-title\""; 213 214 // Update the body the first time, which should succeed 215 final String originalModified = response.getHeaderValue(LAST_MODIFIED); 216 final FcrepoResponse matchResponse = client.put(url) 217 .body(new ByteArrayInputStream(body.getBytes()), TEXT_TURTLE) 218 .ifUnmodifiedSince(originalModified) 219 .perform(); 220 221 assertEquals(NO_CONTENT.getStatusCode(), matchResponse.getStatusCode()); 222 223 // Update the triples a second time with old timestamp 224 final FcrepoResponse mismatchResponse = client.put(url) 225 .body(new ByteArrayInputStream(body.getBytes()), TEXT_TURTLE) 226 .ifUnmodifiedSince(UNMODIFIED_DATE) 227 .perform(); 228 229 assertEquals(PRECONDITION_FAILED.getStatusCode(), mismatchResponse.getStatusCode()); 230 } 231 232 @Test 233 public void testPutLenient() throws Exception { 234 // Create object 235 create(); 236 final String body = "<> <http://purl.org/dc/elements/1.1/title> \"some-title\""; 237 238 // try to update without lenient header 239 final FcrepoResponse strictResponse = client.put(url) 240 .body(new ByteArrayInputStream(body.getBytes()), TEXT_TURTLE) 241 .perform(); 242 assertEquals(CONFLICT.getStatusCode(), strictResponse.getStatusCode()); 243 244 // try again with lenient header 245 final FcrepoResponse lenientResponse = client.put(url) 246 .body(new ByteArrayInputStream(body.getBytes()), TEXT_TURTLE) 247 .preferLenient() 248 .perform(); 249 assertEquals(NO_CONTENT.getStatusCode(), lenientResponse.getStatusCode()); 250 } 251 252 @Test 253 public void testPatch() throws Exception { 254 // Create object 255 create(); 256 257 final InputStream body = new ByteArrayInputStream(sparqlUpdate.getBytes()); 258 259 // Update triples with sparql update 260 final FcrepoResponse response = client.patch(url) 261 .body(body) 262 .perform(); 263 264 assertEquals(NO_CONTENT.getStatusCode(), response.getStatusCode()); 265 } 266 267 @Test 268 public void testPatchEtagUpdated() throws Exception { 269 // Create object 270 final FcrepoResponse createResp = create(); 271 final EntityTag createdEtag = EntityTag.valueOf(createResp.getHeaderValue(ETAG)); 272 273 final InputStream body = new ByteArrayInputStream(sparqlUpdate.getBytes()); 274 275 // Update triples with sparql update 276 final FcrepoResponse response = client.patch(url) 277 .body(body) 278 .ifMatch("\"" + createdEtag.getValue() + "\"") 279 .perform(); 280 281 final EntityTag updateEtag = EntityTag.valueOf(response.getHeaderValue(ETAG)); 282 283 assertEquals(NO_CONTENT.getStatusCode(), response.getStatusCode()); 284 assertNotEquals("Etag did not change after patch", createdEtag, updateEtag); 285 } 286 287 @Test 288 public void testPatchNoBody() throws Exception { 289 create(); 290 291 final FcrepoResponse response = client.patch(url) 292 .perform(); 293 294 assertEquals(BAD_REQUEST.getStatusCode(), response.getStatusCode()); 295 } 296 297 @Test 298 public void testDelete() throws Exception { 299 create(); 300 301 final FcrepoResponse response = client.delete(url).perform(); 302 303 assertEquals(NO_CONTENT.getStatusCode(), response.getStatusCode()); 304 305 assertEquals(GONE.getStatusCode(), client.get(url).perform().getStatusCode()); 306 } 307 308 @Test 309 public void testGet() throws Exception { 310 create(); 311 final FcrepoResponse response = client.get(url).perform(); 312 313 assertEquals(OK.getStatusCode(), response.getStatusCode()); 314 } 315 316 @Test 317 public void testGetNotFound() throws Exception { 318 final FcrepoResponse response = client.get(url).perform(); 319 320 assertEquals(NOT_FOUND.getStatusCode(), response.getStatusCode()); 321 } 322 323 @Test 324 public void testGetUnmodified() throws Exception { 325 // Check that get returns a 304 if the item hasn't changed according to last-modified 326 final FcrepoResponse response = create(); 327 328 // Get tomorrows date to provide as the modified-since date 329 final String lastModified = response.getHeaderValue(LAST_MODIFIED); 330 final Date modDate = DateUtils.parseDate(lastModified); 331 final Calendar cal = Calendar.getInstance(); 332 cal.setTime(modDate); 333 cal.add(Calendar.DATE, 1); 334 335 final FcrepoResponse modResp = client.get(url) 336 .ifModifiedSince(DateUtils.formatDate(cal.getTime())) 337 .perform(); 338 339 assertEquals(NOT_MODIFIED.getStatusCode(), modResp.getStatusCode()); 340 assertNull("Response body should not be returned when unmodified", modResp.getBody()); 341 } 342 343 @Test 344 public void testGetModified() throws Exception { 345 // Check that get returns a 200 if the item has changed according to last-modified 346 final FcrepoResponse response = create(); 347 348 // Get yesterdays date to provide as the modified-since date 349 final String lastModified = response.getHeaderValue(LAST_MODIFIED); 350 final Date modDate = DateUtils.parseDate(lastModified); 351 final Calendar cal = Calendar.getInstance(); 352 cal.setTime(modDate); 353 cal.add(Calendar.DATE, -1); 354 355 final FcrepoResponse modResp = client.get(url) 356 .ifModifiedSince(DateUtils.formatDate(cal.getTime())) 357 .perform(); 358 359 assertEquals(OK.getStatusCode(), modResp.getStatusCode()); 360 assertNotNull("GET response body should be normal when modified", modResp.getBody()); 361 } 362 363 @Test 364 public void testGetAccept() throws Exception { 365 // Check that get returns a 304 if the item hasn't changed according to last-modified/etag 366 create(); 367 368 final FcrepoResponse response = client.get(url) 369 .accept("application/n-triples") 370 .perform(); 371 372 assertEquals("application/n-triples", response.getHeaderValue(CONTENT_TYPE)); 373 assertEquals(OK.getStatusCode(), response.getStatusCode()); 374 } 375 376 @Test 377 public void testGetPrefer() throws Exception { 378 // Check that get returns a 304 if the item hasn't changed according to last-modified/etag 379 create(); 380 381 final FcrepoResponse response = client.get(url) 382 .preferMinimal() 383 .perform(); 384 385 assertEquals(OK.getStatusCode(), response.getStatusCode()); 386 assertEquals("return=minimal", response.getHeaderValue("Preference-Applied")); 387 } 388 389 @Test 390 public void testGetRange() throws Exception { 391 // Creating a binary for retrieval 392 final String mimetype = "text/plain"; 393 final String bodyContent = "Hello world"; 394 final FcrepoResponse response = client.post(new URI(serverAddress)) 395 .body(new ByteArrayInputStream(bodyContent.getBytes()), mimetype) 396 .perform(); 397 398 final URI url = response.getLocation(); 399 400 // Get the content of the object after the first 6 bytes 401 final FcrepoResponse rangeResp = client.get(url) 402 .range(6L, null) 403 .perform(); 404 405 final String content = IOUtils.toString(rangeResp.getBody(), "UTF-8"); 406 assertEquals("Body did not contain correct range of original content", "world", content); 407 assertEquals(PARTIAL_CONTENT.getStatusCode(), rangeResp.getStatusCode()); 408 } 409 410 @Test 411 public void testGetDisableRedirects() throws Exception { 412 // Creating a binary with external content for retrieval 413 final String mimetype = "message/external-body; access-type=URL; URL=\"http://www.example.com/file\""; 414 final FcrepoResponse response = client.post(new URI(serverAddress)) 415 .body(new ByteArrayInputStream(new byte[]{}), mimetype) 416 .perform(); 417 418 final URI url = response.getLocation(); 419 420 // Make sure the response is the redirect itself, not the URL being redirected to 421 final FcrepoResponse getResponse = client.get(url).disableRedirects().perform(); 422 assertEquals(307, getResponse.getStatusCode()); 423 assertEquals(url, getResponse.getUrl()); 424 assertEquals(URI.create("http://www.example.com/file"), getResponse.getLocation()); 425 } 426 427 @Test 428 public void testHead() throws Exception { 429 final FcrepoResponse response = create(); 430 final FcrepoResponse headResp = client.head(url).perform(); 431 432 assertEquals(OK.getStatusCode(), headResp.getStatusCode()); 433 assertEquals(response.getHeaderValue(ETAG), headResp.getHeaderValue(ETAG)); 434 assertNotNull(headResp.getHeaderValue("Allow")); 435 } 436 437 @Test 438 public void testOptions() throws Exception { 439 create(); 440 final FcrepoResponse headResp = client.options(url).perform(); 441 442 assertEquals(OK.getStatusCode(), headResp.getStatusCode()); 443 assertNotNull(headResp.getHeaderValue("Allow")); 444 assertNotNull(headResp.getHeaderValue("Accept-Post")); 445 assertNotNull(headResp.getHeaderValue("Accept-Patch")); 446 } 447 448 @Test 449 public void testMove() throws Exception { 450 create(); 451 452 final URI destUrl = new URI(url.toString() + "_dest"); 453 final FcrepoResponse moveResp = client.move(url, destUrl).perform(); 454 assertEquals(CREATED.getStatusCode(), moveResp.getStatusCode()); 455 456 assertEquals("Object still at original url", 457 GONE.getStatusCode(), client.get(url).perform().getStatusCode()); 458 459 assertEquals("Object not at expected new url", 460 OK.getStatusCode(), client.get(destUrl).perform().getStatusCode()); 461 } 462 463 @Test 464 public void testCopy() throws Exception { 465 create(); 466 467 // Add something identifiable to the record 468 final InputStream body = new ByteArrayInputStream(sparqlUpdate.getBytes()); 469 client.patch(url).body(body).perform(); 470 471 final URI destUrl = new URI(url.toString() + "_dest"); 472 final FcrepoResponse copyResp = client.copy(url, destUrl).perform(); 473 assertEquals(CREATED.getStatusCode(), copyResp.getStatusCode()); 474 475 final FcrepoResponse originalResp = client.get(url).perform(); 476 final String originalContent = IOUtils.toString(originalResp.getBody(), "UTF-8"); 477 assertTrue(originalContent.contains("Foo")); 478 479 final FcrepoResponse destResp = client.get(destUrl).perform(); 480 final String destContent = IOUtils.toString(destResp.getBody(), "UTF-8"); 481 assertTrue(destContent.contains("Foo")); 482 } 483 484 private FcrepoResponse create() throws FcrepoOperationFailedException { 485 return client.put(url).perform(); 486 } 487 488 private String getTurtle(final URI url) throws Exception { 489 final FcrepoResponse getResponse = client.get(url) 490 .accept("text/turtle") 491 .perform(); 492 return IOUtils.toString(getResponse.getBody(), "UTF-8"); 493 } 494}