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.integration.http.api; 019 020import static java.lang.Thread.sleep; 021import static java.nio.charset.StandardCharsets.UTF_8; 022import static java.time.ZoneId.of; 023import static java.time.format.DateTimeFormatter.RFC_1123_DATE_TIME; 024import static java.util.Arrays.asList; 025import static java.util.regex.Pattern.compile; 026import static javax.ws.rs.core.HttpHeaders.ACCEPT; 027import static javax.ws.rs.core.HttpHeaders.CONTENT_DISPOSITION; 028import static javax.ws.rs.core.HttpHeaders.CONTENT_LENGTH; 029import static javax.ws.rs.core.HttpHeaders.CONTENT_TYPE; 030import static javax.ws.rs.core.HttpHeaders.LINK; 031import static javax.ws.rs.core.Link.fromUri; 032import static javax.ws.rs.core.MediaType.TEXT_PLAIN; 033import static javax.ws.rs.core.Response.Status.BAD_REQUEST; 034import static javax.ws.rs.core.Response.Status.CONFLICT; 035import static javax.ws.rs.core.Response.Status.CREATED; 036import static javax.ws.rs.core.Response.Status.GONE; 037import static javax.ws.rs.core.Response.Status.METHOD_NOT_ALLOWED; 038import static javax.ws.rs.core.Response.Status.NOT_ACCEPTABLE; 039import static javax.ws.rs.core.Response.Status.NOT_FOUND; 040import static javax.ws.rs.core.Response.Status.NOT_MODIFIED; 041import static javax.ws.rs.core.Response.Status.NO_CONTENT; 042import static javax.ws.rs.core.Response.Status.OK; 043import static javax.ws.rs.core.Response.Status.PARTIAL_CONTENT; 044import static javax.ws.rs.core.Response.Status.PRECONDITION_FAILED; 045import static javax.ws.rs.core.Response.Status.UNSUPPORTED_MEDIA_TYPE; 046import static nu.validator.htmlparser.common.DoctypeExpectation.NO_DOCTYPE_ERRORS; 047import static nu.validator.htmlparser.common.XmlViolationPolicy.ALLOW; 048import static org.apache.http.HttpStatus.SC_BAD_REQUEST; 049import static org.apache.http.HttpStatus.SC_CREATED; 050import static org.apache.http.HttpStatus.SC_OK; 051import static org.apache.http.entity.ContentType.parse; 052import static org.apache.http.impl.client.cache.CacheConfig.DEFAULT; 053import static org.apache.jena.datatypes.TypeMapper.getInstance; 054import static org.apache.jena.datatypes.xsd.XSDDatatype.XSDinteger; 055import static org.apache.jena.datatypes.xsd.XSDDatatype.XSDlong; 056import static org.apache.jena.graph.Node.ANY; 057import static org.apache.jena.graph.NodeFactory.createLiteral; 058import static org.apache.jena.graph.NodeFactory.createURI; 059import static org.apache.jena.rdf.model.ModelFactory.createDefaultModel; 060import static org.apache.jena.rdf.model.ModelFactory.createModelForGraph; 061import static org.apache.jena.rdf.model.ResourceFactory.createResource; 062import static org.apache.jena.riot.RDFLanguages.contentTypeToLang; 063import static org.apache.jena.riot.WebContent.contentTypeN3; 064import static org.apache.jena.riot.WebContent.contentTypeN3Alt2; 065import static org.apache.jena.riot.WebContent.contentTypeNTriples; 066import static org.apache.jena.riot.WebContent.contentTypeRDFXML; 067import static org.apache.jena.riot.WebContent.contentTypeSPARQLUpdate; 068import static org.apache.jena.riot.WebContent.contentTypeTurtle; 069import static org.apache.jena.vocabulary.DC_11.title; 070import static org.apache.jena.vocabulary.RDF.type; 071import static org.fcrepo.http.commons.domain.RDFMediaType.POSSIBLE_RDF_RESPONSE_VARIANTS_STRING; 072import static org.fcrepo.http.commons.domain.RDFMediaType.POSSIBLE_RDF_VARIANTS; 073import static org.fcrepo.kernel.api.FedoraTypes.FCR_ACL; 074import static org.fcrepo.kernel.api.FedoraTypes.FCR_FIXITY; 075import static org.fcrepo.kernel.api.FedoraTypes.FCR_METADATA; 076import static org.fcrepo.kernel.api.FedoraTypes.FCR_TOMBSTONE; 077import static org.fcrepo.kernel.api.FedoraTypes.FCR_VERSIONS; 078import static org.fcrepo.kernel.api.RdfLexicon.ARCHIVAL_GROUP; 079import static org.fcrepo.kernel.api.RdfLexicon.BASIC_CONTAINER; 080import static org.fcrepo.kernel.api.RdfLexicon.CONSTRAINED_BY; 081import static org.fcrepo.kernel.api.RdfLexicon.CONTAINER; 082import static org.fcrepo.kernel.api.RdfLexicon.CONTAINS; 083import static org.fcrepo.kernel.api.RdfLexicon.CREATED_DATE; 084import static org.fcrepo.kernel.api.RdfLexicon.DIRECT_CONTAINER; 085import static org.fcrepo.kernel.api.RdfLexicon.FEDORA_BINARY; 086import static org.fcrepo.kernel.api.RdfLexicon.FEDORA_CONTAINER; 087import static org.fcrepo.kernel.api.RdfLexicon.FEDORA_RESOURCE; 088import static org.fcrepo.kernel.api.RdfLexicon.HAS_FIXITY_SERVICE; 089import static org.fcrepo.kernel.api.RdfLexicon.HAS_MEMBER_RELATION; 090import static org.fcrepo.kernel.api.RdfLexicon.HAS_MESSAGE_DIGEST; 091import static org.fcrepo.kernel.api.RdfLexicon.HAS_MIME_TYPE; 092import static org.fcrepo.kernel.api.RdfLexicon.HAS_ORIGINAL_NAME; 093import static org.fcrepo.kernel.api.RdfLexicon.HAS_SIZE; 094import static org.fcrepo.kernel.api.RdfLexicon.INBOUND_REFERENCES; 095import static org.fcrepo.kernel.api.RdfLexicon.INDIRECT_CONTAINER; 096import static org.fcrepo.kernel.api.RdfLexicon.INSERTED_CONTENT_RELATION; 097import static org.fcrepo.kernel.api.RdfLexicon.LAST_MODIFIED_DATE; 098import static org.fcrepo.kernel.api.RdfLexicon.LDP_MEMBER; 099import static org.fcrepo.kernel.api.RdfLexicon.MEMBERSHIP_RESOURCE; 100import static org.fcrepo.kernel.api.RdfLexicon.MEMENTO_NAMESPACE; 101import static org.fcrepo.kernel.api.RdfLexicon.MEMENTO_TYPE; 102import static org.fcrepo.kernel.api.RdfLexicon.NON_RDF_SOURCE; 103import static org.fcrepo.kernel.api.RdfLexicon.PREFER_MINIMAL_CONTAINER; 104import static org.fcrepo.kernel.api.RdfLexicon.PREFER_SERVER_MANAGED; 105import static org.fcrepo.kernel.api.RdfLexicon.RDF_SOURCE; 106import static org.fcrepo.kernel.api.RdfLexicon.REPOSITORY_NAMESPACE; 107import static org.fcrepo.kernel.api.RdfLexicon.REPOSITORY_ROOT; 108import static org.fcrepo.kernel.api.RdfLexicon.RESOURCE; 109import static org.fcrepo.kernel.api.RdfLexicon.VERSIONED_RESOURCE; 110import static org.fcrepo.kernel.api.RdfLexicon.VERSIONING_TIMEGATE_TYPE; 111import static org.fcrepo.kernel.api.models.ExternalContent.COPY; 112import static org.fcrepo.kernel.api.models.ExternalContent.PROXY; 113import static org.fcrepo.kernel.api.models.ExternalContent.REDIRECT; 114import static org.junit.Assert.assertEquals; 115import static org.junit.Assert.assertFalse; 116import static org.junit.Assert.assertNotEquals; 117import static org.junit.Assert.assertNotNull; 118import static org.junit.Assert.assertNull; 119import static org.junit.Assert.assertTrue; 120import static org.junit.Assert.fail; 121import static org.slf4j.LoggerFactory.getLogger; 122 123import java.io.ByteArrayInputStream; 124import java.io.ByteArrayOutputStream; 125import java.io.File; 126import java.io.IOException; 127import java.io.InputStream; 128import java.io.OutputStreamWriter; 129import java.io.StringReader; 130import java.io.StringWriter; 131import java.io.UnsupportedEncodingException; 132import java.io.Writer; 133import java.net.URI; 134import java.net.URLEncoder; 135import java.text.ParseException; 136import java.time.Instant; 137import java.time.ZoneId; 138import java.time.format.DateTimeFormatter; 139import java.util.ArrayList; 140import java.util.Arrays; 141import java.util.Collection; 142import java.util.Iterator; 143import java.util.List; 144import java.util.Locale; 145import java.util.NoSuchElementException; 146import java.util.Optional; 147import java.util.Random; 148import java.util.concurrent.Phaser; 149import java.util.concurrent.TimeUnit; 150 151import javax.ws.rs.core.Link; 152import javax.ws.rs.core.Response.Status; 153import javax.ws.rs.core.Variant; 154 155import com.fasterxml.jackson.databind.JsonNode; 156import com.fasterxml.jackson.databind.ObjectMapper; 157import com.google.common.collect.Iterators; 158import nu.validator.htmlparser.sax.HtmlParser; 159import nu.validator.saxtree.TreeBuilder; 160import org.apache.commons.io.IOUtils; 161import org.apache.http.Header; 162import org.apache.http.HttpEntity; 163import org.apache.http.HttpResponse; 164import org.apache.http.client.config.RequestConfig; 165import org.apache.http.client.methods.CloseableHttpResponse; 166import org.apache.http.client.methods.HttpDelete; 167import org.apache.http.client.methods.HttpGet; 168import org.apache.http.client.methods.HttpHead; 169import org.apache.http.client.methods.HttpOptions; 170import org.apache.http.client.methods.HttpPatch; 171import org.apache.http.client.methods.HttpPost; 172import org.apache.http.client.methods.HttpPut; 173import org.apache.http.client.methods.HttpUriRequest; 174import org.apache.http.entity.BasicHttpEntity; 175import org.apache.http.entity.ByteArrayEntity; 176import org.apache.http.entity.FileEntity; 177import org.apache.http.entity.InputStreamEntity; 178import org.apache.http.entity.StringEntity; 179import org.apache.http.impl.client.CloseableHttpClient; 180import org.apache.http.impl.client.cache.CachingHttpClientBuilder; 181import org.apache.http.util.EntityUtils; 182import org.apache.jena.graph.Node; 183import org.apache.jena.graph.NodeFactory; 184import org.apache.jena.query.Dataset; 185import org.apache.jena.rdf.model.Model; 186import org.apache.jena.rdf.model.Property; 187import org.apache.jena.rdf.model.Resource; 188import org.apache.jena.rdf.model.StmtIterator; 189import org.apache.jena.riot.RDFFormat; 190import org.apache.jena.sparql.core.DatasetGraph; 191import org.apache.jena.sparql.core.Quad; 192import org.apache.jena.vocabulary.DC_11; 193import org.apache.jena.vocabulary.RDF; 194import org.fcrepo.http.commons.domain.RDFMediaType; 195import org.fcrepo.http.commons.test.util.CloseableDataset; 196import org.fcrepo.kernel.api.RdfLexicon; 197import org.glassfish.jersey.media.multipart.ContentDisposition; 198import org.junit.Ignore; 199import org.junit.Test; 200import org.slf4j.Logger; 201import org.springframework.test.context.TestExecutionListeners; 202import org.xml.sax.ErrorHandler; 203import org.xml.sax.InputSource; 204import org.xml.sax.SAXException; 205import org.xml.sax.SAXParseException; 206 207/** 208 * @author cabeer 209 * @author ajs6f 210 */ 211@TestExecutionListeners( 212 listeners = { TestIsolationExecutionListener.class }, 213 mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS) 214public class FedoraLdpIT extends AbstractResourceIT { 215 216 private static final Node DC_IDENTIFIER = DC_11.identifier.asNode(); 217 218 private static final String LDP_RESOURCE_LINK_HEADER = "<" + RESOURCE.getURI() + ">; rel=\"type\""; 219 220 private static final Node rdfType = type.asNode(); 221 222 private static final Resource PCDM_FILE_TYPE = createResource("http://pcdm.org/models#File"); 223 224 private static final String CONTAINER_LINK_HEADER = "<" + CONTAINER.getURI() + ">; rel=\"type\""; 225 226 private static final String BASIC_CONTAINER_LINK_HEADER = "<" + BASIC_CONTAINER.getURI() + ">; rel=\"type\""; 227 private static final String INDIRECT_CONTAINER_LINK_HEADER = "<" + INDIRECT_CONTAINER.getURI() + ">; rel=\"type\""; 228 private static final String ARCHIVAL_GROUP_LINK_HEADER = "<" + ARCHIVAL_GROUP.getURI() + ">; rel=\"type\""; 229 230 private static final String RESOURCE_LINK_HEADER = "<" + RESOURCE.getURI() + ">; rel=\"type\""; 231 private static final String RDF_SOURCE_LINK_HEADER = "<" + RDF_SOURCE.getURI() + ">; rel=\"type\""; 232 private static final String NON_RDF_SOURCE_LINK_HEADER = "<" + NON_RDF_SOURCE.getURI() + ">; rel=\"type\""; 233 private static final String VERSIONED_RESOURCE_LINK_HEADER = "<" + VERSIONED_RESOURCE.getURI() + ">; rel=\"type\""; 234 235 private static final String SERVER_MANAGED_TYPE_CONSTRAINT_URI = serverAddress + 236 "static/constraints/ServerManagedTypeException.rdf"; 237 238 private static final String SERVER_MANAGED_PROPERTY_CONSTRAINT_URI = serverAddress + 239 "static/constraints/ServerManagedPropertyException.rdf"; 240 241 private static final String INBOUND_REFERENCE_PREFER_HEADER = "return=representation; include=\"" + 242 INBOUND_REFERENCES + "\""; 243 244 private static final String WANT_DIGEST = "Want-Digest"; 245 246 private static final String DIGEST = "Digest"; 247 248 private static final String TEST_BINARY_CONTENT = "01234567890123456789012345678901234567890123456789"; 249 250 private static final String TEST_SHA_DIGEST_HEADER_VALUE = "sha=9578f951955d37f20b601c26591e260c1e5389bf"; 251 252 private static final String TEST_MD5_DIGEST_HEADER_VALUE = "md5=baed005300234f3d1503c50a48ce8e6f"; 253 254 private static final String TEST_SHA256_DIGEST_HEADER_VALUE = 255 "sha-256=fb871ff8cce8fea83dfaeab41784305a1461e008dc02a371ed26d856c766c903"; 256 257 private static final Logger LOGGER = getLogger(FedoraLdpIT.class); 258 259 private static final DateTimeFormatter headerFormat = 260 RFC_1123_DATE_TIME.withLocale(Locale.US).withZone(ZoneId.of("GMT")); 261 262 private static final DateTimeFormatter tripleFormat = 263 DateTimeFormatter.ISO_INSTANT.withZone(of("GMT")); 264 265 @Test 266 public void testHeadRepositoryGraph() throws IOException { 267 final HttpHead headObjMethod = new HttpHead(serverAddress); 268 try (final CloseableHttpResponse response = execute(headObjMethod)) { 269 assertEquals(OK.getStatusCode(), response.getStatusLine().getStatusCode()); 270 checkForLinkHeader(response, serverAddress + FCR_ACL, "acl"); 271 } 272 } 273 274 @Test 275 public void testHeadObject() throws IOException { 276 final String id = getRandomUniqueId(); 277 createObject(id).close(); 278 final HttpHead headObjMethod = headObjMethod(id); 279 assertEquals(OK.getStatusCode(), getStatus(headObjMethod)); 280 } 281 282 @Test 283 public void testHeadDefaultContainer() throws IOException { 284 final String id = getRandomUniqueId(); 285 createObject(id).close(); 286 final HttpHead headObjMethod = headObjMethod(id); 287 try (final CloseableHttpResponse response = execute(headObjMethod)) { 288 checkForLinkHeader(response, RDF_SOURCE.getURI(), "type"); 289 assertTrue("Didn't find LDP container link header!", getLinkHeaders(response).contains( 290 BASIC_CONTAINER_LINK_HEADER)); 291 } 292 } 293 294 @Test 295 public void testHeadBasicContainer() throws IOException { 296 final String id = getRandomUniqueId(); 297 298 createObjectAndClose(id, BASIC_CONTAINER_LINK_HEADER); 299 300 final HttpHead headObjMethod = headObjMethod(id); 301 try (final CloseableHttpResponse response = execute(headObjMethod)) { 302 final Collection<String> links = getLinkHeaders(response); 303 checkForLinkHeader(response, RDF_SOURCE.getURI(), "type"); 304 assertTrue("Didn't find LDP container link header!", links.contains(BASIC_CONTAINER_LINK_HEADER)); 305 } 306 } 307 308 @Test 309 public void testCreateArchivalGroup() throws Exception { 310 final var id = getRandomUniqueId(); 311 final var childId = id + "/child"; 312 final var grandChildId = childId + "/grandchild"; 313 314 createObjectAndClose(id, BASIC_CONTAINER_LINK_HEADER, ARCHIVAL_GROUP_LINK_HEADER); 315 316 final var headObjMethod = headObjMethod(id); 317 try (final CloseableHttpResponse response = execute(headObjMethod)) { 318 final Collection<String> links = getLinkHeaders(response); 319 assertTrue("Didn't find ArchivalGroup link header!", links.contains(ARCHIVAL_GROUP_LINK_HEADER)); 320 assertTrue("Didn't find LDP container link header!", links.contains(BASIC_CONTAINER_LINK_HEADER)); 321 } 322 323 324 executeAndClose(putObjMethod(childId, "text/turtle", "<> a <http://example.com/Foo> .")); 325 326 final var childHeadObjMethod = headObjMethod(childId); 327 try (final CloseableHttpResponse response = execute(childHeadObjMethod)) { 328 final Collection<String> links = getLinkHeaders(response); 329 assertTrue("Didn't find LDP container link header!", links.contains(BASIC_CONTAINER_LINK_HEADER)); 330 assertFalse("Unexpectedly found ArchivalGroup link header!", 331 links.contains(ARCHIVAL_GROUP_LINK_HEADER)); 332 } 333 334 executeAndClose(putObjMethod(grandChildId, "text/turtle", "<> a <http://example.com/Foo> .")); 335 336 final var grandChildHeadObjMethod = headObjMethod(grandChildId); 337 try (final CloseableHttpResponse response = execute(grandChildHeadObjMethod)) { 338 final Collection<String> links = getLinkHeaders(response); 339 assertTrue("Didn't find LDP container link header!", links.contains(BASIC_CONTAINER_LINK_HEADER)); 340 assertFalse("Unexpectedly found ArchivalGroup link header!", 341 links.contains(ARCHIVAL_GROUP_LINK_HEADER)); 342 } 343 } 344 345 @Test 346 public void testCreateArchivalGroupWithinAnArchivalGroupFails() throws Exception { 347 final var id = getRandomUniqueId(); 348 final var childId = id + "/child"; 349 350 createObjectAndClose(id, BASIC_CONTAINER_LINK_HEADER, ARCHIVAL_GROUP_LINK_HEADER); 351 352 final var headObjMethod = headObjMethod(id); 353 try (final CloseableHttpResponse response = execute(headObjMethod)) { 354 final Collection<String> links = getLinkHeaders(response); 355 assertTrue("Didn't find ArchivalGroup link header!", links.contains(ARCHIVAL_GROUP_LINK_HEADER)); 356 assertTrue("Didn't find LDP container link header!", links.contains(BASIC_CONTAINER_LINK_HEADER)); 357 } 358 359 final var putObjMethod = putObjMethod(childId, "text/turtle", "<> a <http://example.com/Foo> ."); 360 putObjMethod.setHeader("Link", ARCHIVAL_GROUP_LINK_HEADER); 361 try (final CloseableHttpResponse response = execute(putObjMethod)) { 362 assertEquals("Expected Conflict response", CONFLICT.getStatusCode(), 363 response.getStatusLine().getStatusCode()); 364 } 365 } 366 367 @Test 368 public void testCreateBinaryAsArchivalGroupWithPostFails() throws Exception { 369 final var id = getRandomUniqueId(); 370 final var postMethod = postObjMethod(); 371 postMethod.setHeader("Slug", id); 372 postMethod.setEntity(new StringEntity("test")); 373 postMethod.addHeader("Link", ARCHIVAL_GROUP_LINK_HEADER); 374 postMethod.addHeader("Link", NON_RDF_SOURCE_LINK_HEADER); 375 postMethod.addHeader("Content-Type", "text/plain"); 376 377 try (final CloseableHttpResponse response = execute(postMethod)) { 378 assertEquals("Expected Bad Request response", BAD_REQUEST.getStatusCode(), 379 response.getStatusLine().getStatusCode()); 380 } 381 } 382 383 @Test 384 public void testCreateBinaryAsArchivalGroupWithPutFails() throws Exception { 385 final var id = getRandomUniqueId(); 386 final var putMethod = putObjMethod(id, "text/plain", "testcontent"); 387 putMethod.addHeader("Link", ARCHIVAL_GROUP_LINK_HEADER); 388 putMethod.addHeader("Link", NON_RDF_SOURCE_LINK_HEADER); 389 390 try (final CloseableHttpResponse response = execute(putMethod)) { 391 assertEquals("Expected Bad Request response", BAD_REQUEST.getStatusCode(), 392 response.getStatusLine().getStatusCode()); 393 } 394 } 395 396 @Test 397 public void testHeadTurtleContentType() throws IOException { 398 testHeadDefaultContentType(RDFMediaType.TURTLE_WITH_CHARSET); 399 } 400 401 @Test 402 public void testHeadRDFContentType() throws IOException { 403 testHeadDefaultContentType(RDFMediaType.RDF_XML); 404 } 405 406 @Test 407 public void testHeadJSONLDContentType() throws IOException { 408 testHeadDefaultContentType(RDFMediaType.JSON_LD); 409 } 410 411 @Test 412 public void testHeadDefaultContentType() throws IOException { 413 testHeadDefaultContentType(null); 414 } 415 416 private void testHeadDefaultContentType(final String mimeType) throws IOException { 417 final String id = getRandomUniqueId(); 418 createObjectAndClose(id); 419 420 final HttpHead headObjMethod = headObjMethod(id); 421 String mt = mimeType; 422 if (mt != null) { 423 headObjMethod.addHeader("Accept", mt); 424 } else { 425 mt = RDFMediaType.TURTLE_WITH_CHARSET; 426 } 427 try (final CloseableHttpResponse response = execute(headObjMethod)) { 428 final Collection<String> contentTypes = getHeader(response, CONTENT_TYPE); 429 final String contentType = contentTypes.iterator().next(); 430 assertTrue("Didn't find LDP valid content-type header: " + contentType + 431 "; expected result: " + mt, contentType.contains(mt)); 432 testHeadVaryAndPreferHeaders(response); 433 } 434 } 435 436 private void testHeadVaryAndPreferHeaders(final CloseableHttpResponse response) { 437 final Collection<String> preferenceApplied = getHeader(response, "Preference-Applied"); 438 final Collection<String> vary = getHeader(response, "Vary"); 439 assertTrue("Didn't find valid Preference-Applied header", preferenceApplied.contains("return=representation")); 440 assertTrue("Didn't find valid Vary Prefer header", vary.contains("Prefer")); 441 assertTrue("Didn't find valid Vary header", 442 vary.contains("Accept")); 443 assertTrue("Didn't find valid Vary header", 444 vary.contains("Range")); 445 assertTrue("Didn't find valid Vary header", 446 vary.contains("Accept-Encoding")); 447 assertTrue("Didn't find valid Vary header", 448 vary.contains("Accept-Language")); 449 450 } 451 452 @Test 453 public void testHeadDefaultRDF() throws IOException { 454 final String id = getRandomUniqueId(); 455 final HttpPut put = putObjMethod(id, "text/turtle", "<> a <http://example.com/Foo> ."); 456 executeAndClose(put); 457 458 final HttpHead headObjMethod = headObjMethod(id); 459 try (final CloseableHttpResponse response = execute(headObjMethod)) { 460 final Collection<String> links = getLinkHeaders(response); 461 assertTrue("Didn't find LDP BasicContainer link header!", links.contains(BASIC_CONTAINER_LINK_HEADER)); 462 testHeadVaryAndPreferHeaders(response); 463 } 464 } 465 466 @Test 467 public void testHeadDefaultNonRDF() throws IOException { 468 final String id = getRandomUniqueId(); 469 final HttpPut put = putObjMethod(id, "text/plain", "<> a <http://example.com/Foo> ."); 470 executeAndClose(put); 471 472 final HttpHead headObjMethod = headObjMethod(id); 473 try (final CloseableHttpResponse response = execute(headObjMethod)) { 474 final Collection<String> links = getLinkHeaders(response); 475 assertTrue("Didn't find LDP NonRDFSource link header!", links.contains(NON_RDF_SOURCE_LINK_HEADER)); 476 } 477 } 478 479 @Test 480 public void testHeadDirectContainer() throws IOException { 481 final String id = getRandomUniqueId(); 482 final HttpPut put = putObjMethod(id); 483 put.setHeader(LINK, DIRECT_CONTAINER_LINK_HEADER); 484 executeAndClose(put); 485 486 final HttpHead headObjMethod = headObjMethod(id); 487 try (final CloseableHttpResponse response = execute(headObjMethod)) { 488 final Collection<String> links = getLinkHeaders(response); 489 checkForLinkHeader(response, RDF_SOURCE.getURI(), "type"); 490 assertTrue("Didn't find LDP container link header!", links.contains(DIRECT_CONTAINER_LINK_HEADER)); 491 testHeadVaryAndPreferHeaders(response); 492 } 493 } 494 495 @Test 496 public void testHeadIndirectContainer() throws IOException { 497 final String id = getRandomUniqueId(); 498 final HttpPut put = putObjMethod(id); 499 put.setHeader(LINK, INDIRECT_CONTAINER_LINK_HEADER); 500 executeAndClose(put); 501 502 final HttpHead headObjMethod = headObjMethod(id); 503 try (final CloseableHttpResponse response = execute(headObjMethod)) { 504 final Collection<String> links = getLinkHeaders(response); 505 assertTrue("Didn't find LDP container link header!", links.contains(INDIRECT_CONTAINER_LINK_HEADER)); 506 testHeadVaryAndPreferHeaders(response); 507 } 508 } 509 510 @Test 511 public void testHeadDatastream() throws IOException, ParseException { 512 final String id = getRandomUniqueId(); 513 createDatastream(id, "x", "123"); 514 515 final HttpHead headObjMethod = headObjMethod(id + "/x"); 516 try (final CloseableHttpResponse response = execute(headObjMethod)) { 517 assertEquals(OK.getStatusCode(), response.getStatusLine().getStatusCode()); 518 assertEquals(TEXT_PLAIN, response.getFirstHeader(CONTENT_TYPE).getValue()); 519 assertEquals("3", response.getFirstHeader(CONTENT_LENGTH).getValue()); 520 assertEquals("bytes", response.getFirstHeader("Accept-Ranges").getValue()); 521 final ContentDisposition disposition = 522 new ContentDisposition(response.getFirstHeader(CONTENT_DISPOSITION).getValue()); 523 assertEquals("attachment", disposition.getType()); 524 } 525 } 526 527 @Test 528 public void testHeadDatastreamWithWantDigest() throws IOException { 529 final String id = getRandomUniqueId(); 530 createDatastream(id, "x", TEST_BINARY_CONTENT); 531 532 final HttpHead headObjMethod = headObjMethod(id + "/x"); 533 headObjMethod.addHeader(WANT_DIGEST, "SHA"); 534 try (final CloseableHttpResponse response = execute(headObjMethod)) { 535 assertEquals(OK.getStatusCode(), response.getStatusLine().getStatusCode()); 536 assertEquals(TEXT_PLAIN, response.getFirstHeader(CONTENT_TYPE).getValue()); 537 assertTrue(response.getHeaders(DIGEST).length > 0); 538 final String digesterHeaderValue = response.getHeaders(DIGEST)[0].getValue(); 539 assertTrue("Fixity Checksum doesn't match", 540 digesterHeaderValue.equals(TEST_SHA_DIGEST_HEADER_VALUE)); 541 } 542 } 543 544 @Test 545 public void testHeadDatastreamWithWantDigestMultiple() throws IOException { 546 final String id = getRandomUniqueId(); 547 createDatastream(id, "x", TEST_BINARY_CONTENT); 548 549 final HttpHead headObjMethod = headObjMethod(id + "/x"); 550 headObjMethod.addHeader(WANT_DIGEST, "SHA, md5;q=0.3"); 551 try (final CloseableHttpResponse response = execute(headObjMethod)) { 552 assertEquals(OK.getStatusCode(), response.getStatusLine().getStatusCode()); 553 assertEquals(TEXT_PLAIN, response.getFirstHeader(CONTENT_TYPE).getValue()); 554 assertTrue(response.getHeaders(DIGEST).length > 0); 555 556 final String digesterHeaderValue = response.getHeaders(DIGEST)[0].getValue(); 557 assertTrue("SHA-1 Fixity Checksum doesn't match", 558 digesterHeaderValue.contains(TEST_SHA_DIGEST_HEADER_VALUE)); 559 assertTrue("MD5 fixity checksum doesn't match", 560 digesterHeaderValue.contains(TEST_MD5_DIGEST_HEADER_VALUE)); 561 } 562 } 563 564 @Test 565 public void testHeadDatastreamWithWantDigestMultipleOneUnsupported() throws IOException { 566 final String id = getRandomUniqueId(); 567 createDatastream(id, "x", TEST_BINARY_CONTENT); 568 569 final HttpHead headObjMethod = headObjMethod(id + "/x"); 570 headObjMethod.addHeader(WANT_DIGEST, "md5, Indigestion"); 571 try (final CloseableHttpResponse response = execute(headObjMethod)) { 572 assertEquals(OK.getStatusCode(), response.getStatusLine().getStatusCode()); 573 assertEquals(1, response.getHeaders(DIGEST).length); 574 final String digesterHeaderValue = response.getHeaders(DIGEST)[0].getValue(); 575 assertTrue("MD5 fixity checksum doesn't match", 576 digesterHeaderValue.contains(TEST_MD5_DIGEST_HEADER_VALUE)); 577 } 578 } 579 580 @Test 581 public void testHeadDatastreamWithWantDigestSha256() throws IOException { 582 final String id = getRandomUniqueId(); 583 createDatastream(id, "x", TEST_BINARY_CONTENT); 584 585 final HttpHead headObjMethod = headObjMethod(id + "/x"); 586 headObjMethod.addHeader(WANT_DIGEST, "sha-256"); 587 try (final CloseableHttpResponse response = execute(headObjMethod)) { 588 assertEquals(OK.getStatusCode(), response.getStatusLine().getStatusCode()); 589 assertEquals(TEXT_PLAIN, response.getFirstHeader(CONTENT_TYPE).getValue()); 590 assertTrue(response.getHeaders(DIGEST).length > 0); 591 592 final String digesterHeaderValue = response.getHeaders(DIGEST)[0].getValue(); 593 assertTrue("SHA-256 Fixity Checksum doesn't match", 594 digesterHeaderValue.contains(TEST_SHA256_DIGEST_HEADER_VALUE)); 595 } 596 } 597 598 @Test 599 public void testHeadRdfResourceHeaders() throws IOException { 600 final String id = getRandomUniqueId(); 601 createObject(id).close(); 602 603 final String location = serverAddress + id; 604 final HttpHead headObjMethod = headObjMethod(id); 605 try (final CloseableHttpResponse response = execute(headObjMethod)) { 606 assertEquals(OK.getStatusCode(), response.getStatusLine().getStatusCode()); 607 checkForLinkHeader(response, location + "/" + FCR_ACL, "acl"); 608 } 609 } 610 611 @Test 612 public void testHeadNonRdfHeaders() throws IOException { 613 final String id = getRandomUniqueId(); 614 final HttpPut put = putObjMethod(id, "text/plain", "<> a <http://example.com/Foo> ."); 615 executeAndClose(put); 616 617 final String location = serverAddress + id; 618 final HttpHead headObjMethod = headObjMethod(id); 619 try (final CloseableHttpResponse response = execute(headObjMethod)) { 620 assertEquals(OK.getStatusCode(), response.getStatusLine().getStatusCode()); 621 checkForLinkHeader(response, location + "/" + FCR_ACL, "acl"); 622 } 623 } 624 625 @Test 626 public void testOptions() throws IOException { 627 final String id = getRandomUniqueId(); 628 createObjectAndClose(id); 629 630 final HttpOptions optionsRequest = new HttpOptions(serverAddress + id); 631 try (final CloseableHttpResponse optionsResponse = execute(optionsRequest)) { 632 assertEquals(OK.getStatusCode(), optionsResponse.getStatusLine().getStatusCode()); 633 assertContainerOptionsHeaders(optionsResponse); 634 } 635 } 636 637 @Test 638 public void testOptionsBinary() throws IOException { 639 final String id = getRandomUniqueId(); 640 createDatastream(id, "x", id); 641 642 final HttpOptions optionsRequest = new HttpOptions(serverAddress + id + "/x"); 643 try (final CloseableHttpResponse optionsResponse = execute(optionsRequest)) { 644 assertEquals(OK.getStatusCode(), optionsResponse.getStatusLine().getStatusCode()); 645 assertResourceOptionsHeaders(optionsResponse); 646 assertEquals("0", optionsResponse.getFirstHeader(CONTENT_LENGTH).getValue()); 647 } 648 } 649 650 @Test 651 public void testOptionsBinaryMetadata() throws IOException { 652 final String id = getRandomUniqueId(); 653 createDatastream(id, "x", null); 654 655 final HttpOptions optionsRequest = new HttpOptions(serverAddress + id + "/x/fcr:metadata"); 656 try (final CloseableHttpResponse optionsResponse = execute(optionsRequest)) { 657 assertEquals(OK.getStatusCode(), optionsResponse.getStatusLine().getStatusCode()); 658 assertNonRdfResourceDescriptionOptionsHeaders(optionsResponse); 659 } 660 } 661 662 @Test 663 public void testOptionsBinaryMetadataWithUriEncoding() throws Exception { 664 final String id = getRandomUniqueId(); 665 createDatastream(id, "x", null); 666 final String location = serverAddress + id + "/x/fcr%3Ametadata"; 667 668 final HttpOptions optionsRequest = new HttpOptions(location); 669 try (final CloseableHttpResponse optionsResponse = execute(optionsRequest)) { 670 assertEquals(OK.getStatusCode(), optionsResponse.getStatusLine().getStatusCode()); 671 assertNonRdfResourceDescriptionOptionsHeaders(optionsResponse); 672 } 673 } 674 675 private static void assertContainerOptionsHeaders(final HttpResponse httpResponse) { 676 assertRdfOptionsHeaders(httpResponse); 677 final List<String> methods = headerValues(httpResponse, "Allow"); 678 assertTrue("Should allow POST", methods.contains(HttpPost.METHOD_NAME)); 679 680 final List<String> postTypes = headerValues(httpResponse, "Accept-Post"); 681 assertTrue("POST should support text/turtle", postTypes.contains(contentTypeTurtle)); 682 assertTrue("POST should support text/rdf+n3", postTypes.contains(contentTypeN3)); 683 assertTrue("POST should support text/n3", postTypes.contains(contentTypeN3Alt2)); 684 assertTrue("POST should support application/rdf+xml", postTypes.contains(contentTypeRDFXML)); 685 assertTrue("POST should support application/n-triples", postTypes.contains(contentTypeNTriples)); 686 687 final List<String> externalTypes = headerValues(httpResponse, "Accept-External-Content-Handling"); 688 assertTrue("COPY should be advertised for accepted external content.", externalTypes.contains(COPY)); 689 assertTrue("PROXY should be advertised for accepted external content.", externalTypes.contains(PROXY)); 690 assertTrue("REDIRECT should be advertised for accepted external content.", externalTypes.contains(REDIRECT)); 691 } 692 693 private static void assertRdfOptionsHeaders(final HttpResponse httpResponse) { 694 final List<String> methods = headerValues(httpResponse, "Allow"); 695 assertTrue("Should allow PATCH", methods.contains(HttpPatch.METHOD_NAME)); 696 assertTrue("Should allow DELETE", methods.contains("DELETE")); 697 assertTrue("Should allow HEAD", methods.contains("HEAD")); 698 699 final List<String> patchTypes = headerValues(httpResponse, "Accept-Patch"); 700 assertTrue("PATCH should support application/sparql-update", patchTypes.contains(contentTypeSPARQLUpdate)); 701 assertResourceOptionsHeaders(httpResponse); 702 } 703 704 private static void assertNonRdfResourceDescriptionOptionsHeaders(final HttpResponse httpResponse) { 705 final List<String> methods = headerValues(httpResponse, "Allow"); 706 assertTrue("Should allow PATCH", methods.contains(HttpPatch.METHOD_NAME)); 707 assertTrue("Should allow HEAD", methods.contains(HttpHead.METHOD_NAME)); 708 final List<String> patchTypes = headerValues(httpResponse, "Accept-Patch"); 709 assertTrue("PATCH should support application/sparql-update", patchTypes.contains(contentTypeSPARQLUpdate)); 710 assertResourceOptionsHeaders(httpResponse); 711 } 712 713 private static void assertResourceOptionsHeaders(final HttpResponse httpResponse) { 714 final List<String> methods = headerValues(httpResponse, "Allow"); 715 assertTrue("Should allow GET", methods.contains(HttpGet.METHOD_NAME)); 716 assertTrue("Should allow PUT", methods.contains(HttpPut.METHOD_NAME)); 717 assertTrue("Should allow DELETE", methods.contains(HttpDelete.METHOD_NAME)); 718 assertTrue("Should allow OPTIONS", methods.contains(HttpOptions.METHOD_NAME)); 719 } 720 721 @Test 722 public void testGetRDFSource() throws IOException { 723 final String id = getRandomUniqueId(); 724 createObjectAndClose(id); 725 726 final String location = serverAddress + id; 727 try (final CloseableHttpResponse response = execute(new HttpGet(serverAddress + id))) { 728 assertEquals(OK.getStatusCode(), getStatus(response)); 729 checkForLinkHeader(response, location + "/" + FCR_ACL, "acl"); 730 checkForLinkHeader(response, RDF_SOURCE.getURI(), "type"); 731 final HttpEntity entity = response.getEntity(); 732 final String contentType = parse(entity.getContentType().getValue()).getMimeType(); 733 assertNotNull("Entity is not an RDF serialization!", contentTypeToLang(contentType)); 734 } 735 } 736 737 @Test 738 public void testCreateContainerWithCharset() throws IOException { 739 final String id = getRandomUniqueId(); 740 741 final HttpPut put = putObjMethod(id, "text/turtle; charset=ISO-8859-1", "<> <http://test.org/title> 'hello'"); 742 put.setHeader(LINK, BASIC_CONTAINER_LINK_HEADER); 743 try (final CloseableHttpResponse response = execute(put)) { 744 assertEquals(CREATED.getStatusCode(), response.getStatusLine().getStatusCode()); 745 746 } 747 } 748 749 @Test 750 public void testCheckGetAclResourceHeaders() throws IOException { 751 final String aclUri = createAcl(); 752 753 try (final CloseableHttpResponse response = execute(new HttpGet(aclUri))) { 754 assertEquals(OK.getStatusCode(), getStatus(response)); 755 final Collection<String> links = getLinkHeaders(response); 756 final String aclLink = "<" + aclUri + "/" + FCR_ACL + ">;rel=\"acl\""; 757 assertFalse("ACL link header exists in ACL resource!", links.contains(aclLink)); 758 } 759 } 760 761 @Test 762 public void testGetRDFSourceWithPreferRepresentation() throws IOException { 763 final String id = getRandomUniqueId(); 764 createObjectAndClose(id); 765 766 final HttpGet getMethod = new HttpGet(serverAddress + id); 767 final String preferHeader = "return=representation;" 768 + " include=\"http://fedora.info/definitions/fcrepo#PreferInboundReferences\";" 769 + " omit=\"http://www.w3.org/ns/ldp#PreferMembership http://www.w3.org/ns/ldp#PreferContainment\""; 770 getMethod.addHeader("Prefer", preferHeader); 771 772 try (final CloseableHttpResponse response = execute(getMethod)) { 773 assertEquals(OK.getStatusCode(), getStatus(response)); 774 final Collection<String> preferenceApplied = getHeader(response, "Preference-Applied"); 775 assertTrue("Preference-Applied header doesn't matched", preferenceApplied.contains(preferHeader)); 776 } 777 } 778 779 @Test 780 public void testGetNonRDFSource() throws IOException { 781 final String id = getRandomUniqueId(); 782 createDatastream(id, "x", "some content"); 783 784 final String location = serverAddress + id + "/x"; 785 try (final CloseableHttpResponse response = execute(getDSMethod(id, "x"))) { 786 final HttpEntity entity = response.getEntity(); 787 final String content = EntityUtils.toString(entity); 788 assertEquals(OK.getStatusCode(), response.getStatusLine().getStatusCode()); 789 checkForLinkHeader(response, location + "/" + FCR_ACL, "acl"); 790 assertEquals("some content", content); 791 } 792 } 793 794 @Test 795 public void testGetNonRDFSourceDescription() throws IOException { 796 final String id = getRandomUniqueId(); 797 createDatastream(id, "x", "some content"); 798 799 final String location = serverAddress + id + "/x"; 800 try (final CloseableHttpResponse response = execute(getDSDescMethod(id, "x")); 801 final CloseableDataset dataset = getDataset(response)) { 802 checkForLinkHeader(response, location, "describes"); 803 checkForLinkHeader(response, location + "/" + FCR_ACL, "acl"); 804 final DatasetGraph graph = dataset.asDatasetGraph(); 805 final Node correctDSSubject = createURI(serverAddress + id + "/x"); 806 assertTrue("Binary should be a ldp:NonRDFSource", graph.contains(ANY, 807 correctDSSubject, rdfType, NON_RDF_SOURCE.asNode())); 808 // every triple in the response should have a subject of the actual resource described 809 LOGGER.info("Found graph:\n{}", graph); 810 graph.find().forEachRemaining(quad -> { 811 assertEquals("Found a triple with incorrect subject!", correctDSSubject, quad.getSubject()); 812 }); 813 } 814 } 815 816 @Test 817 public void testGetNonRDFSourceWithWantDigest() throws IOException { 818 final String id = getRandomUniqueId(); 819 createDatastream(id, "x", TEST_BINARY_CONTENT); 820 821 final HttpGet getMethod = getDSMethod(id, "x"); 822 getMethod.addHeader(WANT_DIGEST, "SHA"); 823 try (final CloseableHttpResponse response = execute(getMethod)) { 824 final HttpEntity entity = response.getEntity(); 825 final String content = EntityUtils.toString(entity); 826 assertEquals(OK.getStatusCode(), response.getStatusLine().getStatusCode()); 827 assertEquals(TEST_BINARY_CONTENT, content); 828 829 final String digesterHeaderValue = response.getHeaders(DIGEST)[0].getValue(); 830 assertTrue("Fixity Checksum doesn't match", 831 digesterHeaderValue.equals(TEST_SHA_DIGEST_HEADER_VALUE)); 832 } 833 } 834 835 @Test 836 public void testGetNonRDFSourceWithWantDigestMultiple() throws IOException { 837 final String id = getRandomUniqueId(); 838 createDatastream(id, "x", TEST_BINARY_CONTENT); 839 840 final HttpGet getMethod = getDSMethod(id, "x"); 841 getMethod.addHeader(WANT_DIGEST, "SHA,md5;q=0.3,sha-256;q=0.2"); 842 try (final CloseableHttpResponse response = execute(getMethod)) { 843 final HttpEntity entity = response.getEntity(); 844 final String content = EntityUtils.toString(entity); 845 assertEquals(OK.getStatusCode(), response.getStatusLine().getStatusCode()); 846 assertEquals(TEST_BINARY_CONTENT, content); 847 848 final String digesterHeaderValue = response.getHeaders(DIGEST)[0].getValue(); 849 assertTrue("SHA-1 Fixity Checksum doesn't match", 850 digesterHeaderValue.contains(TEST_SHA_DIGEST_HEADER_VALUE)); 851 assertTrue("MD5 fixity checksum doesn't match", 852 digesterHeaderValue.contains(TEST_MD5_DIGEST_HEADER_VALUE)); 853 assertTrue("SHA-256 fixity checksum doesn't match",digesterHeaderValue.contains( 854 "sha-256=fb871ff8cce8fea83dfaeab41784305a1461e008dc02a371ed26d856c766c903")); 855 } 856 } 857 858 /** 859 * Test that a 406 gets returned in the event of an invalid or unsupported 860 * format being requested. 861 */ 862 @Test 863 public void testGetRDFSourceWrongAccept() { 864 final String id = getRandomUniqueId(); 865 createObjectAndClose(id); 866 867 final HttpGet get = new HttpGet(serverAddress + id); 868 get.addHeader(ACCEPT, "application/turtle"); 869 870 assertEquals(NOT_ACCEPTABLE.getStatusCode(), getStatus(get)); 871 } 872 873 @Test 874 public void testGetRDFSourceWithUserTypes() throws IOException { 875 final String id = getRandomUniqueId(); 876 createObjectAndClose(id); 877 878 verifyPresenceOfUserTypeHeader(id); 879 } 880 881 @Test 882 public void testGetNonRDFSourceAndDescriptionWithUserTypes() throws IOException { 883 final String id = getRandomUniqueId(); 884 createDatastream(id, "ds", "sample-content"); 885 886 verifyPresenceOfUserTypeHeader(id + "/ds/" + FCR_METADATA); 887 } 888 889 private void verifyPresenceOfUserTypeHeader(final String id) throws IOException { 890 final HttpHead headMethod = new HttpHead(serverAddress + id); 891 892 final int numInitialHeaders; 893 try (final CloseableHttpResponse response = execute(headMethod)) { 894 assertEquals(headMethod.toString(), OK.getStatusCode(), response.getStatusLine().getStatusCode()); 895 numInitialHeaders = response.getAllHeaders().length; 896 } 897 898 // Add user type 899 final URI userType = URI.create("http://example.org/ObjectType"); 900 setProperty(id, type.toString(), userType); 901 902 try (final CloseableHttpResponse response = execute(headMethod)) { 903 assertEquals(headMethod.toString(), OK.getStatusCode(), response.getStatusLine().getStatusCode()); 904 905 // Should be an additional header from the previous GET request 906 assertEquals(numInitialHeaders + 1, response.getAllHeaders().length); 907 908 // Verify presence of user type 909 checkForLinkHeader(response, userType.toString(), "type"); 910 } 911 912 // Verify presence of user type on NonRDFSource... if applicable 913 if (id.endsWith(FCR_METADATA)) { 914 // Strip the trailing /fcr:metadata 915 final HttpHead headBinary = new HttpHead(serverAddress + id.replace("/" + FCR_METADATA, "")); 916 917 try (final CloseableHttpResponse response = execute(headBinary)) { 918 assertEquals(headBinary.toString(), OK.getStatusCode(), response.getStatusLine().getStatusCode()); 919 920 // Verify presence of user type 921 checkForLinkHeader(response, userType.toString(), "type"); 922 } 923 } 924 } 925 926 @Test 927 public void testDeleteObject() { 928 final String id = getRandomUniqueId(); 929 createObjectAndClose(id); 930 assertEquals(NO_CONTENT.getStatusCode(), getStatus(deleteObjMethod(id))); 931 assertDeleted(id); 932 } 933 934 @Test 935 public void testDeleteContainerWithDepthHeaderSet() { 936 final String id = getRandomUniqueId(); 937 createObjectAndClose(id); 938 final HttpDelete httpDelete = deleteObjMethod(id); 939 httpDelete.addHeader("Depth", "infinity"); 940 assertEquals(NO_CONTENT.getStatusCode(), getStatus(httpDelete)); 941 assertDeleted(id); 942 } 943 944 @Test 945 public void testDeleteContainerWithIncorrectDepthHeaderSet() { 946 final String id = getRandomUniqueId(); 947 createObjectAndClose(id); 948 final HttpDelete httpDelete = deleteObjMethod(id); 949 httpDelete.addHeader("Depth", "0"); 950 assertEquals(BAD_REQUEST.getStatusCode(), getStatus(httpDelete)); 951 assertNotDeleted(id); 952 } 953 954 @Test 955 public void testDeleteHierarchy() { 956 final String id = getRandomUniqueId(); 957 createObjectAndClose(id); 958 createObjectAndClose(id + "/foo"); 959 assertEquals(NO_CONTENT.getStatusCode(), getStatus(deleteObjMethod(id))); 960 assertDeleted(id); 961 assertDeleted(id + "/foo"); 962 } 963 964 @Test 965 public void testEmptyPatch() { 966 final String id = getRandomUniqueId(); 967 createObjectAndClose(id); 968 969 final HttpPatch patch = patchObjMethod(id); 970 patch.addHeader(CONTENT_TYPE, "application/sparql-update"); 971 assertEquals(BAD_REQUEST.getStatusCode(), getStatus(patch)); 972 } 973 974 @Test 975 public void testUpdateObjectGraph() throws IOException { 976 final String id = getRandomUniqueId(); 977 createObjectAndClose(id); 978 979 final String location = serverAddress + id; 980 final HttpPatch updateObjectGraphMethod = new HttpPatch(location); 981 updateObjectGraphMethod.addHeader(CONTENT_TYPE, "application/sparql-update"); 982 updateObjectGraphMethod.setEntity(new StringEntity("INSERT { <" + location + "> " + 983 "<http://purl.org/dc/elements/1.1/identifier> \"this is an identifier\" } WHERE {}")); 984 assertEquals(NO_CONTENT.getStatusCode(), getStatus(updateObjectGraphMethod)); 985 } 986 987 @Test 988 public void testDeleteMultipleMultiValuedProperties() throws IOException { 989 final String id = getRandomUniqueId(); 990 createObjectAndClose(id); 991 992 final String location = serverAddress + id; 993 final HttpPatch addTriplesGraphMethod = new HttpPatch(location); 994 addTriplesGraphMethod.addHeader(CONTENT_TYPE, "application/sparql-update"); 995 996 final String insertStatement = "INSERT DATA { \n" + 997 " <> <http://example.org/test/x> \"x\" . \n" + 998 " <> <http://example.org/test/a> \"1\" . \n" + 999 " <> <http://example.org/test/a> \"2\" . \n" + 1000 " <> <http://example.org/test/a> \"3\" . \n" + 1001 " <> <http://example.org/test/a> \"4\" . \n" + 1002 " <> <http://example.org/test/b> \"1\" . \n" + 1003 " <> <http://example.org/test/b> \"2\" . \n" + 1004 " <> <http://example.org/test/b> \"3\" . \n" + 1005 " <> <http://example.org/test/b> \"4\" . \n" + 1006 "}"; 1007 1008 addTriplesGraphMethod.setEntity(new StringEntity(insertStatement)); 1009 assertEquals(NO_CONTENT.getStatusCode(), getStatus(addTriplesGraphMethod)); 1010 1011 // ensure that the triples are there. 1012 try (final CloseableDataset dataset = getDataset(getObjMethod(id))) { 1013 final DatasetGraph graph = dataset.asDatasetGraph(); 1014 assertTrue("Didn't find a triple we expected!", graph.contains(ANY, 1015 createURI(location), createURI("http://example.org/test/x"), createLiteral("x"))); 1016 assertTrue("Didn't find a triple we expected!", graph.contains(ANY, 1017 createURI(location), createURI("http://example.org/test/a"), createLiteral("1"))); 1018 1019 } 1020 1021 final HttpPatch deleteQuery = new HttpPatch(location); 1022 deleteQuery.addHeader(CONTENT_TYPE, "application/sparql-update"); 1023 1024 final String deleteQueryStatement = "" + 1025 "DELETE \n" + 1026 "WHERE \n" + 1027 "{ \n" + 1028 " <> <http://example.org/test/a> ?a . \n" + 1029 " <> <http://example.org/test/b> ?b . \n" + 1030 "} "; 1031 1032 deleteQuery.setEntity(new StringEntity(deleteQueryStatement)); 1033 assertEquals(NO_CONTENT.getStatusCode(), getStatus(deleteQuery)); 1034 1035 // ensure that the expected triples removed. 1036 try (final CloseableDataset dataset = getDataset(getObjMethod(id))) { 1037 final DatasetGraph graph = dataset.asDatasetGraph(); 1038 assertTrue("Didn't find a triple we expected!", graph.contains(ANY, 1039 createURI(location), createURI("http://example.org/test/x"), createLiteral("x"))); 1040 for (int i = 0; i < 4; ++i) { 1041 for (final String suffix : Arrays.asList("a", "b")) { 1042 assertFalse("Found a triple we deleted!", graph.contains(ANY, 1043 createURI(location), createURI("http://example.org/test/" + suffix), createLiteral(i + 1044 ""))); 1045 } 1046 } 1047 } 1048 } 1049 1050 @Test 1051 public void testPatchBinary() throws IOException { 1052 final String id = getRandomUniqueId(); 1053 createDatastream(id, "x", "some content"); 1054 1055 final HttpPatch patch = patchObjMethod(id + "/x"); 1056 patch.addHeader(CONTENT_TYPE, "application/sparql-update"); 1057 assertEquals(BAD_REQUEST.getStatusCode(), getStatus(patch)); 1058 } 1059 1060 /** 1061 * Descriptions of bitstreams contain triples about the described thing, so only triples with the described thing 1062 * as their subject are legal. 1063 * 1064 * @throws IOException in case of IOException 1065 */ 1066 @Test 1067 public void testPatchBinaryDescription() throws IOException { 1068 final String id = getRandomUniqueId(); 1069 createDatastream(id, "x", "some content"); 1070 1071 final String location = serverAddress + id + "/x/fcr:metadata"; 1072 final HttpPatch patch = new HttpPatch(location); 1073 patch.addHeader(CONTENT_TYPE, "application/sparql-update"); 1074 patch.setEntity(new StringEntity("INSERT { <" + serverAddress + id + "/x> " 1075 + "<http://purl.org/dc/elements/1.1/identifier> \"this is an identifier\" } WHERE {}")); 1076 assertEquals(NO_CONTENT.getStatusCode(), getStatus(patch)); 1077 } 1078 1079 /** 1080 * Descriptions of bitstreams contain only triples about the described thing, so only triples with the described 1081 * thing as their subject are legal. 1082 * 1083 * @throws IOException on error 1084 */ 1085 @Test 1086 public void testPatchBinaryDescriptionWithBinaryProperties() throws IOException { 1087 final String id = getRandomUniqueId(); 1088 createDatastream(id, "x", "some content"); 1089 final String resource = serverAddress + id; 1090 final String location = resource + "/x/fcr:metadata"; 1091 1092 final HttpPatch patch = new HttpPatch(location); 1093 patch.addHeader(CONTENT_TYPE, "application/sparql-update"); 1094 patch.setEntity(new StringEntity("INSERT { <" + 1095 resource + "/x>" + " <" + DC_IDENTIFIER + "> \"identifier\" } WHERE {}")); 1096 try (final CloseableHttpResponse response = execute(patch)) { 1097 assertEquals(NO_CONTENT.getStatusCode(), getStatus(response)); 1098 try (final CloseableDataset dataset = getDataset(new HttpGet(location))) { 1099 final DatasetGraph graph = dataset.asDatasetGraph(); 1100 assertTrue(graph.contains(ANY, 1101 createURI(resource + "/x"), DC_IDENTIFIER, createLiteral("identifier"))); 1102 } 1103 } 1104 } 1105 1106 @Test 1107 public void testPatchBinaryNameAndType() throws IOException { 1108 final String pid = getRandomUniqueId(); 1109 1110 createDatastream(pid, "x", "some content"); 1111 1112 final String location = serverAddress + pid + "/x/fcr:metadata"; 1113 final HttpPatch patch = new HttpPatch(location); 1114 patch.addHeader(CONTENT_TYPE, "application/sparql-update"); 1115 patch.setEntity(new StringEntity("DELETE { " + 1116 "<" + serverAddress + pid + "/x> <" + HAS_MIME_TYPE + "> ?any . } " + 1117 "WHERE {" + 1118 "<" + serverAddress + pid + "/x> <" + HAS_MIME_TYPE + "> ?any . } ; " + 1119 "INSERT {" + 1120 "<" + serverAddress + pid + "/x> <" + HAS_MIME_TYPE + "> \"text/awesome\" ." + 1121 "<" + serverAddress + pid + "/x> <" + HAS_ORIGINAL_NAME + "> \"x.txt\" }" + 1122 "WHERE {}")); 1123 1124 try (final CloseableHttpResponse response = client.execute(patch)) { 1125 assertEquals(NO_CONTENT.getStatusCode(), getStatus(response)); 1126 try (final CloseableDataset dataset = getDataset(new HttpGet(location))) { 1127 final DatasetGraph graphStore = dataset.asDatasetGraph(); 1128 final Node subject = createURI(serverAddress + pid + "/x"); 1129 assertTrue(graphStore.contains(ANY, subject, HAS_MIME_TYPE.asNode(), createLiteral("text/awesome"))); 1130 assertTrue(graphStore.contains(ANY, subject, HAS_ORIGINAL_NAME.asNode(), createLiteral("x.txt"))); 1131 assertFalse("Should not contain old mime type property", graphStore.contains(ANY, 1132 subject, createURI(REPOSITORY_NAMESPACE + "mimeType"), ANY)); 1133 } 1134 } 1135 1136 // Ensure binary can be downloaded (test against regression of: https://jira.duraspace.org/browse/FCREPO-1720) 1137 assertEquals(OK.getStatusCode(), getStatus(getDSMethod(pid, "x"))); 1138 } 1139 1140 @Test 1141 public void testPatchWithBlankNode() throws Exception { 1142 final String id = getRandomUniqueId(); 1143 createObjectAndClose(id); 1144 1145 final String location = serverAddress + id; 1146 final HttpPatch updateObjectGraphMethod = patchObjMethod(id); 1147 updateObjectGraphMethod.addHeader(CONTENT_TYPE, "application/sparql-update"); 1148 updateObjectGraphMethod.setEntity(new StringEntity("INSERT { <" + 1149 location + "> <info:some-predicate> _:a .\n " + 1150 "_:a <http://purl.org/dc/elements/1.1/title> \"this is a title\"\n" + " } WHERE {}")); 1151 assertEquals(NO_CONTENT.getStatusCode(), getStatus(updateObjectGraphMethod)); 1152 1153 try (final CloseableDataset dataset = getDataset(new HttpGet(location))) { 1154 final DatasetGraph graphStore = dataset.asDatasetGraph(); 1155 assertTrue(graphStore.contains(ANY, createURI(location), createURI("info:some-predicate"), ANY)); 1156 final Node bnode = graphStore.find(ANY, 1157 createURI(location), createURI("info:some-predicate"), ANY).next().getObject(); 1158 assertTrue(graphStore.contains(ANY, bnode, DCTITLE, createLiteral("this is a title"))); 1159 } 1160 } 1161 1162 @Test 1163 public void testReplaceGraph() throws IOException { 1164 final String id = getRandomUniqueId(); 1165 createObjectAndClose(id); 1166 1167 final String subjectURI = serverAddress + id; 1168 final String initialContent; 1169 final HttpGet getMethod = getObjMethod(id); 1170 getMethod.addHeader("Prefer", "return=representation; omit=\"" + PREFER_SERVER_MANAGED + "\""); 1171 try (final CloseableHttpResponse subjectResponse = execute(getMethod)) { 1172 initialContent = EntityUtils.toString(subjectResponse.getEntity()); 1173 } 1174 final HttpPut replaceMethod = putObjMethod(id); 1175 replaceMethod.addHeader(CONTENT_TYPE, "text/n3"); 1176 replaceMethod 1177 .setEntity(new StringEntity(initialContent + "\n<" + subjectURI + "> <info:test#label> \"foo\"")); 1178 try (final CloseableHttpResponse response = execute(replaceMethod)) { 1179 assertEquals(NO_CONTENT.getStatusCode(), getStatus(response)); 1180 assertTrue("Didn't find Last-Modified header!", response.containsHeader("Last-Modified")); 1181 assertTrue("Didn't find ETag header!", response.containsHeader("ETag")); 1182 } 1183 try (final CloseableDataset dataset = getDataset(getObjMethod(id))) { 1184 final DatasetGraph graph = dataset.asDatasetGraph(); 1185 assertTrue("Didn't find a triple we tried to create!", graph.contains(ANY, 1186 createURI(subjectURI), createURI("info:test#label"), createLiteral("foo"))); 1187 } 1188 } 1189 1190 @Test 1191 public void testCreateGraph() throws IOException { 1192 final String subjectURI = serverAddress + getRandomUniqueId(); 1193 final HttpPut createMethod = new HttpPut(subjectURI); 1194 createMethod.addHeader(CONTENT_TYPE, "text/n3"); 1195 createMethod.setEntity(new StringEntity("<" + subjectURI + "> <info:test#label> \"foo\"")); 1196 assertEquals(CREATED.getStatusCode(), getStatus(createMethod)); 1197 1198 try (final CloseableDataset dataset = getDataset(new HttpGet(subjectURI))) { 1199 final DatasetGraph graph = dataset.asDatasetGraph(); 1200 assertTrue("Didn't find a triple we tried to create!", graph.contains(ANY, 1201 createURI(subjectURI), createURI("info:test#label"), createLiteral("foo"))); 1202 } 1203 } 1204 1205 @Test 1206 public void testCreateVersionedRDFResource() throws IOException { 1207 createVersionedRDFResource(); 1208 } 1209 1210 @Test 1211 public void testGetVersionedResourceHeaders() throws IOException { 1212 final String subjectURI = createVersionedRDFResource(); 1213 try (final CloseableHttpResponse response = execute(new HttpGet(subjectURI))) { 1214 verifyVersionedResourceResponseHeaders(subjectURI, response); 1215 checkForLinkHeader(response, RDF_SOURCE.getURI(), "type"); 1216 } 1217 } 1218 1219 @Test 1220 public void testHeadVersionedResourceHeaders() throws IOException { 1221 final String subjectURI = createVersionedRDFResource(); 1222 try (final CloseableHttpResponse response = execute(new HttpHead(subjectURI))) { 1223 verifyVersionedResourceResponseHeaders(subjectURI, response); 1224 } 1225 } 1226 1227 private void verifyVersionedResourceResponseHeaders(final String subjectURI, 1228 final CloseableHttpResponse response) { 1229 assertEquals("Didn't get an OK (200) response!", OK.getStatusCode(), getStatus(response)); 1230 checkForVersionedResourceLinkHeader(response); 1231 checkForMementoTimeGateLinkHeader(response); 1232 checkForLinkHeader(response, subjectURI, "original"); 1233 checkForLinkHeader(response, subjectURI, "timegate"); 1234 checkForLinkHeader(response, subjectURI + "/" + FCR_VERSIONS, "timemap"); 1235 checkForLinkHeader(response, subjectURI + "/" + FCR_ACL, "acl"); 1236 assertEquals(1, Arrays.stream(response.getHeaders("Vary")).filter(x -> x.getValue().contains( 1237 "Accept-Datetime")).count()); 1238 } 1239 1240 private String createVersionedRDFResource() throws IOException { 1241 final String id = getRandomUniqueId(); 1242 final String subjectURI = serverAddress + id; 1243 final HttpPost createMethod = postObjMethod(); 1244 createMethod.addHeader("Slug", id); 1245 createMethod.addHeader(CONTENT_TYPE, "text/n3"); 1246 createMethod.setEntity(new StringEntity("<" + subjectURI + "> <info:test#label> \"foo\"")); 1247 1248 try (final CloseableHttpResponse response = execute(createMethod)) { 1249 assertEquals("Didn't get a CREATED response!", CREATED.getStatusCode(), getStatus(response)); 1250 checkForVersionedResourceLinkHeader(response); 1251 } 1252 return subjectURI; 1253 } 1254 1255 private void checkForVersionedResourceLinkHeader(final CloseableHttpResponse response) { 1256 checkForLinkHeader(response, VERSIONED_RESOURCE.getURI(), "type"); 1257 } 1258 1259 private void checkForMementoTimeGateLinkHeader(final CloseableHttpResponse response) { 1260 checkForLinkHeader(response, VERSIONING_TIMEGATE_TYPE, "type"); 1261 } 1262 1263 @Test 1264 public void testCreateVersionedBinaryResource() throws IOException { 1265 final HttpPost method = postObjMethod(); 1266 final String id = getRandomUniqueId(); 1267 method.addHeader("Slug", id); 1268 method.addHeader(CONTENT_TYPE, "text/plain"); 1269 method.setEntity(new StringEntity("test content")); 1270 1271 method.addHeader(LINK, VERSIONED_RESOURCE_LINK_HEADER); 1272 try (final CloseableHttpResponse response = execute(method)) { 1273 assertEquals("Didn't get a CREATED response!", CREATED.getStatusCode(), getStatus(response)); 1274 checkForVersionedResourceLinkHeader(response); 1275 } 1276 } 1277 1278 private String createAcl() throws UnsupportedEncodingException { 1279 final String aclPid = "acl" + getRandomUniqueId(); 1280 final String aclURI = serverAddress + aclPid; 1281 createObjectAndClose(aclPid); 1282 final HttpPatch patch = patchObjMethod(aclPid); 1283 patch.addHeader(CONTENT_TYPE, "application/sparql-update"); 1284 // add webac:Acl type to aclURI 1285 patch.setEntity(new StringEntity( 1286 "INSERT { <> a <http://fedora.info/definitions/v4/webac#Acl> } WHERE {}")); 1287 assertEquals("Couldn't add webac:Acl type", NO_CONTENT.getStatusCode(), getStatus(patch)); 1288 return aclURI; 1289 } 1290 1291 @Test 1292 public void testCreateGraphWithBlanknodes() throws IOException { 1293 final String subjectURI = serverAddress + getRandomUniqueId(); 1294 final HttpPut createMethod = new HttpPut(subjectURI); 1295 createMethod.addHeader(CONTENT_TYPE, "text/n3"); 1296 createMethod.setEntity(new StringEntity("<" + subjectURI + "> <info:some-predicate> _:a ." + 1297 "_:a <info:test#label> \"asdfg\"")); 1298 assertEquals(CREATED.getStatusCode(), getStatus(createMethod)); 1299 1300 final HttpGet getObjMethod = new HttpGet(subjectURI); 1301 getObjMethod.addHeader(ACCEPT, "application/rdf+xml"); 1302 try (final CloseableDataset dataset = getDataset(getObjMethod)) { 1303 final DatasetGraph graph = dataset.asDatasetGraph(); 1304 final Iterator<Quad> quads = 1305 graph.find(ANY, createURI(subjectURI), createURI("info:some-predicate"), ANY); 1306 assertTrue("Didn't find skolemized blank node assertion", quads.hasNext()); 1307 final Node skolemizedNode = quads.next().getObject(); 1308 assertTrue("Didn't find a triple we tried to create!", graph.contains(ANY, 1309 skolemizedNode, createURI("info:test#label"), createLiteral("asdfg"))); 1310 } 1311 } 1312 1313 @Test 1314 public void testRoundTripReplaceGraph() throws IOException { 1315 1316 final String subjectURI = getLocation(postObjMethod()); 1317 final HttpGet getObjMethod = new HttpGet(subjectURI); 1318 getObjMethod.addHeader(ACCEPT, "text/turtle"); 1319 getObjMethod.addHeader("Prefer", "return=representation; omit=\"" + PREFER_SERVER_MANAGED + "\""); 1320 1321 final Model model = createDefaultModel(); 1322 try (final CloseableHttpResponse getResponse = execute(getObjMethod)) { 1323 model.read(getResponse.getEntity().getContent(), subjectURI, "TURTLE"); 1324 } 1325 final HttpPut replaceMethod = new HttpPut(subjectURI); 1326 replaceMethod.addHeader(CONTENT_TYPE, "application/n-triples"); 1327 try (final StringWriter w = new StringWriter()) { 1328 model.write(w, "N-TRIPLE"); 1329 replaceMethod.setEntity(new StringEntity(w.toString())); 1330 logger.trace("Retrieved object graph for testRoundTripReplaceGraph():\n {}", w); 1331 } 1332 assertEquals(NO_CONTENT.getStatusCode(), getStatus(replaceMethod)); 1333 } 1334 1335 @Test 1336 public void testPutBinary() throws IOException { 1337 1338 final String id = getRandomUniqueId(); 1339 createObjectAndClose(id); 1340 1341 final String location = serverAddress + id + "/x"; 1342 final HttpPut method = new HttpPut(location); 1343 method.setEntity(new StringEntity("foo")); 1344 method.addHeader(LINK, NON_RDF_SOURCE_LINK_HEADER); 1345 try (final CloseableHttpResponse response = execute(method)) { 1346 assertTrue("Didn't find Last-Modified header!", response.containsHeader("Last-Modified")); 1347 assertTrue("Didn't find ETag header!", response.containsHeader("ETag")); 1348 1349 final String receivedLocation = response.getFirstHeader("Location").getValue(); 1350 assertEquals(CREATED.getStatusCode(), getStatus(response)); 1351 assertEquals("Got wrong URI in Location header for datastream creation!", location, receivedLocation); 1352 1353 assertTrue("Didn't find Last-Modified header!", response.containsHeader("Last-Modified")); 1354 final String lastmod = response.getFirstHeader("Last-Modified").getValue(); 1355 assertNotNull("Should set Last-Modified for new nodes", lastmod); 1356 assertNotEquals("Last-Modified should not be blank for new nodes", lastmod.trim(), ""); 1357 final Link link = Link.valueOf(response.getFirstHeader(LINK).getValue()); 1358 assertEquals("describedby", link.getRel()); 1359 } 1360 } 1361 1362 @Test 1363 public void testPutBinaryChildViolation() throws IOException { 1364 final String id = getRandomUniqueId(); 1365 createObject(id); 1366 createDatastream(id, "binary", "some-content"); 1367 1368 final String location = serverAddress + id + "/binary/xx"; 1369 assertEquals("Should be a 409 Conflict!", CONFLICT.getStatusCode(), getStatus(new HttpPut(location))); 1370 } 1371 1372 @Test 1373 public void testBinaryEtags() throws IOException, InterruptedException { 1374 final String id = getRandomUniqueId(); 1375 createObjectAndClose(id); 1376 final String binaryLocation = serverAddress + id + "/binary"; 1377 final HttpPut method = putDSMethod(id, "binary", "foo"); 1378 1379 final String binaryEtag1, binaryEtag2, binaryEtag3, descEtag1, descEtag2, descEtag3; 1380 final String binaryLastModed1, binaryLastModed2, binaryLastModed3; 1381 final String descLastModed1, descLastModed2, descLastModed3; 1382 final String descLocation; 1383 1384 try (final CloseableHttpResponse response = execute(method)) { 1385 binaryEtag1 = response.getFirstHeader("ETag").getValue(); 1386 binaryLastModed1 = response.getFirstHeader("Last-Modified").getValue(); 1387 descLocation = Link.valueOf(response.getFirstHeader(LINK).getValue()).getUri().toString(); 1388 } 1389 1390 // First check ETags and Last-Modified headers for the binary 1391 final HttpGet get1 = new HttpGet(binaryLocation); 1392 get1.addHeader("If-None-Match", binaryEtag1); 1393 assertEquals("Expected 304 Not Modified", NOT_MODIFIED.getStatusCode(), getStatus(get1)); 1394 1395 final HttpGet get2 = new HttpGet(binaryLocation); 1396 get2.addHeader("If-Modified-Since", binaryLastModed1); 1397 assertEquals("Expected 304 Not Modified", NOT_MODIFIED.getStatusCode(), getStatus(get2)); 1398 1399 // Next, check ETags and Last-Modified headers on the description 1400 final HttpGet get3 = new HttpGet(descLocation); 1401 try (final CloseableHttpResponse response = execute(get3)) { 1402 descEtag1 = response.getFirstHeader("ETag").getValue(); 1403 descLastModed1 = response.getFirstHeader("Last-Modified").getValue(); 1404 } 1405 assertNotEquals("Binary, description ETags should be different", binaryEtag1, descEtag1); 1406 1407 final HttpGet get4 = new HttpGet(descLocation); 1408 get4.addHeader("If-None-Match", descEtag1); 1409 assertEquals("Expected 304 Not Modified", NOT_MODIFIED.getStatusCode(), getStatus(get4)); 1410 1411 final HttpGet get5 = new HttpGet(descLocation); 1412 get5.addHeader("If-Modified-Since", descLastModed1); 1413 assertEquals("Expected 304 Not Modified", NOT_MODIFIED.getStatusCode(), getStatus(get5)); 1414 1415 // Pause two seconds before updating the description 1416 sleep(2000); 1417 1418 // Next, update the description 1419 final HttpPatch httpPatch = patchObjMethod(id + "/binary/fcr:metadata"); 1420 assertTrue("Expected weak ETag", descEtag1.startsWith("W/")); 1421 httpPatch.addHeader(CONTENT_TYPE, "application/sparql-update"); 1422 httpPatch.addHeader("If-Match", descEtag1.substring(2)); 1423 httpPatch.setEntity(new StringEntity( 1424 "INSERT { <> <http://purl.org/dc/elements/1.1/title> 'this is a title' } WHERE {}")); 1425 assertEquals(NO_CONTENT.getStatusCode(), getStatus(httpPatch)); 1426 1427 // Next, check headers for the binary; they should not have changed 1428 final HttpHead head1 = new HttpHead(binaryLocation); 1429 try (final CloseableHttpResponse response = execute(head1)) { 1430 binaryEtag2 = response.getFirstHeader("ETag").getValue(); 1431 binaryLastModed2 = response.getFirstHeader("Last-Modified").getValue(); 1432 } 1433 1434 assertEquals("ETags should be the same", binaryEtag1, binaryEtag2); 1435 assertEquals("Last-Modified should be the same", binaryLastModed1, binaryLastModed2); 1436 1437 final HttpGet get6 = new HttpGet(binaryLocation); 1438 get6.addHeader("If-Match", binaryEtag1); 1439 assertEquals("Expected 200", OK.getStatusCode(), getStatus(get6)); 1440 1441 final HttpGet get7 = new HttpGet(binaryLocation); 1442 get7.addHeader("If-Unmodified-Since", binaryLastModed1); 1443 assertEquals("Expected 200", OK.getStatusCode(), getStatus(get7)); 1444 1445 // Next, check headers for the description; they should have changed 1446 final HttpHead head2 = new HttpHead(descLocation); 1447 try (final CloseableHttpResponse response = execute(head2)) { 1448 descEtag2 = response.getFirstHeader("ETag").getValue(); 1449 descLastModed2 = response.getFirstHeader("Last-Modified").getValue(); 1450 } 1451 1452 assertNotEquals("ETags should not be the same", descEtag1, descEtag2); 1453 assertNotEquals("Last-Modified should not be the same", descLastModed1, descLastModed2); 1454 1455 final HttpGet get8 = new HttpGet(descLocation); 1456 get8.addHeader("If-None-Match", descEtag2); 1457 assertEquals("Expected 304 Not Modified", NOT_MODIFIED.getStatusCode(), getStatus(get8)); 1458 1459 final HttpGet get9 = new HttpGet(descLocation); 1460 get9.addHeader("If-Modified-Since", descLastModed2); 1461 assertEquals("Expected 304 Not Modified", NOT_MODIFIED.getStatusCode(), getStatus(get9)); 1462 1463 sleep(1000); 1464 1465 // Next, update the binary itself 1466 final HttpPut method2 = new HttpPut(binaryLocation); 1467 assertFalse("Expected strong ETag", binaryEtag2.startsWith("W/")); 1468 method2.addHeader("If-Match", binaryEtag2); 1469 method2.setEntity(new StringEntity("foobar")); 1470 try (final CloseableHttpResponse response = execute(method2)) { 1471 assertEquals(NO_CONTENT.getStatusCode(), getStatus(response)); 1472 binaryEtag3 = response.getFirstHeader("ETag").getValue(); 1473 binaryLastModed3 = response.getFirstHeader("Last-Modified").getValue(); 1474 } 1475 1476 final HttpGet get10 = new HttpGet(binaryLocation); 1477 get10.addHeader("If-None-Match", binaryEtag2); 1478 assertEquals("Expected 200 OK", OK.getStatusCode(), getStatus(get10)); 1479 1480 final HttpGet get11 = new HttpGet(binaryLocation); 1481 get11.addHeader("If-Modified-Since", binaryLastModed1); 1482 assertEquals("Expected 200 OK", OK.getStatusCode(), getStatus(get11)); 1483 1484 assertNotEquals("ETags should have changed", binaryEtag1, binaryEtag3); 1485 assertNotEquals("Last-Modified should have changed", binaryLastModed1, binaryLastModed3); 1486 1487 // Next, check headers for the description; they should not have changed 1488 final HttpHead head3 = new HttpHead(descLocation); 1489 try (final CloseableHttpResponse response = execute(head3)) { 1490 descEtag3 = response.getFirstHeader("ETag").getValue(); 1491 descLastModed3 = response.getFirstHeader("Last-Modified").getValue(); 1492 } 1493 1494 assertEquals("ETags should have changed", descEtag2, descEtag3); 1495 assertEquals("Last-Modified should have changed", descLastModed2, descLastModed3); 1496 } 1497 1498 @Test 1499 public void testPutBinaryRdfChanges() throws Exception { 1500 final String id = getRandomUniqueId(); 1501 final Model model = createDefaultModel(); 1502 final Resource subject = model.createResource(serverAddress + id); 1503 final Property property = model.createProperty("http://purl.org/dcterms/", "title"); 1504 1505 // Create the binary 1506 createObjectAndClose(id, "<" + NON_RDF_SOURCE + ">; rel=\"type\""); 1507 1508 // Get the current description 1509 final HttpGet get = getObjMethod(id + "/" + FCR_METADATA); 1510 get.addHeader("Prefer", 1511 "return=representation; omit=\"http://fedora.info/definitions/v4/repository#ServerManaged\""); 1512 get.addHeader("Accept", "text/turtle"); 1513 try (final CloseableHttpResponse response = execute(get)) { 1514 assertEquals("Expected 200 OK", OK.getStatusCode(), getStatus(response)); 1515 model.read(response.getEntity().getContent(), null, "TURTLE"); 1516 } 1517 1518 // Put triples to the description. 1519 model.add(subject, property, model.createLiteral("ABC")); 1520 final ByteArrayOutputStream out1 = new ByteArrayOutputStream(); 1521 final Writer writer1 = new OutputStreamWriter(out1, "UTF-8"); 1522 model.write(writer1, "TURTLE"); 1523 1524 final HttpPut put = putObjMethod(id + "/" + FCR_METADATA); 1525 final String outputModel = out1.toString("UTF-8"); 1526 put.setEntity(new StringEntity(outputModel, "UTF-8")); 1527 put.setHeader("Content-type", "text/turtle"); 1528 put.setHeader("Prefer", "handling=lenient; received=\"minimal\""); 1529 assertEquals("Did put the binary description", NO_CONTENT.getStatusCode(), getStatus(put)); 1530 1531 // Get the description and verify the content. 1532 final Model model2 = createDefaultModel(); 1533 try (final CloseableHttpResponse response = execute(get)) { 1534 assertEquals("Expected 200 OK", OK.getStatusCode(), getStatus(response)); 1535 model2.read(response.getEntity().getContent(), serverAddress, "TURTLE"); 1536 final StmtIterator st = model.listStatements(subject, property, "ABC"); 1537 assertTrue(st.hasNext()); 1538 } 1539 1540 // Alter the triples. 1541 model2.remove(subject, property, model.createLiteral("ABC")); 1542 model2.add(subject, property, model.createLiteral("XYZ")); 1543 1544 // Put it again. 1545 final ByteArrayOutputStream out2 = new ByteArrayOutputStream(); 1546 final Writer writer2 = new OutputStreamWriter(out2, "UTF-8"); 1547 model2.write(writer2, "TURTLE"); 1548 final HttpPut put2 = putObjMethod(id + "/" + FCR_METADATA); 1549 put2.setHeader("Content-type", "text/turtle"); 1550 put2.setHeader("Prefer", "handling=lenient; received=\"minimal\""); 1551 put2.setEntity(new ByteArrayEntity(out2.toByteArray())); 1552 assertEquals("Did not update binary description", NO_CONTENT.getStatusCode(), getStatus(put2)); 1553 1554 // Get the description and verify the content again. 1555 final Model model3 = createDefaultModel(); 1556 try (final CloseableHttpResponse response = execute(get)) { 1557 assertEquals("Expected 200 OK", OK.getStatusCode(), getStatus(response)); 1558 model3.read(response.getEntity().getContent(), serverAddress, "TURTLE"); 1559 final StmtIterator st1 = model3.listStatements(subject, property, "XYZ"); 1560 assertTrue(st1.hasNext()); 1561 final StmtIterator st2 = model3.listStatements(subject, property, "ABC"); 1562 assertFalse(st2.hasNext()); 1563 } 1564 } 1565 1566 @Test 1567 public void testETagOnDeletedChild() throws Exception { 1568 final String id = getRandomUniqueId(); 1569 final String child = id + "/child"; 1570 createObjectAndClose(id); 1571 createObjectAndClose(child); 1572 1573 final HttpGet get = new HttpGet(serverAddress + id); 1574 final String etag1; 1575 try (final CloseableHttpResponse response = execute(get)) { 1576 etag1 = response.getFirstHeader("ETag").getValue(); 1577 } 1578 1579 // If the child is created and deleted in the same second the eTag would be the same. 1580 // Wait to delete. 1581 TimeUnit.SECONDS.sleep(1); 1582 1583 assertEquals("Child resource not deleted!", NO_CONTENT.getStatusCode(), 1584 getStatus(new HttpDelete(serverAddress + child))); 1585 final String etag2; 1586 try (final CloseableHttpResponse response = execute(get)) { 1587 etag2 = response.getFirstHeader("ETag").getValue(); 1588 } 1589 1590 assertNotEquals("ETag didn't change!", etag1, etag2); 1591 } 1592 1593 @Test 1594 public void testContainmentHashChanges() throws Exception { 1595 final String parentUri; 1596 final String createdEtag; 1597 // Create a resource and note its ETag 1598 try (final var response = execute(postObjMethod())) { 1599 assertEquals(CREATED.getStatusCode(), getStatus(response)); 1600 parentUri = getLocation(response); 1601 createdEtag = getEtag(response); 1602 } 1603 1604 // Create one child and see the parent's ETag changes 1605 final String oneChildEtag; 1606 assertEquals(CREATED.getStatusCode(), getStatus(new HttpPost(parentUri))); 1607 try (final var response = execute(new HttpGet(parentUri))) { 1608 oneChildEtag = getEtag(response); 1609 assertEquals(OK.getStatusCode(), getStatus(response)); 1610 assertNotEquals(createdEtag, oneChildEtag); 1611 } 1612 1613 // We use the last created time for the eTag, if 2 children are created in the same second there is no 1614 // difference, so we wait. 1615 TimeUnit.SECONDS.sleep(1); 1616 1617 // Create another child 1618 final String otherChild; 1619 try (final var response = execute(new HttpPost(parentUri))) { 1620 assertEquals(CREATED.getStatusCode(), getStatus(response)); 1621 otherChild = getLocation(response); 1622 } 1623 // See the parent's ETag changes again. 1624 final var currentETag = getEtag(parentUri); 1625 assertNotEquals(createdEtag, currentETag); 1626 assertNotEquals(oneChildEtag, currentETag); 1627 1628 TimeUnit.SECONDS.sleep(1); 1629 1630 // Delete the second child. 1631 assertEquals(NO_CONTENT.getStatusCode(), getStatus(new HttpDelete(otherChild))); 1632 // See the parent's ETag change again. 1633 final var currentETag2 = getEtag(parentUri); 1634 assertNotEquals(createdEtag, currentETag2); 1635 assertNotEquals(oneChildEtag, currentETag2); 1636 assertNotEquals(currentETag, currentETag2); 1637 1638 // Request parent with containment omitted, etag should change 1639 final var httpGetOmitContainment = new HttpGet(parentUri); 1640 final String preferHeader2 = "return=representation; omit=\"" + 1641 RdfLexicon.PREFER_CONTAINMENT.getURI() + "\""; 1642 httpGetOmitContainment.setHeader("Prefer", preferHeader2); 1643 final String omitEtag = getEtag(httpGetOmitContainment); 1644 assertEquals("Etag should match the etag before adding a child", createdEtag, omitEtag); 1645 assertNotEquals(oneChildEtag, omitEtag); 1646 assertNotEquals(currentETag, omitEtag); 1647 assertNotEquals(currentETag2, omitEtag); 1648 } 1649 1650 @Test 1651 public void testPutDatastreamContentOnObject() throws IOException { 1652 final String content = "foo"; 1653 final String id = getRandomUniqueId(); 1654 createObjectAndClose(id); 1655 1656 final HttpPut put = putObjMethod(id); 1657 put.setEntity(new StringEntity(content)); 1658 put.setHeader(CONTENT_TYPE, "application/octet-stream"); 1659 assertEquals( 1660 "Expected UNSUPPORTED MEDIA TYPE response when PUTing content to an object (as opposed to datastream)", 1661 UNSUPPORTED_MEDIA_TYPE.getStatusCode(), getStatus(put)); 1662 } 1663 1664 @Test 1665 public void testPutMalformedRDFOnObject() throws IOException { 1666 final String content = "this is not legitimate RDF"; 1667 final String id = getRandomUniqueId(); 1668 createObjectAndClose(id); 1669 1670 final HttpPut put = putObjMethod(id); 1671 put.setEntity(new StringEntity(content)); 1672 put.setHeader(CONTENT_TYPE, "text/plain"); 1673 assertEquals("Expected BAD REQUEST response code when PUTing malformed RDF on an object", 1674 BAD_REQUEST.getStatusCode(), getStatus(put)); 1675 } 1676 1677 @Test 1678 public void testIngest() throws IOException { 1679 final String id = getRandomUniqueId(); 1680 try (final CloseableHttpResponse response = createObject(id)) { 1681 final String content = EntityUtils.toString(response.getEntity()); 1682 assertIdentifierness(content); 1683 assertTrue("Didn't find Last-Modified header!", response.containsHeader("Last-Modified")); 1684 assertTrue("Didn't find ETag header!", response.containsHeader("ETag")); 1685 assertTrue("Didn't find Location header!", response.containsHeader("Location")); 1686 } 1687 } 1688 1689 @Test 1690 public void testIngestWithNewAndSparqlQuery() throws IOException { 1691 final HttpPost method = postObjMethod(); 1692 method.addHeader(CONTENT_TYPE, "application/sparql-update"); 1693 method.setEntity(new StringEntity( 1694 "INSERT { <> <http://purl.org/dc/elements/1.1/title> \"title\" } WHERE {}")); 1695 assertEquals(UNSUPPORTED_MEDIA_TYPE.getStatusCode(), getStatus(method)); 1696 1697 final String id = getRandomUniqueId(); 1698 final HttpPut putMethod = putObjMethod(id); 1699 putMethod.addHeader(CONTENT_TYPE, "application/sparql-update"); 1700 putMethod.setEntity(new StringEntity( 1701 "INSERT { <> <http://purl.org/dc/elements/1.1/title> \"title\" } WHERE {}")); 1702 assertEquals(UNSUPPORTED_MEDIA_TYPE.getStatusCode(), getStatus(putMethod)); 1703 } 1704 1705 @Test 1706 public void testIngestWithNewAndGraph() throws IOException { 1707 final HttpPost method = postObjMethod(); 1708 method.addHeader(CONTENT_TYPE, "text/n3"); 1709 method.setEntity(new StringEntity("<> <http://purl.org/dc/elements/1.1/title> \"title\".")); 1710 1711 try (final CloseableHttpResponse response = execute(method)) { 1712 assertEquals("Didn't get a CREATED response!", CREATED.getStatusCode(), getStatus(response)); 1713 assertTrue("Didn't find Last-Modified header!", response.containsHeader("Last-Modified")); 1714 final String lastmod = response.getFirstHeader("Last-Modified").getValue(); 1715 assertNotNull("Should set Last-Modified for new nodes", lastmod); 1716 assertNotEquals("Last-Modified should not be blank for new nodes", lastmod.trim(), ""); 1717 final String location = getLocation(response); 1718 try (final CloseableDataset dataset = getDataset(new HttpGet(location))) { 1719 final DatasetGraph graphStore = dataset.asDatasetGraph(); 1720 assertTrue(graphStore.contains(ANY, createURI(location), DCTITLE, createLiteral("title"))); 1721 } 1722 } 1723 } 1724 1725 @Test 1726 public void testIngestWithSlug() throws IOException { 1727 final HttpPost method = postObjMethod(); 1728 method.addHeader("Slug", getRandomUniqueId()); 1729 try (final CloseableHttpResponse response = execute(method)) { 1730 final String content = EntityUtils.toString(response.getEntity()); 1731 assertEquals("Didn't get a CREATED response!", CREATED.getStatusCode(), getStatus(response)); 1732 assertIdentifierness(content); 1733 final String location = getLocation(response); 1734 assertNotEquals(serverAddress + "/objects", location); 1735 assertEquals("Object wasn't created!", OK.getStatusCode(), getStatus(new HttpGet(location))); 1736 } 1737 } 1738 1739 @Test 1740 public void testIngestWithRepeatedSlug() { 1741 final String id = getRandomUniqueId(); 1742 assertEquals(CREATED.getStatusCode(), getStatus(putObjMethod(id))); 1743 final HttpPost method = postObjMethod(); 1744 method.addHeader("Slug", id); 1745 assertEquals(CREATED.getStatusCode(), getStatus(method)); 1746 } 1747 1748 // TODO this was copied from extant tests as a refactoring, but is this a good test for identifier-ness? 1749 public static void assertIdentifierness(final String content) { 1750 assertTrue("Response wasn't a PID", compile("[a-z]+").matcher(content).find()); 1751 } 1752 1753 @Test 1754 public void testIngestWithBinary() throws IOException { 1755 final HttpPost method = postObjMethod(); 1756 method.addHeader(CONTENT_TYPE, "application/octet-stream"); 1757 method.addHeader(LINK, NON_RDF_SOURCE_LINK_HEADER); 1758 final String binaryContent = "xyz"; 1759 method.setEntity(new StringEntity(binaryContent)); 1760 1761 try (final CloseableHttpResponse response = execute(method)) { 1762 final String content = EntityUtils.toString(response.getEntity()); 1763 final int status = getStatus(response); 1764 assertEquals("Didn't get a CREATED response! Got content:\n" + content, CREATED.getStatusCode(), status); 1765 assertIdentifierness(content); 1766 final String location = getLocation(response); 1767 1768 try (final CloseableHttpResponse getResponse = execute(new HttpGet(location))) { 1769 assertEquals("Object wasn't created!", OK.getStatusCode(), getResponse.getStatusLine().getStatusCode()); 1770 final String resp = IOUtils.toString(getResponse.getEntity().getContent(), UTF_8); 1771 assertEquals("application/octet-stream", getResponse.getFirstHeader(CONTENT_TYPE).getValue()); 1772 assertEquals(binaryContent, resp); 1773 } 1774 1775 final Link link = Link.valueOf(response.getFirstHeader(LINK).getValue()); 1776 1777 assertEquals("describedby", link.getRel()); 1778 assertTrue("Expected an anchor to the newly created resource", link.getParams().containsKey("anchor")); 1779 assertEquals("Expected anchor at the newly created resource", location, 1780 link.getParams().get("anchor")); 1781 assertEquals("Expected describedBy link", location + "/" + FCR_METADATA, link.getUri().toString()); 1782 } 1783 } 1784 1785 /* Verifies RDF persisted as binary is retrieved with byte-for-byte fidelity */ 1786 @Test 1787 public void testIngestOpaqueRdfAsBinary() throws IOException { 1788 final HttpPost method = postObjMethod(); 1789 method.addHeader(CONTENT_TYPE, "application/n-triples"); 1790 method.addHeader(LINK, NON_RDF_SOURCE_LINK_HEADER); 1791 1792 final String rdf = "<test:/subject> <test:/predicate> <test:/object> ."; 1793 method.setEntity(new StringEntity(rdf)); 1794 1795 try (final CloseableHttpResponse response = execute(method)) { 1796 assertEquals(CREATED.getStatusCode(), getStatus(response)); 1797 1798 final String location = getLocation(response); 1799 final HttpGet get = new HttpGet(location); 1800 1801 try (final CloseableHttpResponse getResponse = execute(get)) { 1802 final String resp = IOUtils.toString(getResponse.getEntity().getContent(), UTF_8); 1803 assertEquals("application/n-triples", getResponse.getFirstHeader(CONTENT_TYPE).getValue()); 1804 assertEquals(rdf, resp); 1805 } 1806 } 1807 } 1808 1809 /** 1810 * Ensure that the objects can be created with a Digest header 1811 * with a SHA1 sum of the binary content 1812 */ 1813 @Test 1814 public void testIngestWithBinaryAndChecksum() { 1815 final HttpPost method = postObjMethod(); 1816 final File img = new File("src/test/resources/test-objects/img.png"); 1817 method.addHeader(CONTENT_TYPE, "application/octet-stream"); 1818 method.addHeader("Digest", "SHA=f0b632679fab4f22e031010bd81a3b0544294730"); 1819 method.addHeader(LINK, NON_RDF_SOURCE_LINK_HEADER); 1820 method.setEntity(new FileEntity(img)); 1821 1822 assertEquals("Didn't get a CREATED response!", CREATED.getStatusCode(), getStatus(method)); 1823 } 1824 1825 /** 1826 * Ensure that the objects cannot be created when a Digest header 1827 * contains a SHA1 sum that does not match the uploaded binary 1828 * content 1829 */ 1830 @Test 1831 public void testIngestWithBinaryAndChecksumMismatch() { 1832 final HttpPost method = postObjMethod(); 1833 final File img = new File("src/test/resources/test-objects/img.png"); 1834 method.addHeader(CONTENT_TYPE, "application/octet-stream"); 1835 method.addHeader("Digest", "SHA=fedoraicon"); 1836 method.addHeader(LINK, NON_RDF_SOURCE_LINK_HEADER); 1837 method.setEntity(new FileEntity(img)); 1838 1839 assertEquals("Should be a 409 Conflict!", CONFLICT.getStatusCode(), getStatus(method)); 1840 } 1841 1842 /** 1843 * Ensure that the a malformed Digest header returns a 400 Bad Request 1844 */ 1845 @Test 1846 public void testIngestWithBinaryAndMalformedDigestHeader() { 1847 final HttpPost method = postObjMethod(); 1848 final File img = new File("src/test/resources/test-objects/img.png"); 1849 method.addHeader(CONTENT_TYPE, "application/octet-stream"); 1850 method.addHeader("Digest", "md5=not a valid hash,SHA:thisisbadtoo"); 1851 method.addHeader(LINK, NON_RDF_SOURCE_LINK_HEADER); 1852 method.setEntity(new FileEntity(img)); 1853 assertEquals("Should be a 400 BAD REQUEST!", BAD_REQUEST.getStatusCode(), getStatus(method)); 1854 } 1855 1856 /** 1857 * Ensure that a non-SHA1 Digest header returns a 409 Conflict 1858 */ 1859 @Test 1860 public void testIngestWithBinaryAndNonSha1DigestHeader() { 1861 final HttpPost method = postObjMethod(); 1862 final File img = new File("src/test/resources/test-objects/img.png"); 1863 method.addHeader(CONTENT_TYPE, "application/octet-stream"); 1864 method.addHeader("Digest", "md5=anything"); 1865 method.addHeader(LINK, NON_RDF_SOURCE_LINK_HEADER); 1866 method.setEntity(new FileEntity(img)); 1867 1868 assertEquals("Should be a 409 Conflict!", CONFLICT.getStatusCode(), getStatus(method)); 1869 } 1870 1871 @Test 1872 public void testIngestWithBinaryAndMD5DigestHeader() { 1873 final HttpPost method = postObjMethod(); 1874 final File img = new File("src/test/resources/test-objects/img.png"); 1875 method.addHeader(CONTENT_TYPE, "application/octet-stream"); 1876 method.addHeader("Digest", "md5=6668675a91f39ca1afe46c084e8406ba"); 1877 method.addHeader(LINK, NON_RDF_SOURCE_LINK_HEADER); 1878 method.setEntity(new FileEntity(img)); 1879 1880 assertEquals("Should be a 201 Created!", CREATED.getStatusCode(), getStatus(method)); 1881 } 1882 1883 @Test 1884 public void testIngestWithBinaryAndTwoValidHeadersDigestHeaders() throws Exception { 1885 final HttpPost method = postObjMethod(); 1886 final File img = new File("src/test/resources/test-objects/img.png"); 1887 final var md5 = "6668675a91f39ca1afe46c084e8406ba"; 1888 final var sha256 = "7b115a72978fe138287c1a6dfe6cc1afce4720fb3610a81d32e4ad518700c923"; 1889 method.addHeader(CONTENT_TYPE, "application/octet-stream"); 1890 method.addHeader("Digest", "md5=" + md5 + "," + 1891 " sha-256=" + sha256); 1892 method.setEntity(new FileEntity(img)); 1893 method.addHeader(LINK, NON_RDF_SOURCE_LINK_HEADER); 1894 1895 final String location; 1896 try (final CloseableHttpResponse response = execute(method)) { 1897 assertEquals("Should be a 201 Created!", CREATED.getStatusCode(), getStatus(method)); 1898 location = getLocation(response); 1899 } 1900 1901 // verify that both headers are returned in the metadata. 1902 try (final var response = execute(new HttpGet(location + "/" + FCR_METADATA))) { 1903 try (final var dataset = getDataset(response)) { 1904 final var graph = dataset.asDatasetGraph(); 1905 final var model = createModelForGraph(graph.getDefaultGraph()); 1906 final var nodeUri = createResource(location); 1907 final var stmts = model.listStatements(nodeUri, HAS_MESSAGE_DIGEST, (String) null); 1908 final var digests = new ArrayList<String>(); 1909 while (stmts.hasNext()) { 1910 digests.add(stmts.nextStatement().getObject().asResource().getURI()); 1911 } 1912 assertTrue("contains md5", digests.contains("urn:md5:" + md5)); 1913 assertTrue("contains sha-256", digests.contains("urn:sha-256:" + sha256)); 1914 } 1915 } 1916 } 1917 1918 @Test 1919 public void testIngestWithBinaryAndValidAndInvalidDigestHeaders() { 1920 final HttpPost method = postObjMethod(); 1921 final File img = new File("src/test/resources/test-objects/img.png"); 1922 method.addHeader(CONTENT_TYPE, "application/octet-stream"); 1923 method.addHeader("Digest", "md5=6668675a91f39ca1afe46c084e8406ba," + 1924 " sha99=7b115a72978fe138287c1a6dfe6cc1afce4720fb3610a81d32e4ad518700c923"); 1925 method.setEntity(new FileEntity(img)); 1926 method.addHeader(LINK, NON_RDF_SOURCE_LINK_HEADER); 1927 1928 assertEquals("Should be a 400 Bad Request!", BAD_REQUEST.getStatusCode(), getStatus(method)); 1929 } 1930 1931 @Test 1932 public void testContentDispositionHeader() throws ParseException, IOException { 1933 final HttpPost method = postObjMethod(); 1934 final File img = new File("src/test/resources/test-objects/img.png"); 1935 final String filename = "some-file.png"; 1936 method.addHeader(CONTENT_TYPE, "application/png"); 1937 method.addHeader(CONTENT_DISPOSITION, "attachment; filename=\"" + filename + "\""); 1938 method.setEntity(new FileEntity(img)); 1939 method.addHeader(LINK, NON_RDF_SOURCE_LINK_HEADER); 1940 1941 // Create a binary resource with content-disposition 1942 final String location; 1943 try (final CloseableHttpResponse response = execute(method)) { 1944 assertEquals("Should be a 201 Created!", CREATED.getStatusCode(), getStatus(response)); 1945 location = getLocation(response); 1946 } 1947 1948 // Retrieve the new resource and verify the content-disposition 1949 verifyContentDispositionFilename(location, filename); 1950 1951 // TODO enable once PATCH is working 1952 // DO WE WANT TO ALTER THE USER'S RDF BY ADDING THE TRIPLES? 1953 // Update the filename 1954 // final String filename1 = "new-file.png"; 1955 // final HttpPatch patch = new HttpPatch(location + "/" + FCR_METADATA); 1956 // patch.setHeader(CONTENT_TYPE, "application/sparql-update"); 1957 // final String updateString = "PREFIX ebucore: <http://www.ebu.ch/metadata/ontologies/ebucore/ebucore#>\n" + 1958 // "DELETE { <> ebucore:filename ?x}\n" + 1959 // "INSERT { <> ebucore:filename \"" + filename1 + "\"}\n" + 1960 // "WHERE { <> ebucore:filename ?x}"; 1961 // 1962 // patch.setEntity(new StringEntity(updateString)); 1963 // assertEquals(location, NO_CONTENT.getStatusCode(), getStatus(patch)); 1964 // 1965 // // Retrieve the new resource and verify the content-disposition 1966 // verifyContentDispositionFilename(location, filename1); 1967 } 1968 1969 private void verifyContentDispositionFilename(final String location, final String filename) 1970 throws IOException, ParseException { 1971 final HttpHead head = new HttpHead(location); 1972 try (final CloseableHttpResponse headResponse = execute(head)) { 1973 final Header header = headResponse.getFirstHeader(CONTENT_DISPOSITION); 1974 assertNotNull(header); 1975 1976 final ContentDisposition contentDisposition = new ContentDisposition(header.getValue()); 1977 assertEquals(filename, contentDisposition.getFileName()); 1978 } 1979 } 1980 1981 @Test 1982 public void testIngestOnSubtree() throws IOException { 1983 final String id = getRandomUniqueId(); 1984 createObjectAndClose(id); 1985 final HttpPost method = postObjMethod(id); 1986 method.addHeader("Slug", "x"); 1987 assertEquals(serverAddress + id + "/x", getLocation(method)); 1988 } 1989 1990 @Test 1991 public void testIngestWithRDFLang() throws IOException { 1992 final HttpPost method = postObjMethod(); 1993 method.addHeader(CONTENT_TYPE, "text/n3"); 1994 method.setEntity(new StringEntity("<> <http://purl.org/dc/elements/1.1/title> \"french title\"@fr ." 1995 + "<> <http://purl.org/dc/elements/1.1/title> \"english title\"@en .")); 1996 1997 try (final CloseableHttpResponse response = execute(method)) { 1998 assertEquals("Didn't get a CREATED response!", CREATED.getStatusCode(), getStatus(response)); 1999 final String location = getLocation(response); 2000 try (final CloseableDataset dataset = getDataset(new HttpGet(location))) { 2001 final DatasetGraph graphStore = dataset.asDatasetGraph(); 2002 final Node subj = createURI(location); 2003 assertTrue(graphStore.contains(ANY, subj, DCTITLE, createLiteral("english title", "en", false))); 2004 assertTrue(graphStore.contains(ANY, subj, DCTITLE, createLiteral("french title", "fr", false))); 2005 } 2006 } 2007 } 2008 2009 @Test 2010 public void testDeleteWithBadEtag() throws IOException { 2011 try (final CloseableHttpResponse response = execute(postObjMethod())) { 2012 assertEquals(CREATED.getStatusCode(), getStatus(response)); 2013 final HttpDelete request = new HttpDelete(getLocation(response)); 2014 request.addHeader("If-Match", "\"doesnt-match\""); 2015 assertEquals(PRECONDITION_FAILED.getStatusCode(), getStatus(request)); 2016 } 2017 } 2018 2019 @Test 2020 public void testGetDatastream() throws IOException, ParseException { 2021 final String id = getRandomUniqueId(); 2022 createObjectAndClose(id); 2023 createDatastream(id, "ds1", "foo"); 2024 2025 try (final CloseableHttpResponse response = execute(getDSMethod(id, "ds1"))) { 2026 assertEquals("Wasn't able to retrieve a datastream!", OK.getStatusCode(), getStatus(response)); 2027 assertEquals(TEXT_PLAIN, response.getFirstHeader(CONTENT_TYPE).getValue()); 2028 assertEquals("3", response.getFirstHeader(CONTENT_LENGTH).getValue()); 2029 assertEquals("bytes", response.getFirstHeader("Accept-Ranges").getValue()); 2030 final ContentDisposition disposition = 2031 new ContentDisposition(response.getFirstHeader(CONTENT_DISPOSITION).getValue()); 2032 assertEquals("attachment", disposition.getType()); 2033 2034 final Collection<String> links = getLinkHeaders(response); 2035 final String describedByHeader = 2036 "<" + serverAddress + id + "/ds1/" + FCR_METADATA + ">; rel=\"describedby\""; 2037 assertTrue("Didn't find 'describedby' link header!", links.contains(describedByHeader)); 2038 } 2039 } 2040 2041 @Test 2042 public void testGetLongRange() throws IOException { 2043 final String id = getRandomUniqueId(); 2044 createObjectAndClose(id); 2045 final StringBuilder buf = new StringBuilder(); 2046 while ( buf.length() < 9000 ) { 2047 buf.append("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); 2048 } 2049 createDatastream(id, "ds1", buf.toString()); 2050 2051 final HttpGet get = getDSMethod(id, "ds1"); 2052 get.setHeader("Range", "bytes=0-8199"); 2053 try (final CloseableHttpResponse response = execute(get)) { 2054 assertEquals("Expected 206 Partial Content!", PARTIAL_CONTENT.getStatusCode(), getStatus(response)); 2055 assertEquals("Expected range length (8200)!", "8200", response.getFirstHeader(CONTENT_LENGTH).getValue()); 2056 } 2057 } 2058 2059 @Test 2060 public void testDeleteDatastream() throws IOException { 2061 final String id = getRandomUniqueId(); 2062 createObjectAndClose(id); 2063 createDatastream(id, "ds1", "foo"); 2064 assertEquals(NO_CONTENT.getStatusCode(), getStatus(deleteObjMethod(id + "/ds1"))); 2065 assertDeleted(id + "/ds1"); 2066 } 2067 2068 @Test 2069 public void testDeleteBinaryDescription() throws IOException { 2070 final String id = getRandomUniqueId(); 2071 createObjectAndClose(id, NON_RDF_SOURCE_LINK_HEADER); 2072 2073 // Check that the binary and description exist. 2074 binaryExists(id); 2075 2076 // Try to delete the description. 2077 final HttpDelete deleteBinaryDesc = deleteObjMethod(id + "/" + FCR_METADATA); 2078 try (final CloseableHttpResponse response = execute(deleteBinaryDesc)) { 2079 assertEquals(METHOD_NOT_ALLOWED.getStatusCode(), getStatus(response)); 2080 } 2081 2082 // ensure the binary and description still exist. 2083 binaryExists(id); 2084 2085 // Delete the binary 2086 final HttpDelete deleteBinary = deleteObjMethod(id); 2087 try (final CloseableHttpResponse response = execute(deleteBinary)) { 2088 assertEquals(NO_CONTENT.getStatusCode(), getStatus(response)); 2089 } 2090 2091 // Ensure the binary and description are gone. 2092 binaryDoesntExist(id); 2093 2094 } 2095 2096 /** 2097 * Check if a binary and its description exist. 2098 * 2099 * @param id id of the binary. 2100 * @throws IOException on error with http communication. 2101 */ 2102 private void binaryExists(final String id) throws IOException { 2103 binaryStatus(id, OK); 2104 } 2105 2106 /** 2107 * Check if a binary and its description don't exist. 2108 * 2109 * @param id id of the binary. 2110 * @throws IOException on error with http communication. 2111 */ 2112 private void binaryDoesntExist(final String id) throws IOException { 2113 binaryStatus(id, GONE); 2114 } 2115 2116 /** 2117 * Utility function to confirm status of both a binary and its description 2118 * 2119 * @param id id of the binary. 2120 * @param status the expected status. 2121 * @throws IOException on error with http communication. 2122 */ 2123 private void binaryStatus(final String id, final Status status) throws IOException { 2124 final HttpHead headBinary = headObjMethod(id); 2125 final var tombstoneUri = serverAddress + id + "/" + FCR_TOMBSTONE; 2126 try (final CloseableHttpResponse response = execute(headBinary)) { 2127 assertEquals(status.getStatusCode(), getStatus(response)); 2128 if (status.equals(GONE)) { 2129 checkForLinkHeader(response, tombstoneUri, "hasTombstone"); 2130 } 2131 } 2132 2133 final HttpHead headBinaryDesc = headObjMethod(id + "/" + FCR_METADATA); 2134 try (final CloseableHttpResponse response = execute(headBinaryDesc)) { 2135 assertEquals(status.getStatusCode(), getStatus(response)); 2136 if (status.equals(GONE)) { 2137 checkForLinkHeader(response, tombstoneUri, "hasTombstone"); 2138 } 2139 } 2140 } 2141 2142 @Test 2143 public void testGetObjectGraphHtml() throws IOException { 2144 final HttpGet getObjMethod = new HttpGet(getLocation(postObjMethod())); 2145 getObjMethod.addHeader(ACCEPT, "text/html"); 2146 assertEquals(OK.getStatusCode(), getStatus(getObjMethod)); 2147 } 2148 2149 @Test 2150 public void testGetObjectGraphVariants() throws IOException { 2151 final String location = getLocation(postObjMethod()); 2152 for (final Variant variant : POSSIBLE_RDF_VARIANTS) { 2153 final HttpGet getObjMethod = new HttpGet(location); 2154 final String type = variant.getMediaType().getType(); 2155 getObjMethod.addHeader(ACCEPT, type); 2156 assertEquals("Got bad response for type " + type + " !", OK.getStatusCode(), getStatus(getObjMethod)); 2157 } 2158 } 2159 2160 @Test 2161 public void testGetObjectGraph() throws IOException { 2162 logger.trace("Entering testGetObjectGraph()..."); 2163 final String location = getLocation(postObjMethod()); 2164 final HttpGet getObjMethod = new HttpGet(location); 2165 2166 try (final CloseableHttpResponse response = execute(getObjMethod)) { 2167 assertEquals(OK.getStatusCode(), getStatus(response)); 2168 assertResourceOptionsHeaders(response); 2169 checkForLinkHeader(response, RDF_SOURCE.getURI(), "type"); 2170 assertTrue("Didn't find LDP link header!", getLinkHeaders(response).contains(LDP_RESOURCE_LINK_HEADER)); 2171 checkForLinkHeader(response, location + "/" + FCR_ACL, "acl"); 2172 try (final CloseableDataset dataset = getDataset(response)) { 2173 assertTrue("Didn't find any type triples!", dataset.asDatasetGraph().contains(ANY, 2174 createURI(location), rdfType, ANY)); 2175 } 2176 logger.trace("Leaving testGetObjectGraph()..."); 2177 } 2178 } 2179 2180 @Test 2181 public void verifyFullSetOfRdfTypes() throws IOException { 2182 logger.trace("Entering verifyFullSetOfRdfTypes()..."); 2183 final String id = getRandomUniqueId(); 2184 createObjectAndClose(id); 2185 2186 try (final CloseableDataset dataset = getDataset(getObjMethod(id))) { 2187 final DatasetGraph graph = dataset.asDatasetGraph(); 2188 final Node resource = createURI(serverAddress + id); 2189 verifyResource(graph, resource, REPOSITORY_NAMESPACE, "Container"); 2190 verifyResource(graph, resource, REPOSITORY_NAMESPACE, "Resource"); 2191 } 2192 logger.trace("Leaving verifyFullSetOfRdfTypes()..."); 2193 } 2194 2195 private static void verifyResource(final DatasetGraph g, final Node subject, final String ns, final String type) { 2196 assertTrue("Should find type: " + ns + type, g.contains(ANY, subject, rdfType, createURI(ns + type))); 2197 } 2198 2199 @Test 2200 public void testGetObjectGraphWithChild() throws IOException { 2201 final String id = getRandomUniqueId(); 2202 final String location = getLocation(createObject(id)); 2203 createObjectAndClose(id + "/c"); 2204 2205 try (final CloseableHttpResponse response = execute(getObjMethod(id))) { 2206 try (final CloseableDataset dataset = getDataset(response)) { 2207 assertTrue("Didn't find child node!", dataset.asDatasetGraph().contains(ANY, 2208 createURI(location), CONTAINS.asNode(), createURI(location + "/c"))); 2209 2210 final Collection<String> links = getLinkHeaders(response); 2211 assertTrue("Didn't find LDP resource link header!", links.contains(LDP_RESOURCE_LINK_HEADER)); 2212 } 2213 } 2214 } 2215 2216 @Test 2217 public void testGetObjectGraphWithChildAndRemove() throws IOException { 2218 final String id = getRandomUniqueId(); 2219 final String location = getLocation(createObject(id)); 2220 createObjectAndClose(id + "/c"); 2221 try (final CloseableHttpResponse response = execute(getObjMethod(id))) { 2222 try (final CloseableDataset dataset = getDataset(response)) { 2223 assertTrue("Didn't find child node!", dataset.asDatasetGraph().contains(ANY, 2224 createURI(location), CONTAINS.asNode(), createURI(location + "/c"))); 2225 } 2226 } 2227 2228 assertEquals(NO_CONTENT.getStatusCode(), getStatus(deleteObjMethod(id + "/c"))); 2229 2230 try (final CloseableHttpResponse response1 = execute(getObjMethod(id))) { 2231 try (final CloseableDataset dataset = getDataset(response1)) { 2232 assertFalse("Found child node!", dataset.asDatasetGraph().contains(ANY, 2233 createURI(location), CONTAINS.asNode(), createURI(location + "/c"))); 2234 } 2235 } 2236 } 2237 2238 @Test 2239 public void testGetObjectGraphWithChildren() throws IOException { 2240 final String id = getRandomUniqueId(); 2241 final String location = getLocation(createObject(id)); 2242 2243 // Create some children 2244 final int CHILDREN_TOTAL = 20; 2245 for (int x = 0; x < CHILDREN_TOTAL; ++x) { 2246 createObjectAndClose(id + "/child-" + x); 2247 } 2248 2249 final int CHILDREN_LIMIT = 12; 2250 final HttpGet httpGet = getObjMethod(id); 2251 httpGet.setHeader("Limit", Integer.toString(CHILDREN_LIMIT)); 2252 try (final CloseableHttpResponse response = execute(httpGet)) { 2253 try (final CloseableDataset dataset = getDataset(response)) { 2254 final DatasetGraph graph = dataset.asDatasetGraph(); 2255 final Iterator<Quad> contains = graph.find(ANY, createURI(location), CONTAINS.asNode(), ANY); 2256 assertTrue("Should find contained child!", contains.hasNext()); 2257 assertEquals(CHILDREN_LIMIT, Iterators.size(contains)); 2258 } 2259 } 2260 } 2261 2262 @Test 2263 public void testGetObjectGraphWithBadLimit() throws IOException { 2264 final String id = getRandomUniqueId(); 2265 getLocation(createObject(id)); 2266 2267 final HttpGet httpGet = getObjMethod(id); 2268 httpGet.setHeader("Limit", "not-an-integer"); 2269 try (final CloseableHttpResponse response = execute(httpGet)) { 2270 assertEquals(SC_BAD_REQUEST, response.getStatusLine().getStatusCode()); 2271 } 2272 } 2273 2274 2275 2276 @Test 2277 public void testPatchToCreateDirectContainerInSparqlUpdateFails() throws IOException { 2278 final String id = getRandomUniqueId(); 2279 createObjectAndClose(id); 2280 final HttpPatch patch = patchObjMethod(id); 2281 patch.setHeader(CONTENT_TYPE, "application/sparql-update"); 2282 final String updateString = 2283 "INSERT DATA { <> a <" + DIRECT_CONTAINER.getURI() + "> ; <" + MEMBERSHIP_RESOURCE.getURI() + 2284 "> <> ; <" + HAS_MEMBER_RELATION + "> <" + LDP_MEMBER + "> .}"; 2285 patch.setEntity(new StringEntity(updateString)); 2286 assertEquals("Patch with sparql update created direct container from basic container!", 2287 CONFLICT.getStatusCode(), getStatus(patch)); 2288 } 2289 2290 @Test 2291 public void testPatchToDeleteNonRdfSourceInteractionModel() throws IOException { 2292 final String pid = getRandomUniqueId(); 2293 2294 createDatastream(pid, "x", "some content"); 2295 2296 final String location = serverAddress + pid + "/x/fcr:metadata"; 2297 final HttpPatch patchDeleteMethod = new HttpPatch(location); 2298 patchDeleteMethod.addHeader(CONTENT_TYPE, "application/sparql-update"); 2299 patchDeleteMethod.setEntity(new StringEntity("PREFIX ldp: <http://www.w3.org/ns/ldp#> " + 2300 "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> DELETE { " + 2301 "<> rdf:type ldp:NonRDFSource .} WHERE {}")); 2302 assertEquals("Delete interaction model got status 409!\n", 2303 CONFLICT.getStatusCode(), getStatus(patchDeleteMethod)); 2304 } 2305 2306 @Test 2307 public void testPutToChangeNonRdfSourceToRdfSource() throws IOException { 2308 final String pid = getRandomUniqueId(); 2309 2310 createDatastream(pid, "x", "some content"); 2311 2312 final String ttl = "<> <http://purl.org/dc/elements/1.1/title> \"this is a title\" ."; 2313 final HttpPut put = putObjMethod(pid + "/x/fcr:metadata", "text/turtle", ttl); 2314 put.setHeader(LINK, BASIC_CONTAINER_LINK_HEADER); 2315 assertEquals("Changed the NonRdfSource ixn to basic container", 2316 CONFLICT.getStatusCode(), getStatus(put)); 2317 } 2318 2319 @Test 2320 public void testPutChangeBinaryTypeNotAllowed() throws IOException { 2321 final HttpPost postMethod = new HttpPost(serverAddress); 2322 postMethod.setEntity(new StringEntity("TestString.")); 2323 postMethod.addHeader(CONTENT_DISPOSITION, "attachment; filename=\"postCreate.txt\""); 2324 2325 final String location; 2326 try (final CloseableHttpResponse response = execute(postMethod)) { 2327 location = getLocation(response); 2328 } 2329 2330 // Change to RDFSource 2331 final HttpPut put1Method = new HttpPut(location); 2332 put1Method.setEntity(new StringEntity("TestString2.")); 2333 put1Method.addHeader(CONTENT_DISPOSITION, "attachment; filename=\"putUpdate.txt\""); 2334 put1Method.setHeader(LINK, RDF_SOURCE_LINK_HEADER); 2335 assertEquals("Changed the NonRdfSource interaction model to RdfSource", 2336 BAD_REQUEST.getStatusCode(), getStatus(put1Method)); 2337 2338 // Change to Basic Container 2339 final HttpPut put2Method = new HttpPut(location); 2340 put2Method.setEntity(new StringEntity("TestString2.")); 2341 put2Method.addHeader(CONTENT_DISPOSITION, "attachment; filename=\"putUpdate.txt\""); 2342 put2Method.setHeader(LINK, BASIC_CONTAINER_LINK_HEADER); 2343 assertEquals("Changed the NonRdfSource interaction model to RdfSource", 2344 CONFLICT.getStatusCode(), getStatus(put2Method)); 2345 } 2346 2347 @Test 2348 public void testPutChangeBasicContainerToParent() throws Exception { 2349 final String pid = getRandomUniqueId(); 2350 final String location = serverAddress + pid; 2351 createObjectAndClose(pid); 2352 2353 // Change to RDFSource 2354 final HttpPut put1Method = new HttpPut(location); 2355 put1Method.setHeader(LINK, CONTAINER_LINK_HEADER); 2356 assertEquals("Changed the BasicContainer interaction model to Container", 2357 BAD_REQUEST.getStatusCode(), getStatus(put1Method)); 2358 2359 final HttpPut put2Method = new HttpPut(location); 2360 put2Method.setHeader(LINK, RESOURCE_LINK_HEADER); 2361 assertEquals("Changed the BasicContainer interaction model to Resource", 2362 BAD_REQUEST.getStatusCode(), getStatus(put2Method)); 2363 2364 final HttpPut put3Method = new HttpPut(location); 2365 put3Method.setHeader(LINK, DIRECT_CONTAINER_LINK_HEADER); 2366 assertEquals("Changed the BasicContainer interaction model to DirectContainer", 2367 CONFLICT.getStatusCode(), getStatus(put3Method)); 2368 } 2369 2370 @Test 2371 public void testPutToChangeInteractionModelWithRdf() throws IOException { 2372 final String pid = getRandomUniqueId(); 2373 final String resource = serverAddress + pid; 2374 final String container = serverAddress + pid + "/c"; 2375 2376 createObjectAndClose(pid); 2377 createObjectAndClose(pid + "/a"); 2378 createObjectAndClose(pid + "/c"); 2379 2380 // attempt to change basic container to NonRdfSource 2381 final String ttl1 = "<> a <" + NON_RDF_SOURCE.getURI() + "> ."; 2382 final HttpPut put1 = putObjMethod(pid + "/a", "text/turtle", ttl1); 2383 assertEquals("Changed the basic container ixn to NonRdfSource through PUT with RDF content!", 2384 CONFLICT.getStatusCode(), getStatus(put1)); 2385 2386 // attempt to change basic container to direct container 2387 final String ttl2 = "<> a <" + DIRECT_CONTAINER.getURI() + "> ; <" + MEMBERSHIP_RESOURCE.getURI() + 2388 "> <" + resource + "> ; <" + HAS_MEMBER_RELATION + "> <" + LDP_MEMBER + "> ."; 2389 final HttpPut put2 = putObjMethod(pid + "/a", "text/turtle", ttl2); 2390 assertEquals("Changed the basic container ixn to Direct Container through PUT with RDF content!", 2391 CONFLICT.getStatusCode(), getStatus(put2)); 2392 2393 // create direct container 2394 final String ttl = "<> <" + MEMBERSHIP_RESOURCE.getURI() + 2395 "> <" + resource + "> ; <" + HAS_MEMBER_RELATION + "> <" + LDP_MEMBER + "> ."; 2396 final HttpPut put = putObjMethod(pid + "/b", "text/turtle", ttl); 2397 put.setHeader(LINK, DIRECT_CONTAINER_LINK_HEADER); 2398 assertEquals(CREATED.getStatusCode(), getStatus(put)); 2399 assertTrue(getLinkHeaders(getObjMethod(pid + "/b")).contains(DIRECT_CONTAINER_LINK_HEADER)); 2400 2401 // successful update the properties with the interaction mode 2402 final String ttla = "<> <" + MEMBERSHIP_RESOURCE + "> <" + container + ">;\n" 2403 + "<" + HAS_MEMBER_RELATION + "> <info:some/relation> .\n"; 2404 final HttpPut puta = putObjMethod(pid + "/b", "text/turtle", ttla); 2405 puta.addHeader(LINK, DIRECT_CONTAINER_LINK_HEADER); 2406 assertEquals(NO_CONTENT.getStatusCode(), getStatus(puta)); 2407 2408 // attempt to change direct container to basic container 2409 final String ttl3 = "<> a <" + BASIC_CONTAINER.getURI() + 2410 "> ; <" + title + "> \"this is a title\"."; 2411 final HttpPut put3 = putObjMethod(pid + "/b", "text/turtle", ttl3); 2412 assertEquals("Changed the direct container ixn to basic container through PUT with RDF content!", 2413 CONFLICT.getStatusCode(), getStatus(put3)); 2414 2415 // attempt to change direct container to indirect container 2416 final String ttl4 = "<> a <" + INDIRECT_CONTAINER.getURI() 2417 + "> ; <" + MEMBERSHIP_RESOURCE + "> <" + container + ">;\n" 2418 + "<" + HAS_MEMBER_RELATION + "> <info:some/relation>;\n" 2419 + "<" + INSERTED_CONTENT_RELATION + "> <info:proxy/for> .\n"; 2420 final HttpPut put4 = putObjMethod(pid + "/b", "text/turtle", ttl4); 2421 assertEquals("Changed the direct container ixn to indirect container through PUT with RDF content!", 2422 CONFLICT.getStatusCode(), getStatus(put4)); 2423 } 2424 2425 @Test 2426 public void testChangeInteractionModelWithPut() throws IOException { 2427 final String pid = getRandomUniqueId(); 2428 final String resource = serverAddress + pid; 2429 final String container = serverAddress + pid + "/c"; 2430 2431 createObjectAndClose(pid); 2432 createObjectAndClose(pid + "/a"); 2433 createObjectAndClose(pid + "/c"); 2434 2435 final String ttl1 = "<> <" + MEMBERSHIP_RESOURCE.getURI() + 2436 "> <" + resource + "> ; <" + HAS_MEMBER_RELATION + "> <" + LDP_MEMBER + "> ."; 2437 final HttpPut put1 = putObjMethod(pid + "/a", "text/turtle", ttl1); 2438 put1.setHeader(LINK, DIRECT_CONTAINER_LINK_HEADER); 2439 put1.addHeader("Prefer", "handling=lenient"); 2440 assertEquals("Changed the basic container ixn to direct container!", 2441 CONFLICT.getStatusCode(), getStatus(put1)); 2442 2443 // create direct container 2444 final String ttl = "<> <" + MEMBERSHIP_RESOURCE.getURI() + 2445 "> <" + resource + "> ; <" + HAS_MEMBER_RELATION + "> <" + LDP_MEMBER + "> ."; 2446 final HttpPut put = putObjMethod(pid + "/b", "text/turtle", ttl); 2447 put.setHeader(LINK, DIRECT_CONTAINER_LINK_HEADER); 2448 assertEquals(CREATED.getStatusCode(), getStatus(put)); 2449 assertTrue(getLinkHeaders(getObjMethod(pid + "/b")).contains(DIRECT_CONTAINER_LINK_HEADER)); 2450 2451 final String ttl2 = "<> <" + title + "> \"this is a title\""; 2452 final HttpPut put2 = putObjMethod(pid + "/b", "text/turtle", ttl2); 2453 put2.setHeader(LINK, INDIRECT_CONTAINER_LINK_HEADER); 2454 put2.addHeader("Prefer", "handling=lenient"); 2455 assertEquals("Changed the direct container ixn to basic container", 2456 CONFLICT.getStatusCode(), getStatus(put2)); 2457 2458 final String ttl3 = "<> <" + MEMBERSHIP_RESOURCE + "> <" + container + ">;\n" 2459 + "<" + HAS_MEMBER_RELATION + "> <info:some/relation>;\n" 2460 + "<" + INSERTED_CONTENT_RELATION + "> <info:proxy/for> .\n"; 2461 final HttpPut put3 = putObjMethod(pid + "/b", "text/turtle", ttl3); 2462 put3.setHeader(LINK, INDIRECT_CONTAINER_LINK_HEADER); 2463 put3.addHeader("Prefer", "handling=lenient"); 2464 assertEquals("Changed the direct container ixn to indirect container!", 2465 CONFLICT.getStatusCode(), getStatus(put3)); 2466 } 2467 2468 @Test 2469 public void testGetObjectReferences() throws IOException { 2470 final String id = getRandomUniqueId(); 2471 final String resource = serverAddress + id; 2472 final String resourcea = resource + "/a"; 2473 final String resourceb = resource + "/b"; 2474 2475 createObjectAndClose(id); 2476 createObjectAndClose(id + "/a"); 2477 createObjectAndClose(id + "/b"); 2478 final HttpPatch updateObjectGraphMethod = patchObjMethod(id + "/a"); 2479 updateObjectGraphMethod.addHeader(CONTENT_TYPE, "application/sparql-update"); 2480 updateObjectGraphMethod.setEntity(new StringEntity("INSERT { <" + 2481 resourcea + "> <http://purl.org/dc/terms/isPartOf> <" + resourceb + "> . \n <" + 2482 resourcea + "> <info:xyz#some-other-property> <" + resourceb + "> } WHERE {}")); 2483 assertEquals(NO_CONTENT.getStatusCode(), getStatus(updateObjectGraphMethod)); 2484 2485 final String withoutRefsEtag = getEtag(resourceb); 2486 2487 final HttpGet getObjMethod = new HttpGet(resourceb); 2488 getObjMethod.addHeader("Prefer", INBOUND_REFERENCE_PREFER_HEADER); 2489 2490 assertNotEquals(withoutRefsEtag, getEtag(getObjMethod)); 2491 2492 try (final CloseableDataset dataset = getDataset(getObjMethod)) { 2493 final DatasetGraph graph = dataset.asDatasetGraph(); 2494 assertTrue(graph.contains(ANY, 2495 createURI(resourcea), createURI("http://purl.org/dc/terms/isPartOf"), createURI(resourceb))); 2496 2497 assertTrue(graph.contains(ANY, 2498 createURI(resourcea), createURI("info:xyz#some-other-property"), createURI(resourceb))); 2499 } 2500 } 2501 2502 @Test 2503 public void testInboundReferencesFromBinary() throws Exception { 2504 final Node referenceProp = NodeFactory.createURI("http://awoods.com/pointsAt"); 2505 final HttpPost postContainer = postObjMethod(); 2506 final String containerUri; 2507 try (final CloseableHttpResponse response = execute(postContainer)) { 2508 assertEquals(CREATED.getStatusCode(), getStatus(response)); 2509 containerUri = getLocation(response); 2510 } 2511 2512 final HttpPost postBinary = postObjMethod(); 2513 postBinary.setHeader(CONTENT_TYPE, TEXT_PLAIN); 2514 postBinary.setEntity(new StringEntity("Test text")); 2515 final String binaryUri; 2516 try (final CloseableHttpResponse response = execute(postBinary)) { 2517 assertEquals(CREATED.getStatusCode(), getStatus(response)); 2518 binaryUri = getLocation(response); 2519 } 2520 2521 setProperty(binaryUri.replace(serverAddress, "") + "/" + FCR_METADATA, referenceProp.getURI(), 2522 URI.create(containerUri)); 2523 2524 final String withoutRefsEtag = getEtag(containerUri); 2525 2526 final HttpGet getContainer = new HttpGet(containerUri); 2527 getContainer.setHeader("Prefer", INBOUND_REFERENCE_PREFER_HEADER); 2528 2529 assertNotEquals(withoutRefsEtag, getEtag(getContainer)); 2530 try (final CloseableDataset dataset = getDataset(getContainer)) { 2531 final DatasetGraph graph = dataset.asDatasetGraph(); 2532 assertTrue(graph.contains(ANY, NodeFactory.createURI(binaryUri), referenceProp, 2533 NodeFactory.createURI(containerUri))); 2534 } 2535 } 2536 2537 /** 2538 * Create the same reference from two resources, check that one exists when the other is deleted. 2539 */ 2540 @Test 2541 public void testMultipleReferences() throws Exception { 2542 // Need to use something different or they come out as a single triple in the serialization. 2543 final Node referenceProp1 = NodeFactory.createURI("http://awoods.com/pointsAt"); 2544 final Node referenceProp2 = createURI("http://example.org/lookAt"); 2545 final HttpPost postContainer = postObjMethod(); 2546 final String targetUri; 2547 try (final CloseableHttpResponse response = execute(postContainer)) { 2548 assertEquals(CREATED.getStatusCode(), getStatus(response)); 2549 targetUri = getLocation(response); 2550 } 2551 final Node targetNode = createURI(targetUri); 2552 2553 final HttpPost post1 = postObjMethod(); 2554 post1.setHeader(CONTENT_TYPE, "text/turtle"); 2555 post1.setEntity(new StringEntity("<> <" + referenceProp1.getURI() + "> <" + targetUri + "> .")); 2556 final String firstContainerUri; 2557 try (final CloseableHttpResponse response = execute(post1)) { 2558 assertEquals(CREATED.getStatusCode(), getStatus(response)); 2559 firstContainerUri = getLocation(response); 2560 } 2561 final Node subjectNode = createURI(firstContainerUri); 2562 2563 final HttpPost post2 = postObjMethod(); 2564 post2.setHeader(CONTENT_TYPE, "text/turtle"); 2565 post2.setEntity(new StringEntity("<" + firstContainerUri + "> <" + referenceProp2.getURI() + "> <" + targetUri + 2566 "> .")); 2567 final String secondContainerUri; 2568 try (final CloseableHttpResponse response = execute(post2)) { 2569 assertEquals(CREATED.getStatusCode(), getStatus(response)); 2570 secondContainerUri = getLocation(response); 2571 } 2572 2573 final HttpGet getReferences = new HttpGet(targetUri); 2574 getReferences.setHeader("Prefer", INBOUND_REFERENCE_PREFER_HEADER); 2575 try (final CloseableDataset dataset = getDataset(getReferences)) { 2576 final DatasetGraph graph = dataset.asDatasetGraph(); 2577 assertTrue(graph.contains(ANY, subjectNode, referenceProp1, targetNode)); 2578 assertTrue(graph.contains(ANY, subjectNode, referenceProp2, targetNode)); 2579 } 2580 // Delete the first resource. 2581 final HttpDelete deleteFirst = new HttpDelete(firstContainerUri); 2582 assertEquals(NO_CONTENT.getStatusCode(), getStatus(deleteFirst)); 2583 // Still one reference. 2584 final HttpGet getReferences2 = new HttpGet(targetUri); 2585 getReferences2.setHeader("Prefer", INBOUND_REFERENCE_PREFER_HEADER); 2586 try (final CloseableDataset dataset = getDataset(getReferences2)) { 2587 final DatasetGraph graph = dataset.asDatasetGraph(); 2588 assertFalse(graph.contains(ANY, subjectNode, referenceProp1, targetNode)); 2589 assertTrue(graph.contains(ANY, subjectNode, referenceProp2, targetNode)); 2590 } 2591 // Delete the second resource 2592 final HttpDelete deleteSecond = new HttpDelete(secondContainerUri); 2593 assertEquals(NO_CONTENT.getStatusCode(), getStatus(deleteSecond)); 2594 // No more references. 2595 final HttpGet getReferences3 = new HttpGet(targetUri); 2596 getReferences3.setHeader("Prefer", INBOUND_REFERENCE_PREFER_HEADER); 2597 try (final CloseableDataset dataset = getDataset(getReferences3)) { 2598 final DatasetGraph graph = dataset.asDatasetGraph(); 2599 assertFalse(graph.contains(ANY, subjectNode, referenceProp1, targetNode)); 2600 assertFalse(graph.contains(ANY, subjectNode, referenceProp2, targetNode)); 2601 } 2602 } 2603 2604 @Test 2605 public void testReferenceRemovedOnDelete() throws Exception { 2606 final String id = getRandomUniqueId(); 2607 final String resource = serverAddress + id; 2608 final String resourcea = resource + "/a"; 2609 final String resourceb = resource + "/b"; 2610 final String resourcec = resource + "/c"; 2611 2612 createObjectAndClose(id); 2613 createObjectAndClose(id + "/a"); 2614 createObjectAndClose(id + "/b"); 2615 createObjectAndClose(id + "/c"); 2616 // Patch a to point to c. 2617 final HttpPatch updateObjectGraphMethodA = patchObjMethod(id + "/a"); 2618 updateObjectGraphMethodA.addHeader(CONTENT_TYPE, "application/sparql-update"); 2619 updateObjectGraphMethodA.setEntity(new StringEntity("INSERT { <" + 2620 resourcea + "> <http://purl.org/dc/terms/isPartOf> <" + resourcec + "> . } WHERE {}")); 2621 assertEquals(NO_CONTENT.getStatusCode(), getStatus(updateObjectGraphMethodA)); 2622 // Patch b to point to c as well. 2623 final HttpPatch updateObjectGraphMethodB = patchObjMethod(id + "/b"); 2624 updateObjectGraphMethodB.addHeader(CONTENT_TYPE, "application/sparql-update"); 2625 updateObjectGraphMethodB.setEntity(new StringEntity("INSERT { <" + 2626 resourceb + "> <http://purl.org/dc/terms/isPartOf> <" + resourcec + "> . } WHERE {}")); 2627 assertEquals(NO_CONTENT.getStatusCode(), getStatus(updateObjectGraphMethodB)); 2628 2629 // Verify the inbound references. 2630 final HttpGet getObjMethod = new HttpGet(resourcec); 2631 getObjMethod.addHeader("Prefer", INBOUND_REFERENCE_PREFER_HEADER); 2632 try (final CloseableDataset dataset = getDataset(getObjMethod)) { 2633 final DatasetGraph graph = dataset.asDatasetGraph(); 2634 assertTrue(graph.contains(ANY, 2635 createURI(resourcea), createURI("http://purl.org/dc/terms/isPartOf"), createURI(resourcec))); 2636 2637 assertTrue(graph.contains(ANY, 2638 createURI(resourceb), createURI("http://purl.org/dc/terms/isPartOf"), createURI(resourcec))); 2639 } 2640 // Delete a. 2641 final HttpDelete deleteReferrer = deleteObjMethod(id + "/a"); 2642 assertEquals(NO_CONTENT.getStatusCode(), getStatus(deleteReferrer)); 2643 // Verify only inbound reference from b 2644 final HttpGet getObjMethod2 = new HttpGet(resourcec); 2645 getObjMethod2.addHeader("Prefer", INBOUND_REFERENCE_PREFER_HEADER); 2646 try (final CloseableDataset dataset = getDataset(getObjMethod2)) { 2647 final DatasetGraph graph = dataset.asDatasetGraph(); 2648 assertFalse(graph.contains(ANY, 2649 createURI(resourcea), createURI("http://purl.org/dc/terms/isPartOf"), createURI(resourceb))); 2650 2651 assertTrue(graph.contains(ANY, 2652 createURI(resourceb), createURI("http://purl.org/dc/terms/isPartOf"), createURI(resourcec))); 2653 } 2654 } 2655 2656 @Test 2657@Ignore("Membership/containment not included as inbound references - FCREPO-3589") 2658 public void testGetObjectReferencesIndirect() throws Exception { 2659 final String uuid = getRandomUniqueId(); 2660 final String pid1 = uuid + "/parent"; 2661 final String pid2 = uuid + "/child1"; 2662 final String pid3 = uuid + "/child2"; 2663 createObjectAndClose(pid1); 2664 createObjectAndClose(pid2); 2665 createObjectAndClose(pid3); 2666 2667 final String memberRelation = "http://pcdm.org/models#hasMember"; 2668 2669 // create an indirect container 2670 final HttpPut createContainer = new HttpPut(serverAddress + pid1 + "/members"); 2671 createContainer.addHeader(CONTENT_TYPE, "text/turtle"); 2672 createContainer.addHeader(LINK, INDIRECT_CONTAINER_LINK_HEADER); 2673 final String membersRDF = "<> <http://www.w3.org/ns/ldp#hasMemberRelation> <" + memberRelation + ">; " 2674 + "<http://www.w3.org/ns/ldp#insertedContentRelation> <http://www.openarchives.org/ore/terms/proxyFor>; " 2675 + "<http://www.w3.org/ns/ldp#membershipResource> <" + serverAddress + pid1 + "> . "; 2676 createContainer.setEntity(new StringEntity(membersRDF)); 2677 assertEquals(CREATED.getStatusCode(), getStatus(createContainer)); 2678 2679 // create proxies for the children in the indirect container 2680 createProxy(pid1, pid2); 2681 createProxy(pid1, pid3); 2682 2683 // retrieve the parent and verify the outbound triples exist 2684 final HttpGet getParent = new HttpGet(serverAddress + pid1); 2685 getParent.addHeader(ACCEPT, "application/n-triples"); 2686 try (final CloseableDataset dataset = getDataset(getParent)) { 2687 final DatasetGraph parentGraph = dataset.asDatasetGraph(); 2688 assertTrue(parentGraph.contains(Node.ANY, 2689 createURI(serverAddress + pid1), 2690 createURI(memberRelation), 2691 createURI(serverAddress + pid2))); 2692 assertTrue(parentGraph.contains(Node.ANY, 2693 createURI(serverAddress + pid1), 2694 createURI(memberRelation), 2695 createURI(serverAddress + pid3))); 2696 } 2697 2698 // retrieve the members container and verify the LDP triples exist 2699 final HttpGet getContainer = new HttpGet(serverAddress + pid1 + "/members"); 2700 getContainer.addHeader("Prefer", "return=representation;include=\"http://www.w3.org/ns/ldp#PreferMembership\""); 2701 getContainer.addHeader(ACCEPT, "application/n-triples"); 2702 try (final CloseableDataset dataset = getDataset(getContainer)) { 2703 final DatasetGraph containerGraph = dataset.asDatasetGraph(); 2704 assertTrue(containerGraph.contains(Node.ANY, 2705 createURI(serverAddress + pid1 + "/members"), 2706 createURI("http://www.w3.org/ns/ldp#hasMemberRelation"), 2707 createURI(memberRelation))); 2708 2709 assertTrue(containerGraph.contains(Node.ANY, 2710 createURI(serverAddress + pid1 + "/members"), 2711 createURI("http://www.w3.org/ns/ldp#insertedContentRelation"), 2712 createURI("http://www.openarchives.org/ore/terms/proxyFor"))); 2713 2714 assertTrue(containerGraph.contains(Node.ANY, 2715 createURI(serverAddress + pid1 + "/members"), 2716 createURI("http://www.w3.org/ns/ldp#membershipResource"), 2717 createURI(serverAddress + pid1))); 2718 } 2719 2720 2721 // retrieve the member and verify inbound triples exist 2722 final HttpGet getMember = new HttpGet(serverAddress + pid2); 2723 getMember.addHeader("Prefer", "return=representation; include=\"" + INBOUND_REFERENCES.toString() + "\""); 2724 getMember.addHeader(ACCEPT, "application/n-triples"); 2725 try (final CloseableDataset dataset = getDataset(getMember)) { 2726 final DatasetGraph memberGraph = dataset.asDatasetGraph(); 2727 assertTrue(memberGraph.contains(Node.ANY, 2728 Node.ANY, 2729 createURI("http://www.openarchives.org/ore/terms/proxyFor"), 2730 createURI(serverAddress + pid2))); 2731 2732 assertTrue(memberGraph.contains(Node.ANY, 2733 createURI(serverAddress + pid1), 2734 createURI(memberRelation), 2735 createURI(serverAddress + pid2))); 2736 2737 assertFalse("Should not contain inbound references to the other child", memberGraph.contains(Node.ANY, 2738 createURI(serverAddress + pid1), 2739 createURI(memberRelation), 2740 createURI(serverAddress + pid3))); 2741 } 2742 } 2743 private static void createProxy(final String parent, final String child) { 2744 final HttpPost createProxy = new HttpPost(serverAddress + parent + "/members"); 2745 createProxy.addHeader(CONTENT_TYPE, "text/turtle"); 2746 final String proxyRDF = "<> <http://www.openarchives.org/ore/terms/proxyFor> <" + serverAddress + child + ">;" 2747 + " <http://www.openarchives.org/ore/terms/proxyIn> <" + serverAddress + parent + "> ."; 2748 createProxy.setEntity(new StringEntity(proxyRDF, UTF_8)); 2749 assertEquals(CREATED.getStatusCode(), getStatus(createProxy)); 2750 } 2751 2752 @Test 2753 public void testNoReferentialIntegrity() throws IOException { 2754 final HttpPatch patch = new HttpPatch(getLocation(postObjMethod())); 2755 patch.addHeader(CONTENT_TYPE, "application/sparql-update"); 2756 patch.setEntity(new StringEntity("INSERT { " + 2757 "<> <http://some-vocabulary#isMemberOfCollection> <" + serverAddress + "non-existant> } WHERE {}")); 2758 assertEquals(NO_CONTENT.getStatusCode(), getStatus(patch)); 2759 } 2760 2761 @Test 2762 public void testUpdateAndReplaceObjectGraph() throws IOException { 2763 final String subjectURI = getLocation(postObjMethod()); 2764 final HttpPatch updateObjectGraphMethod = new HttpPatch(subjectURI); 2765 updateObjectGraphMethod.addHeader(CONTENT_TYPE, "application/sparql-update"); 2766 updateObjectGraphMethod.setEntity(new StringEntity("INSERT {<" + subjectURI + "> <info:test#label> \"foo\" ; " + 2767 " <info:test#number> 42 ; " + 2768 " <info:test#date> \"1953?\"^^<http://id.loc.gov/datatypes/edtf/EDTF> }" + 2769 " WHERE {}")); 2770 executeAndClose(updateObjectGraphMethod); 2771 try (final CloseableDataset dataset = getDataset(new HttpGet(subjectURI))) { 2772 final DatasetGraph graph = dataset.asDatasetGraph(); 2773 assertTrue("Didn't find a triple we thought we added.", graph.contains(ANY, 2774 createURI(subjectURI), createURI("info:test#label"), createLiteral("foo"))); 2775 assertTrue("Didn't find a triple we thought we added.", graph.contains(ANY, 2776 createURI(subjectURI), createURI("info:test#number"), createLiteral("42", XSDinteger))); 2777 assertTrue("Didn't find a triple we thought we added.", graph.contains(ANY, 2778 createURI(subjectURI), createURI("info:test#date"), createLiteral("1953?", 2779 getInstance().getSafeTypeByName("http://id.loc.gov/datatypes/edtf/EDTF")))); 2780 } 2781 updateObjectGraphMethod.setEntity(new StringEntity("DELETE WHERE { " + 2782 "<" + subjectURI + "> <info:test#label> \"foo\"." + 2783 "<" + subjectURI + "> <info:test#number> 42 . " + 2784 "<" + subjectURI + "> <info:test#date> \"1953?\"^^<http://id.loc.gov/datatypes/edtf/EDTF> . }; \n" + 2785 "INSERT {<" + subjectURI + "> <info:test#label> \"qwerty\" ; " + 2786 "<info:test#number> 43 ; " + 2787 "<info:test#date> \"1953\"^^<http://id.loc.gov/datatypes/edtf/EDTF> . } \n" + 2788 "WHERE { }")); 2789 2790 try (final CloseableHttpResponse response = execute(updateObjectGraphMethod)) { 2791 assertEquals(NO_CONTENT.getStatusCode(), getStatus(response)); 2792 assertTrue("Didn't find Last-Modified header!", response.containsHeader("Last-Modified")); 2793 assertTrue("Didn't find ETag header!", response.containsHeader("ETag")); 2794 } 2795 try (final CloseableDataset dataset = getDataset(new HttpGet(subjectURI))) { 2796 final DatasetGraph graph = dataset.asDatasetGraph(); 2797 assertFalse("Found a triple we thought we deleted.", graph.contains(ANY, 2798 createURI(subjectURI), createURI("info:test#label"), createLiteral("foo"))); 2799 assertFalse("Found a triple we thought we deleted.", graph.contains(ANY, 2800 createURI(subjectURI), createURI("info:test#number"), createLiteral("42", XSDinteger))); 2801 assertFalse("Found a triple we thought we deleted.", graph.contains(ANY, 2802 createURI(subjectURI), createURI("info:test#date"), createLiteral("1953?", 2803 getInstance().getSafeTypeByName("http://id.loc.gov/datatypes/edtf/EDTF")))); 2804 } 2805 } 2806 2807 @Test 2808 public void testUpdateObjectGraphWithProblems() throws IOException { 2809 final String subjectURI = getLocation(postObjMethod()); 2810 final Link ex = fromUri(URI.create(serverAddress + "static/constraints/ServerManagedPropertyException.rdf")) 2811 .rel(CONSTRAINED_BY.getURI()).build(); 2812 2813 final HttpPatch patchObjMethod = new HttpPatch(subjectURI); 2814 patchObjMethod.addHeader(CONTENT_TYPE, "application/sparql-update"); 2815 patchObjMethod.setEntity(new StringEntity("INSERT { <" + 2816 subjectURI + "> <" + REPOSITORY_NAMESPACE + "uuid> \"value-doesn't-matter\" } WHERE {}\n")); 2817 try (final CloseableHttpResponse response = execute(patchObjMethod)) { 2818 assertEquals(CONFLICT.getStatusCode(), getStatus(response)); 2819 assertEquals(ex.toString(), response.getFirstHeader(LINK).getValue()); 2820 } 2821 } 2822 2823 @Test 2824 public void testPutResourceBadRdf() throws IOException { 2825 final HttpPut httpPut = new HttpPut(serverAddress + getRandomUniqueId()); 2826 httpPut.setHeader(CONTENT_TYPE, "text/turtle"); 2827 httpPut.setEntity(new StringEntity("<> a \"still image\".")); 2828 assertEquals(BAD_REQUEST.getStatusCode(), getStatus(httpPut)); 2829 } 2830 2831 @Test 2832 public void testCreateResourceWithoutContentType() { 2833 assertEquals(CREATED.getStatusCode(), getStatus(new HttpPut(serverAddress + getRandomUniqueId()))); 2834 } 2835 2836 @Test 2837 public void testUpdateObjectWithoutContentType() throws IOException { 2838 final HttpPut httpPut = new HttpPut(getLocation(postObjMethod())); 2839 // use a bytestream-based entity to avoid settin a content type 2840 httpPut.setEntity(new ByteArrayEntity("bogus content".getBytes(UTF_8))); 2841 assertEquals(BAD_REQUEST.getStatusCode(), getStatus(httpPut)); 2842 } 2843 2844 @Test 2845 public void testUpdateBinaryWithoutContentType() throws IOException { 2846 final String id = getRandomUniqueId(); 2847 assertEquals(CREATED.getStatusCode(), getStatus(putObjMethod(id))); 2848 createDatastream(id, "x", "xyz"); 2849 final HttpPut httpPut = new HttpPut(serverAddress + id + "/x"); 2850 assertEquals(NO_CONTENT.getStatusCode(), getStatus(httpPut)); 2851 } 2852 2853 @Test 2854 public void testCreateBinaryUpperCaseMimeType() throws IOException { 2855 final String subjectURI = serverAddress + getRandomUniqueId(); 2856 final HttpPut createMethod = new HttpPut(subjectURI); 2857 createMethod.addHeader(CONTENT_TYPE, "TEXT/PlAiN"); 2858 createMethod.setEntity(new StringEntity("Some text here.")); 2859 createMethod.addHeader(LINK, NON_RDF_SOURCE_LINK_HEADER); 2860 2861 try (final CloseableHttpResponse response = execute(createMethod)) { 2862 assertEquals(CREATED.getStatusCode(), getStatus(response)); 2863 2864 final String receivedLocation = response.getFirstHeader("Location").getValue(); 2865 assertEquals("Got wrong URI in Location header for datastream creation!", subjectURI, receivedLocation); 2866 2867 final Link link = Link.valueOf(response.getFirstHeader(LINK).getValue()); 2868 // ensure it's a binary; if it doesn't have describedby, then it's not 2869 assertEquals("No described by header!", "describedby", link.getRel()); 2870 } 2871 } 2872 2873 @Test 2874 public void testBinarySetBadMimeType() throws IOException { 2875 final String subjectURI = serverAddress + getRandomUniqueId(); 2876 final HttpPut createMethod = new HttpPut(subjectURI); 2877 createMethod.addHeader(CONTENT_TYPE, "text/plain"); 2878 createMethod.setEntity(new StringEntity("Some text here.")); 2879 createMethod.addHeader(LINK, NON_RDF_SOURCE_LINK_HEADER); 2880 2881 assertEquals(CREATED.getStatusCode(), getStatus(createMethod)); 2882 2883 final HttpPatch patch = new HttpPatch(subjectURI + "/fcr:metadata"); 2884 patch.addHeader(CONTENT_TYPE, "application/sparql-update"); 2885 patch.setEntity(new StringEntity( 2886 "PREFIX ebucore: <http://www.ebu.ch/metadata/ontologies/ebucore/ebucore#> " + 2887 "INSERT { <" + subjectURI + "> ebucore:hasMimeType \"-- invalid syntax! --\" } WHERE {}") 2888 ); 2889 2890 assertEquals(NO_CONTENT.getStatusCode(), getStatus(patch)); 2891 2892 // make sure it's still retrievable 2893 final HttpGet getMethod = new HttpGet(subjectURI); 2894 try (final CloseableHttpResponse response = execute(getMethod)) { 2895 assertEquals(OK.getStatusCode(), getStatus(response)); 2896 final Collection<String> contentTypes = getHeader(response, CONTENT_TYPE); 2897 final String contentType = contentTypes.iterator().next(); 2898 assertTrue("GET: Expected 'text/plain' instead got: '" + contentType + "'", 2899 contentType.contains("text/plain")); 2900 } 2901 2902 final HttpHead httpHead = new HttpHead(subjectURI); 2903 try (final CloseableHttpResponse response = execute(httpHead)) { 2904 final Collection<String> contentTypes = getHeader(response, CONTENT_TYPE); 2905 final String contentType = contentTypes.iterator().next(); 2906 assertTrue("HEAD: Expected 'text/plain' instead got: '" + contentType + "'", 2907 contentType.contains("text/plain")); 2908 } 2909 } 2910 2911 @Test 2912 public void testCreateBinaryCSV() throws IOException { 2913 final String subjectURI = serverAddress + getRandomUniqueId(); 2914 final HttpPut createMethod = new HttpPut(subjectURI); 2915 createMethod.addHeader(CONTENT_TYPE, "text/csv"); 2916 createMethod.addHeader(LINK, NON_RDF_SOURCE_LINK_HEADER); 2917 createMethod.setEntity(new StringEntity("Header 1, Header 2, Header 3\r1,2,3\r1,2,3")); 2918 2919 try (final CloseableHttpResponse response = execute(createMethod)) { 2920 assertEquals(CREATED.getStatusCode(), getStatus(response)); 2921 2922 final String receivedLocation = response.getFirstHeader("Location").getValue(); 2923 2924 assertEquals("Got wrong URI in Location header for datastream creation!", subjectURI, receivedLocation); 2925 2926 final Link link = Link.valueOf(response.getFirstHeader(LINK).getValue()); 2927 // ensure it's a binary; if it doesn't have describedby, then it's not 2928 assertEquals("No described by header!", "describedby", link.getRel()); 2929 } 2930 } 2931 2932 @Test 2933 public void testRoundTripReplaceGraphForDatastreamDescription() throws IOException { 2934 final String id = getRandomUniqueId(); 2935 final String subjectURI = serverAddress + id + "/ds1"; 2936 createDatastream(id, "ds1", "some-content"); 2937 2938 final HttpGet getObjMethod = new HttpGet(subjectURI + "/" + FCR_METADATA); 2939 getObjMethod.addHeader(ACCEPT, "text/turtle"); 2940 getObjMethod.addHeader("Prefer", "return=representation; omit=\"" + PREFER_SERVER_MANAGED + "\""); 2941 final Model model = createDefaultModel(); 2942 try (final CloseableHttpResponse getResponse = execute(getObjMethod)) { 2943 final String graph = EntityUtils.toString(getResponse.getEntity()); 2944 logger.trace("Got serialized object graph for testRoundTripReplaceGraphForDatastream():\n {}", graph); 2945 try (final StringReader r = new StringReader(graph)) { 2946 model.read(r, subjectURI, "TURTLE"); 2947 } 2948 } 2949 final HttpPut replaceMethod = new HttpPut(subjectURI + "/" + FCR_METADATA); 2950 try (final StringWriter w = new StringWriter()) { 2951 model.write(w, "N-TRIPLE"); 2952 final String updatedMetadata = w.toString() + 2953 "<" + subjectURI + "> <http://www.w3.org/2000/01/rdf-schema#label> 'foo' ."; 2954 replaceMethod.setEntity(new StringEntity(updatedMetadata)); 2955 logger.trace("Transmitting object graph for testRoundTripReplaceGraphForDatastream():\n {}", w); 2956 } 2957 replaceMethod.addHeader(CONTENT_TYPE, "application/n-triples"); 2958 assertEquals(NO_CONTENT.getStatusCode(), getStatus(replaceMethod)); 2959 } 2960 2961 @Test 2962 public void testDefaultBinaryDescriptionTriples() throws Exception { 2963 final String id = getRandomUniqueId(); 2964 final HttpPost post = postObjMethod(); 2965 final String binaryUri = serverAddress + id; 2966 final Node binaryNode = createURI(binaryUri); 2967 2968 post.setHeader("Slug", id); 2969 post.setHeader(CONTENT_TYPE, TEXT_PLAIN); 2970 post.setEntity(new StringEntity("some text", UTF_8)); 2971 post.setHeader(CONTENT_DISPOSITION, "attachment; filename=\"mytest.txt\""); 2972 assertEquals(CREATED.getStatusCode(), getStatus(post)); 2973 2974 final HttpGet get = getObjMethod(id + "/" + FCR_METADATA); 2975 try (final CloseableHttpResponse response = execute(get)) { 2976 assertEquals(OK.getStatusCode(), getStatus(response)); 2977 try (final CloseableDataset dataset = getDataset(response)) { 2978 final DatasetGraph graph = dataset.asDatasetGraph(); 2979 // Not checking for CreatedBy and LastModifiedBy as they require authentication. 2980 assertFalse(dataset.isEmpty()); 2981 assertTrue(graph.contains(ANY, binaryNode, CREATED_DATE.asNode(), ANY)); 2982 assertTrue(graph.contains(ANY, binaryNode, LAST_MODIFIED_DATE.asNode(), ANY)); 2983 assertTrue(graph.contains(ANY, binaryNode, type.asNode(), NON_RDF_SOURCE.asNode())); 2984 assertTrue(graph.contains(ANY, binaryNode, type.asNode(), RESOURCE.asNode())); 2985 assertTrue(graph.contains(ANY, binaryNode, type.asNode(), FEDORA_RESOURCE.asNode())); 2986 assertTrue(graph.contains(ANY, binaryNode, type.asNode(), FEDORA_BINARY.asNode())); 2987 assertTrue(graph.contains(ANY, binaryNode, HAS_SIZE.asNode(), createLiteral("9", XSDlong))); 2988 assertTrue(graph.contains(ANY, binaryNode, HAS_MESSAGE_DIGEST.asNode(), ANY)); 2989 assertTrue(graph.contains(ANY, binaryNode, HAS_FIXITY_SERVICE.asNode(), 2990 createURI(binaryUri + "/" + FCR_FIXITY))); 2991 assertTrue(graph.contains(ANY, binaryNode, HAS_ORIGINAL_NAME.asNode(), 2992 createLiteral("mytest.txt"))); 2993 } 2994 } 2995 2996 // Now get with ServerManaged omitted 2997 final HttpGet get2 = getObjMethod(id + "/" + FCR_METADATA); 2998 get2.setHeader("Prefer", "return=representation; omit=\"" + PREFER_SERVER_MANAGED + "\""); 2999 try (final CloseableHttpResponse response = execute(get2)) { 3000 assertEquals(OK.getStatusCode(), getStatus(response)); 3001 try (final CloseableDataset dataset = getDataset(response)) { 3002 assertTrue(dataset.isEmpty()); 3003 } 3004 } 3005 } 3006 3007 @Test 3008 public void testResponseContentTypes() throws Exception { 3009 final String id = getRandomUniqueId(); 3010 createObjectAndClose(id); 3011 for (final String type : POSSIBLE_RDF_RESPONSE_VARIANTS_STRING) { 3012 final HttpGet method = new HttpGet(serverAddress + id); 3013 method.addHeader(ACCEPT, type); 3014 assertEquals(type, getContentType(method)); 3015 } 3016 } 3017 3018 @Test 3019 public void testDescribeRdfCached() throws IOException { 3020 try (final CloseableHttpClient cachClient = CachingHttpClientBuilder.create().setCacheConfig(DEFAULT).build()) { 3021 final String location = getLocation(postObjMethod()); 3022 try (final CloseableHttpResponse response = cachClient.execute(new HttpGet(location))) { 3023 assertEquals("Client didn't return a OK!", OK.getStatusCode(), getStatus(response)); 3024 logger.debug("Found HTTP headers:\n{}", asList(response.getAllHeaders())); 3025 assertTrue("Didn't find Last-Modified header!", response.containsHeader("Last-Modified")); 3026 final String lastModed = response.getFirstHeader("Last-Modified").getValue(); 3027 final String etag = response.getFirstHeader("ETag").getValue(); 3028 final HttpGet getObjMethod2 = new HttpGet(location); 3029 getObjMethod2.setHeader("If-Modified-Since", lastModed); 3030 getObjMethod2.setHeader("If-None-Match", etag); 3031 assertEquals("Client didn't get a NOT_MODIFIED!", NOT_MODIFIED.getStatusCode(), 3032 getStatus(getObjMethod2)); 3033 } 3034 } 3035 } 3036 3037 @Test 3038 public void testValidHTMLForRepo() throws IOException, SAXException { 3039 validateHTML(""); 3040 } 3041 3042 @Test 3043 public void testValidHTMLForObject() throws IOException, SAXException { 3044 final String id = getRandomUniqueId(); 3045 createObjectAndClose(id); 3046 validateHTML(id); 3047 } 3048 3049 @Test 3050 public void testValidHTMLForDS() throws IOException, SAXException { 3051 final String id = getRandomUniqueId(); 3052 createDatastream(id, "ds", "content"); 3053 validateHTML(id + "/ds/" + FCR_METADATA); 3054 } 3055 3056 private static void validateHTML(final String path) throws IOException, SAXException { 3057 final HttpGet getMethod = getObjMethod(path); 3058 getMethod.addHeader(ACCEPT, "text/html"); 3059 try (final CloseableHttpResponse response = execute(getMethod)) { 3060 assertEquals(OK.getStatusCode(), getStatus(response)); 3061 final String content = EntityUtils.toString(response.getEntity()); 3062 logger.trace("Retrieved HTML view:\n" + content); 3063 final HtmlParser htmlParser = new HtmlParser(ALLOW); 3064 htmlParser.setDoctypeExpectation(NO_DOCTYPE_ERRORS); 3065 htmlParser.setErrorHandler(new HTMLErrorHandler()); 3066 htmlParser.setContentHandler(new TreeBuilder()); 3067 try (final InputStream htmlStream = new ByteArrayInputStream(content.getBytes(UTF_8))) { 3068 htmlParser.parse(new InputSource(htmlStream)); 3069 } 3070 logger.debug("HTML found to be valid."); 3071 } 3072 } 3073 3074 private static class HTMLErrorHandler implements ErrorHandler { 3075 3076 @Override 3077 public void warning(final SAXParseException e) { 3078 fail(e.toString()); 3079 } 3080 3081 @Override 3082 public void error(final SAXParseException e) { 3083 fail(e.toString()); 3084 } 3085 3086 @Override 3087 public void fatalError(final SAXParseException e) { 3088 fail(e.toString()); 3089 } 3090 } 3091 3092 @Test 3093 public void testLinkedDeletion() { 3094 final String linkedFrom = getRandomUniqueId(); 3095 final String linkedTo = getRandomUniqueId(); 3096 createObjectAndClose(linkedFrom); 3097 createObjectAndClose(linkedTo); 3098 3099 final String sparql = 3100 "INSERT DATA { <" + serverAddress + linkedFrom + "> " + 3101 "<http://some-vocabulary#isMemberOfCollection> <" + serverAddress + linkedTo + "> . }"; 3102 final HttpPatch patch = patchObjMethod(linkedFrom); 3103 patch.addHeader(CONTENT_TYPE, "application/sparql-update"); 3104 patch.setEntity(new ByteArrayEntity(sparql.getBytes(UTF_8))); 3105 assertEquals("Couldn't link resources!", NO_CONTENT.getStatusCode(), getStatus(patch)); 3106 assertEquals("Error deleting linked-to!", NO_CONTENT.getStatusCode(), getStatus(deleteObjMethod(linkedTo))); 3107 assertEquals("Linked to should still exist!", OK.getStatusCode(), getStatus(getObjMethod(linkedFrom))); 3108 } 3109 3110 @Test 3111 public void testUpdateObjectWithSpacesPost() throws IOException { 3112 final String id = getRandomUniqueId() + " 2"; 3113 final String expectedUri = serverAddress + id.replace(" ", "%20"); 3114 final String notExpectedUri = expectedUri.replace("%", "%25"); 3115 try (final CloseableHttpResponse createResponse = createObject(id)) { 3116 final String subjectURI = getLocation(createResponse); 3117 assertEquals(expectedUri, subjectURI); 3118 final HttpPatch updateObjectGraphMethod = new HttpPatch(subjectURI); 3119 updateObjectGraphMethod.addHeader(CONTENT_TYPE, "application/sparql-update"); 3120 updateObjectGraphMethod.setEntity(new StringEntity( 3121 "INSERT { <> <http://purl.org/dc/elements/1.1/title> \"test\" } WHERE {}")); 3122 assertEquals(NO_CONTENT.getStatusCode(), getStatus(updateObjectGraphMethod)); 3123 } 3124 final HttpGet httpGet = new HttpGet(expectedUri); 3125 try (final var dataset = getDataset(httpGet)) { 3126 final var graph = dataset.asDatasetGraph(); 3127 // Ensure the graph has an encoded URI too. 3128 assertTrue(graph.contains( 3129 ANY, 3130 createURI(expectedUri), 3131 ANY, 3132 ANY 3133 )); 3134 // Ensure it does not have a double encoded URI. 3135 assertFalse(graph.contains( 3136 ANY, 3137 createURI(notExpectedUri), 3138 ANY, 3139 ANY 3140 )); 3141 } 3142 } 3143 3144 @Test 3145 public void testUpdateObjectWithSpacesPut() throws IOException { 3146 final String id = getRandomUniqueId() + "%202"; 3147 final String expectedUri = serverAddress + id; 3148 final String notExpectedUri = expectedUri.replace("%", "%25"); 3149 try (final CloseableHttpResponse createResponse = execute(putObjMethod(id))) { 3150 final String subjectURI = getLocation(createResponse); 3151 assertEquals(expectedUri, subjectURI); 3152 final HttpPatch updateObjectGraphMethod = new HttpPatch(subjectURI); 3153 updateObjectGraphMethod.addHeader(CONTENT_TYPE, "application/sparql-update"); 3154 updateObjectGraphMethod.setEntity(new StringEntity( 3155 "INSERT { <> <http://purl.org/dc/elements/1.1/title> \"test\" } WHERE {}")); 3156 assertEquals(NO_CONTENT.getStatusCode(), getStatus(updateObjectGraphMethod)); 3157 } 3158 final HttpGet httpGet = new HttpGet(expectedUri); 3159 try (final var dataset = getDataset(httpGet)) { 3160 final var graph = dataset.asDatasetGraph(); 3161 // Ensure the graph has an encoded URI too. 3162 assertTrue(graph.contains( 3163 ANY, 3164 createURI(expectedUri), 3165 ANY, 3166 ANY 3167 )); 3168 // Ensure it does not have a double encoded URI. 3169 assertFalse(graph.contains( 3170 ANY, 3171 createURI(notExpectedUri), 3172 ANY, 3173 ANY 3174 )); 3175 } 3176 } 3177 3178 @Test 3179 public void testCreatedAndModifiedDates() throws IOException { 3180 final String location = getLocation(postObjMethod()); 3181 final HttpGet getObjMethod = new HttpGet(location); 3182 try (final CloseableHttpResponse response = execute(getObjMethod)) { 3183 try (final CloseableDataset dataset = getDataset(response)) { 3184 final DatasetGraph graph = dataset.asDatasetGraph(); 3185 final Model model = createModelForGraph(graph.getDefaultGraph()); 3186 final Resource nodeUri = createResource(location); 3187 final String lastmodString = response.getFirstHeader("Last-Modified").getValue(); 3188 final Optional<Instant> createdDateTriples = 3189 getDateFromModel(model, nodeUri, CREATED_DATE); 3190 final Optional<Instant> lastmodDateTriples = 3191 getDateFromModel(model, nodeUri, LAST_MODIFIED_DATE); 3192 assertTrue(createdDateTriples.isPresent()); 3193 assertTrue(lastmodDateTriples.isPresent()); 3194 // Reformatting lastModified header to ensure consistent formatting between it and fedora timestamps 3195 final Instant lastMod = headerFormat.parse(lastmodString, Instant::from); 3196 final String formattedLastModifiedHeader = headerFormat.format(lastMod); 3197 assertEquals(formattedLastModifiedHeader, headerFormat.format(createdDateTriples.get())); 3198 assertEquals(formattedLastModifiedHeader, headerFormat.format(lastmodDateTriples.get())); 3199 } 3200 } 3201 } 3202 3203 @Test 3204 public void testPostCreateDate() throws IOException { 3205 final HttpPost httpPost = postObjMethod("/"); 3206 httpPost.addHeader("Slug", getRandomUniqueId()); 3207 httpPost.addHeader(CONTENT_TYPE, "text/turtle"); 3208 httpPost.setEntity(new StringEntity(getTTLThatUpdatesServerManagedTriples("fakeuser", null, null, null))); 3209 try (final CloseableHttpResponse response = execute(httpPost)) { 3210 assertEquals("Must not be able to update createdBy!", CONFLICT.getStatusCode(), getStatus(response)); 3211 assertConstrainedByPresent(response); 3212 checkForLinkHeader(response, SERVER_MANAGED_PROPERTY_CONSTRAINT_URI, CONSTRAINED_BY.toString()); 3213 } 3214 } 3215 3216 @Test 3217 public void testPutRdfLenient() throws IOException { 3218 final String location = getLocation(postObjMethod()); 3219 final Node uriResource = createURI(location); 3220 final HttpGet getObjMethod = new HttpGet(location); 3221 String createdDate = null; 3222 try (final CloseableHttpResponse response = execute(getObjMethod)) { 3223 assertEquals(OK.getStatusCode(), getStatus(response)); 3224 try (final CloseableDataset dataset = getDataset(response)) { 3225 final DatasetGraph graph = dataset.asDatasetGraph(); 3226 final var iterator = graph.find(ANY, uriResource, CREATED_DATE.asNode(), ANY); 3227 while (iterator.hasNext()) { 3228 final var quad = iterator.next(); 3229 createdDate = quad.getObject().getLiteral().toString(); 3230 } 3231 } 3232 } 3233 final HttpPut putMethod = new HttpPut(location); 3234 putMethod.setHeader(CONTENT_TYPE, "text/turtle"); 3235 putMethod.setEntity(new StringEntity("<> <" + CREATED_DATE + "> \"1979-01-01T00:00:00\" ;" + 3236 "<" + DCTITLE + "> \"This is a new title\" . ")); 3237 // Can't set the created date 3238 assertEquals(CONFLICT.getStatusCode(), getStatus(putMethod)); 3239 3240 // With lenient we ignore it. 3241 putMethod.setHeader("Prefer", "handling=\"lenient\""); 3242 assertEquals(NO_CONTENT.getStatusCode(), getStatus(putMethod)); 3243 3244 final HttpGet getObj2 = new HttpGet(location); 3245 try (final CloseableHttpResponse response = execute(getObj2)) { 3246 assertEquals(OK.getStatusCode(), getStatus(response)); 3247 try (final CloseableDataset dataset = getDataset(response)) { 3248 String createdDate2 = null; 3249 String title = null; 3250 final DatasetGraph graph = dataset.asDatasetGraph(); 3251 final var iterator = graph.find(ANY, uriResource, CREATED_DATE.asNode(), ANY); 3252 while (iterator.hasNext()) { 3253 final var quad = iterator.next(); 3254 createdDate2 = quad.getObject().getLiteral().toString(); 3255 } 3256 final var iterator2 = graph.find(ANY, uriResource, DCTITLE, ANY); 3257 while (iterator2.hasNext()) { 3258 final var quad = iterator2.next(); 3259 title = quad.getObject().getLiteral().toString(); 3260 } 3261 // Created date change did not take effect. 3262 assertEquals(createdDate, createdDate2); 3263 // But the new title is there. 3264 assertEquals("This is a new title", title); 3265 } 3266 } 3267 } 3268 3269 @Test 3270 public void testNonRDFSourceInteraction() throws IOException { 3271 final String id = getRandomUniqueId(); 3272 final HttpPut put = putObjMethod(id, "text/turtle", "<> a <http://example.com/Foo> ."); 3273 put.setHeader(LINK, NON_RDF_SOURCE_LINK_HEADER); 3274 executeAndClose(put); 3275 assertTrue(getLinkHeaders(getObjMethod(id)).contains(NON_RDF_SOURCE_LINK_HEADER)); 3276 } 3277 3278 @Test 3279 public void testContainerInteraction() throws IOException { 3280 final String id = getRandomUniqueId(); 3281 final String location; 3282 final HttpPut put0 = putObjMethod(id); 3283 put0.setHeader(LINK, BASIC_CONTAINER_LINK_HEADER); 3284 try (final CloseableHttpResponse response = execute(put0)) { 3285 location = getLocation(response); 3286 } 3287 assertTrue(getLinkHeaders(getObjMethod(id)).contains(BASIC_CONTAINER_LINK_HEADER)); 3288 3289 final String ttl = "<> <" + MEMBERSHIP_RESOURCE + "> <" + location + ">; " 3290 + "<" + HAS_MEMBER_RELATION + "> <info:some/relation> .\n"; 3291 final HttpPut put1 = putObjMethod(id + "/a", "text/turtle", ttl); 3292 put1.setHeader(LINK, DIRECT_CONTAINER_LINK_HEADER); 3293 executeAndClose(put1); 3294 assertTrue(getLinkHeaders(getObjMethod(id + "/a")).contains(DIRECT_CONTAINER_LINK_HEADER)); 3295 createObject(id + "/a/1"); 3296 3297 final String ttl2 = "<> <" + MEMBERSHIP_RESOURCE + "> <" + location + ">; " 3298 + "<" + HAS_MEMBER_RELATION + "> <info:some/another-relation> .\n"; 3299 final HttpPut put2 = putObjMethod(id + "/b", "text/turtle", ttl2); 3300 put2.setHeader(LINK, DIRECT_CONTAINER_LINK_HEADER); 3301 executeAndClose(put2); 3302 assertTrue(getLinkHeaders(getObjMethod(id + "/b")).contains(DIRECT_CONTAINER_LINK_HEADER)); 3303 createObject(id + "/b/1"); 3304 3305 try (final CloseableDataset dataset = getDataset(new HttpGet(location))) { 3306 final DatasetGraph graphStore = dataset.asDatasetGraph(); 3307 final Node resource = createURI(location); 3308 assertTrue("Expected to have container t", graphStore.contains(ANY, 3309 resource, CONTAINS.asNode(), createURI(location + "/a"))); 3310 assertTrue("Expected to have container b", graphStore.contains(ANY, 3311 resource, CONTAINS.asNode(), createURI(location + "/b"))); 3312 assertTrue("Expected member relation", graphStore.contains(ANY, 3313 resource, createURI("info:some/relation"), createURI(location + "/a/1"))); 3314 assertTrue("Expected other member relation", graphStore.contains(ANY, 3315 resource, createURI("info:some/another-relation"), createURI(location + "/b/1"))); 3316 } 3317 } 3318 3319 @Test 3320 public void testIndirectContainerInteraction() throws IOException { 3321 3322 // Create resource (object) 3323 final String resourceId = getRandomUniqueId(); 3324 final String resource; 3325 try (final CloseableHttpResponse createResponse = createObject(resourceId)) { 3326 resource = getLocation(createResponse); 3327 } 3328 // Create container (c0) 3329 final String containerId = getRandomUniqueId(); 3330 final String container; 3331 try (final CloseableHttpResponse createResponse = createObject(containerId)) { 3332 container = getLocation(createResponse); 3333 } 3334 // Create indirect container (c0/members) 3335 final String indirectContainerId = containerId + "/t"; 3336 final String indirectContainer; 3337 final String ttl = "<> <" + MEMBERSHIP_RESOURCE + "> <" + container + ">;\n" 3338 + "<" + HAS_MEMBER_RELATION + "> <info:some/relation>;\n" 3339 + "<" + INSERTED_CONTENT_RELATION + "> <info:proxy/for> .\n"; 3340 final HttpPut put = putObjMethod(containerId + "/t", "text/turtle", ttl); 3341 put.setHeader(LINK, INDIRECT_CONTAINER_LINK_HEADER); 3342 try (final CloseableHttpResponse response = execute(put)) { 3343 indirectContainer = getLocation(response); 3344 } 3345 assertTrue(getLinkHeaders(getObjMethod(containerId + "/t")).contains(INDIRECT_CONTAINER_LINK_HEADER)); 3346 3347 // Add indirect resource to indirect container 3348 final HttpPost postIndirectResource = postObjMethod(indirectContainerId); 3349 final String irRdf = 3350 "<> <info:proxy/in> <" + container + "> ;\n" + 3351 " <info:proxy/for> <" + resource + "> ."; 3352 postIndirectResource.setEntity(new StringEntity(irRdf)); 3353 postIndirectResource.setHeader(CONTENT_TYPE, "text/turtle"); 3354 3355 final String indirectResource; 3356 try (final CloseableHttpResponse postResponse = execute(postIndirectResource)) { 3357 indirectResource = getLocation(postResponse); 3358 assertEquals("Expected post to succeed", CREATED.getStatusCode(), getStatus(postResponse)); 3359 } 3360 // Ensure container has been updated with relationship... indirectly 3361 try (final CloseableHttpResponse getResponse = execute(new HttpGet(container)); 3362 final CloseableDataset dataset = getDataset(getResponse)) { 3363 final DatasetGraph graphStore = dataset.asDatasetGraph(); 3364 assertTrue("Expected to have indirect container", graphStore.contains(ANY, 3365 createURI(container), CONTAINS.asNode(), createURI(indirectContainer))); 3366 3367 assertTrue("Expected to have resource: " + graphStore.toString(), graphStore.contains(ANY, 3368 createURI(container), createURI("info:some/relation"), createURI(resource))); 3369 } 3370 // Remove indirect resource 3371 assertEquals("Expected delete to succeed", 3372 NO_CONTENT.getStatusCode(), getStatus(new HttpDelete(indirectResource))); 3373 3374 // Ensure container has been updated with relationship... indirectly 3375 try (final CloseableHttpResponse getResponse1 = execute(new HttpGet(container)); 3376 final CloseableDataset dataset = getDataset(getResponse1)) { 3377 final DatasetGraph graph = dataset.asDatasetGraph(); 3378 assertFalse("Expected NOT to have resource: " + graph, graph.contains(ANY, 3379 createURI(container), createURI("info:some/relation"), createURI(resource))); 3380 } 3381 } 3382 3383 @Test 3384 public void testWithHashUris() throws IOException { 3385 final HttpPost method = postObjMethod(); 3386 method.addHeader(CONTENT_TYPE, "text/turtle"); 3387 method.setEntity(new StringEntity("<> <info:some-predicate> <#abc> .\n" 3388 + "<#abc> <info:test#label> \"asdfg\" .")); 3389 try (final CloseableHttpResponse response = execute(method)) { 3390 assertEquals("Didn't get a CREATED response!", CREATED.getStatusCode(), getStatus(response)); 3391 final String location = getLocation(response); 3392 try (final CloseableDataset dataset = getDataset(new HttpGet(location))) { 3393 final DatasetGraph graphStore = dataset.asDatasetGraph(); 3394 assertTrue(graphStore.contains(ANY, 3395 createURI(location), createURI("info:some-predicate"), createURI(location + "#abc"))); 3396 assertTrue(graphStore.contains(ANY, 3397 createURI(location + "#abc"), createURI("info:test#label"), createLiteral("asdfg"))); 3398 assertFalse(graphStore.contains(ANY, 3399 createURI(location + "#abc"), LAST_MODIFIED_DATE.asNode(), ANY)); 3400 assertFalse(graphStore.contains(ANY, 3401 createURI(location + "#abc"), rdfType, FEDORA_RESOURCE.asNode())); 3402 } 3403 } 3404 } 3405 3406 @Test 3407 public void testCreateAndReplaceGraphMinimal() throws IOException { 3408 LOGGER.trace("Entering testCreateAndReplaceGraphMinimal()..."); 3409 final HttpPost httpPost = postObjMethod("/"); 3410 httpPost.addHeader("Slug", getRandomUniqueId()); 3411 httpPost.addHeader(CONTENT_TYPE, "text/turtle"); 3412 httpPost.setEntity(new StringEntity("<> <" + DCTITLE.getURI() + "> \"abc\"")); 3413 final String subjectURI; 3414 try (final CloseableHttpResponse response = execute(httpPost)) { 3415 assertEquals("Didn't get a CREATED response!", CREATED.getStatusCode(), getStatus(response)); 3416 subjectURI = getLocation(response); 3417 } 3418 final HttpPut replaceMethod = new HttpPut(subjectURI); 3419 replaceMethod.addHeader(CONTENT_TYPE, "text/turtle"); 3420 replaceMethod.addHeader("Prefer", "handling=lenient"); 3421 replaceMethod.setEntity(new StringEntity("<> <" + DCTITLE.getURI() + "> \"xyz\"")); 3422 assertEquals(NO_CONTENT.getStatusCode(), getStatus(replaceMethod)); 3423 3424 final HttpGet get = new HttpGet(subjectURI); 3425 get.addHeader("Prefer", "return=representation; include=\"" + PREFER_MINIMAL_CONTAINER + "\""); 3426 try (final CloseableDataset dataset = getDataset(get)) { 3427 assertTrue(dataset.asDatasetGraph().contains(ANY, ANY, DCTITLE, createLiteral("xyz"))); 3428 } 3429 LOGGER.trace("Done with testCreateAndReplaceGraphMinimal()."); 3430 } 3431 3432 @Test 3433 public void testAclHeaderWithPost() throws IOException { 3434 final String pid = getRandomUniqueId(); 3435 final HttpPost httpPost = postObjMethod("/"); 3436 httpPost.addHeader("Slug", pid); 3437 final String location = serverAddress + pid; 3438 try (final CloseableHttpResponse response = execute(httpPost)) { 3439 assertEquals(CREATED.getStatusCode(), getStatus(response)); 3440 checkForLinkHeader(response, location + "/" + FCR_ACL, "acl"); 3441 } 3442 } 3443 3444 @Test 3445 public void testAclHeaderWithPut() throws IOException { 3446 final String pid = getRandomUniqueId(); 3447 final String location = serverAddress + pid; 3448 3449 final HttpPut httpPut = new HttpPut(location); 3450 try (final CloseableHttpResponse response = execute(httpPut)) { 3451 assertEquals(CREATED.getStatusCode(), getStatus(response)); 3452 checkForLinkHeader(response, location + "/" + FCR_ACL, "acl"); 3453 } 3454 } 3455 3456 @Test 3457 public void testJsonLdProfileCompacted() throws IOException { 3458 // Create a resource 3459 final HttpPost method = postObjMethod(); 3460 method.addHeader(CONTENT_TYPE, "text/n3"); 3461 final BasicHttpEntity entity = new BasicHttpEntity(); 3462 final String rdf = "<> <http://purl.org/dc/elements/1.1/title> \"ceci n'est pas un titre français\"@fr ." + 3463 "<> <http://purl.org/dc/elements/1.1/title> \"this is an english title\"@en ."; 3464 entity.setContent(new ByteArrayInputStream(rdf.getBytes(UTF_8))); 3465 method.setEntity(entity); 3466 3467 final String location; 3468 try (final CloseableHttpResponse response = execute(method)) { 3469 assertEquals("Didn't get a CREATED response!", CREATED.getStatusCode(), getStatus(response)); 3470 location = response.getFirstHeader("Location").getValue(); 3471 } 3472 // GET the resource with a JSON profile 3473 final HttpGet httpGet = new HttpGet(location); 3474 httpGet.setHeader(ACCEPT, "application/ld+json; profile=\"http://www.w3.org/ns/json-ld#compacted\""); 3475 final JsonNode json; 3476 try (final CloseableHttpResponse responseGET = execute(httpGet)) { 3477 // Inspect the response 3478 final ObjectMapper mapper = new ObjectMapper(); 3479 json = mapper.readTree(responseGET.getEntity().getContent()); 3480 } 3481 3482 final JsonNode titles = json.get("title"); 3483 assertNotNull(titles); 3484 assertTrue("Should be a list", titles.isArray()); 3485 3486 assertEquals("Should be two langs!", 2, titles.findValues("@language").size()); 3487 assertEquals("Should be two values!", 2, titles.findValues("@value").size()); 3488 } 3489 3490 @Test 3491 public void testJsonLdProfileExpanded() throws IOException { 3492 // Create a resource 3493 final HttpPost method = postObjMethod(); 3494 method.addHeader(CONTENT_TYPE, "text/n3"); 3495 final BasicHttpEntity entity = new BasicHttpEntity(); 3496 final String rdf = "<> <http://purl.org/dc/elements/1.1/title> \"ceci n'est pas un titre français\"@fr ." + 3497 "<> <http://purl.org/dc/elements/1.1/title> \"this is an english title\"@en ."; 3498 entity.setContent(new ByteArrayInputStream(rdf.getBytes(UTF_8))); 3499 method.setEntity(entity); 3500 3501 final String location; 3502 try (final CloseableHttpResponse response = execute(method)) { 3503 assertEquals("Didn't get a CREATED response!", CREATED.getStatusCode(), getStatus(response)); 3504 location = response.getFirstHeader("Location").getValue(); 3505 } 3506 // GET the resource with a JSON profile 3507 final HttpGet httpGet = new HttpGet(location); 3508 httpGet.setHeader(ACCEPT, "application/ld+json; profile=\"http://www.w3.org/ns/json-ld#expanded\""); 3509 final JsonNode json; 3510 try (final CloseableHttpResponse responseGET = execute(httpGet)) { 3511 // Inspect the response 3512 final ObjectMapper mapper = new ObjectMapper(); 3513 json = mapper.readTree(responseGET.getEntity().getContent()); 3514 } 3515 3516 final List<JsonNode> titlesList = json.findValues("http://purl.org/dc/elements/1.1/title"); 3517 assertNotNull(titlesList); 3518 assertEquals("Should be list of lists", 1, titlesList.size()); 3519 3520 final JsonNode titles = titlesList.get(0); 3521 assertEquals("Should be two langs!", 2, titles.findValues("@language").size()); 3522 assertEquals("Should be two values!", 2, titles.findValues("@value").size()); 3523 } 3524 3525 @Test 3526 public void testJsonLdProfileFlattened() throws IOException { 3527 // Create a resource 3528 final HttpPost method = postObjMethod(); 3529 method.addHeader(CONTENT_TYPE, "text/n3"); 3530 final BasicHttpEntity entity = new BasicHttpEntity(); 3531 final String rdf = "<> <http://purl.org/dc/elements/1.1/title> \"ceci n'est pas un titre français\"@fr ." + 3532 "<> <http://purl.org/dc/elements/1.1/title> \"this is an english title\"@en ."; 3533 entity.setContent(new ByteArrayInputStream(rdf.getBytes(UTF_8))); 3534 method.setEntity(entity); 3535 3536 final String location; 3537 try (final CloseableHttpResponse response = execute(method)) { 3538 assertEquals("Didn't get a CREATED response!", CREATED.getStatusCode(), getStatus(response)); 3539 location = response.getFirstHeader("Location").getValue(); 3540 } 3541 // GET the resource with a JSON profile 3542 final HttpGet httpGet = new HttpGet(location); 3543 httpGet.setHeader(ACCEPT, "application/ld+json; profile=\"http://www.w3.org/ns/json-ld#flattened\""); 3544 final JsonNode json; 3545 try (final CloseableHttpResponse responseGET = execute(httpGet)) { 3546 // Inspect the response 3547 final ObjectMapper mapper = new ObjectMapper(); 3548 json = mapper.readTree(responseGET.getEntity().getContent()); 3549 } 3550 3551 final List<JsonNode> titlesList = json.get("@graph").findValues("title"); 3552 assertNotNull(titlesList); 3553 assertEquals("Should be list of lists", 1, titlesList.size()); 3554 3555 final JsonNode titles = titlesList.get(0); 3556 assertEquals("Should be two langs!", 2, titles.findValues("@language").size()); 3557 assertEquals("Should be two values!", 2, titles.findValues("@value").size()); 3558 } 3559 3560 @Test 3561 public void testPathWithEmptySegment() { 3562 // Ensure HttpClient does not remove empty paths 3563 final RequestConfig config = RequestConfig.custom().setNormalizeUri(false).build(); 3564 final String badLocation = "test/me/mb/er/s//members/9528a300-22da-40f2-bf3c-5b345d71affb"; 3565 final var getEmpty = headObjMethod(badLocation); 3566 getEmpty.setConfig(config); 3567 assertEquals(BAD_REQUEST.getStatusCode(), getStatus(getEmpty)); 3568 } 3569 3570 private static Optional<Instant> getDateFromModel(final Model model, final Resource subj, final Property pred) 3571 throws NoSuchElementException { 3572 final StmtIterator stmts = model.listStatements(subj, pred, (String) null); 3573 return Optional.ofNullable( 3574 stmts.hasNext() ? Instant.from(tripleFormat.parse(stmts.nextStatement().getString())) : null); 3575 } 3576 3577 @Test 3578 public void testUpdateObjectGraphWithNonLocalTriples() throws IOException { 3579 final String pid = getRandomUniqueId(); 3580 createObject(pid); 3581 final String otherPid = getRandomUniqueId(); 3582 createObject(otherPid); 3583 final String location = serverAddress + pid; 3584 final String otherLocation = serverAddress + otherPid; 3585 final HttpPatch updateObjectGraphMethod = new HttpPatch(location); 3586 updateObjectGraphMethod.addHeader(CONTENT_TYPE, "application/sparql-update"); 3587 updateObjectGraphMethod.setEntity(new StringEntity("INSERT { <" + location + 3588 "> <http://purl.org/dc/elements/1.1/identifier> \"this is an identifier\". " + "<" + otherLocation + 3589 "> <http://purl.org/dc/elements/1.1/identifier> \"this is an identifier\"" + " } WHERE {}")); 3590 assertEquals("It ought not be possible to use PATCH to create non-local triples!", 3591 NO_CONTENT.getStatusCode(),getStatus(updateObjectGraphMethod)); 3592 } 3593 3594 @Test 3595 public void testPutMalformedHeader() throws IOException { 3596 // Create a resource 3597 final String id = getRandomUniqueId(); 3598 executeAndClose(putObjMethod(id)); 3599 3600 // Get the resource's etag 3601 final String etag; 3602 final HttpHead httpHead = headObjMethod(id); 3603 try (final CloseableHttpResponse response = execute(httpHead)) { 3604 etag = response.getFirstHeader("ETag").getValue(); 3605 assertNotNull("ETag was missing!?", etag); 3606 } 3607 3608 // PUT properly formatted etag 3609 final HttpPut httpPut = putObjMethod(id); 3610 httpPut.addHeader("If-Match", etag); 3611 3612 try (final CloseableHttpResponse response = execute(httpPut)) { 3613 assertEquals("Should be a 412 Precondition Failed!", PRECONDITION_FAILED.getStatusCode(), 3614 getStatus(response)); 3615 } 3616 3617 // PUT improperly formatted etag ... not quoted. 3618 final HttpPut httpPut2 = putObjMethod(id); 3619 httpPut2.addHeader("If-Match", etag.replace("\"", "")); 3620 3621 try (final CloseableHttpResponse response = execute(httpPut2)) { 3622 assertEquals("Should be a 400 BAD REQUEST!", BAD_REQUEST.getStatusCode(), getStatus(response)); 3623 } 3624 } 3625 3626 @Test 3627 public void testPutServerManagedPredicateInIndirectContainer() throws IOException { 3628 LOGGER.info("running testPutServerManagedPredicateInIndirectContainer"); 3629 // Create a resource 3630 final String id = getRandomUniqueId(); 3631 executeAndClose(putObjMethod(id)); 3632 3633 final String child1 = id + "/1"; 3634 executeAndClose(putObjMethod(child1)); 3635 3636 // create indirect container using a server managed triple. 3637 final String body = 3638 "@prefix ore: <http://www.openarchives.org/ore/terms/> .\n" + 3639 "@prefix ldp: <http://www.w3.org/ns/ldp#> .\n" + 3640 "@prefix pcdm: <http://pcdm.org/models#> .\n" + 3641 "<> a ldp:IndirectContainer ;\n" + 3642 " ldp:hasMemberRelation <http://fedora.info/definitions/v4/repository#lastModified> ; \n" + 3643 " ldp:insertedContentRelation ore:proxyFor ; \n" + 3644 " ldp:membershipResource <" + serverAddress + child1 + "> . "; 3645 final String child1MembersId = child1 + "/members"; 3646 final HttpPut httpPut = putObjMethod(child1MembersId); 3647 httpPut.addHeader("Content-Type", "text/turtle"); 3648 httpPut.setEntity(new StringEntity(body)); 3649 3650 try (final CloseableHttpResponse response = execute(httpPut)) { 3651 assertEquals("Should be a 409 CONFLICT!", CONFLICT.getStatusCode(), getStatus(response)); 3652 } 3653 } 3654 3655 @Test 3656 public void testPutEmptyBody() throws IOException { 3657 final HttpPut httpPut = putObjMethod(getRandomUniqueId()); 3658 httpPut.addHeader(CONTENT_TYPE, "application/ld+json"); 3659 3660 try (final CloseableHttpResponse response = execute(httpPut)) { 3661 assertEquals("Should be a client error", BAD_REQUEST.getStatusCode(), getStatus(response)); 3662 } 3663 } 3664 3665 @Test 3666 public void testPutOgg() throws IOException { 3667 final String id = getRandomUniqueId(); 3668 execute(putObjMethod(id)); 3669 createDatastream(id, "x", "OggS"); 3670 } 3671 3672 @Test 3673 public void testPutReferenceRoot() throws Exception { 3674 final HttpPut httpPut = putObjMethod(getRandomUniqueId()); 3675 httpPut.addHeader(CONTENT_TYPE, "text/turtle"); 3676 httpPut.setEntity(new StringEntity("@prefix acl: <http://www.w3.org/ns/auth/acl#> . " + 3677 "<> a acl:Authorization ; " + 3678 "acl:agent \"smith123\" ; " + 3679 "acl:mode acl:Read ;" + 3680 "acl:accessTo <" + serverAddress + "> .")); 3681 3682 try (final CloseableHttpResponse response = execute(httpPut)) { 3683 assertEquals(CREATED.getStatusCode(), getStatus(response)); 3684 } 3685 } 3686 3687 @Test 3688 public void testDeleteLargeLiteralStoredAsBinary() throws IOException { 3689 3690 final String pid = getRandomUniqueId(); 3691 createObject(pid); 3692 3693 final Node DC_TITLE = title.asNode(); 3694 final String location = serverAddress + pid; 3695 final HttpPatch patch = new HttpPatch(location); 3696 final HttpPatch delPatch = new HttpPatch(location); 3697 3698 final String longLiteral = // minimumBinaryInByteSize is currently 40 bytes 3699 TEST_BINARY_CONTENT + 3700 TEST_BINARY_CONTENT + 3701 TEST_BINARY_CONTENT + 3702 TEST_BINARY_CONTENT; 3703 3704 patch.addHeader(CONTENT_TYPE, "application/sparql-update"); 3705 patch.setEntity(new StringEntity( 3706 "INSERT { <> <" + DC_TITLE + "> \"\"\"" + longLiteral + "\"\"\" } WHERE {}")); 3707 3708 assertEquals("Unable to add property value", NO_CONTENT.getStatusCode(), getStatus(patch)); 3709 3710 delPatch.addHeader(CONTENT_TYPE, "application/sparql-update"); 3711 delPatch.setEntity(new StringEntity( 3712 "DELETE WHERE { <> <" + DC_TITLE + "> \"\"\"" + longLiteral + "\"\"\"}")); 3713 3714 // delete that triple, or at least try to. 3715 assertEquals("Unable to complete delete property HTTP request", NO_CONTENT.getStatusCode(), 3716 getStatus(delPatch)); 3717 3718 // now test if property exists anymore (it shouldn't). 3719 try (final CloseableDataset dataset = getDataset(getObjMethod(pid))) { 3720 assertFalse("Found the literal we tried to delete!", dataset.asDatasetGraph().contains(ANY, 3721 createURI(location), DC_TITLE, createLiteral(longLiteral))); 3722 } 3723 } 3724 3725 @Test 3726 public void testInboundLinksDoNotUpdateEtag() throws IOException { 3727 final String id1 = getRandomUniqueId(); 3728 final HttpPut httpPut = putObjMethod(id1); 3729 final String oldETag; 3730 final String oldMod; 3731 try (final CloseableHttpResponse response = execute(httpPut)) { 3732 assertEquals(CREATED.getStatusCode(), getStatus(response)); 3733 oldETag = response.getFirstHeader("ETag").getValue(); 3734 oldMod = response.getFirstHeader("Last-Modified").getValue(); 3735 } 3736 3737 final String id2 = getRandomUniqueId(); 3738 createObject(id2).close(); 3739 3740 final HttpPatch patch = patchObjMethod(id2); 3741 patch.addHeader(CONTENT_TYPE, "application/sparql-update"); 3742 patch.setEntity(new StringEntity( 3743 "INSERT { <> <http://purl.org/dc/elements/1.1/relation> <" + serverAddress + id1 + "> } WHERE {}")); 3744 assertEquals(NO_CONTENT.getStatusCode(), getStatus(execute(patch))); 3745 3746 try (final CloseableHttpResponse response = execute(getObjMethod(id1))) { 3747 final String etag = response.getFirstHeader("ETag").getValue(); 3748 final String lastmod = response.getFirstHeader("Last-Modified").getValue(); 3749 assertEquals(oldMod, lastmod); 3750 assertEquals(oldETag, etag); 3751 } 3752 } 3753 3754 @Test 3755 public void testCreationResponseDefault() { 3756 testCreationResponse(null, null, CREATED, "text/plain"); 3757 testCreationResponse(null, "application/ld+json", NOT_ACCEPTABLE, "text/html"); 3758 } 3759 3760 @Test 3761 public void testCreationResponseMinimal() { 3762 testCreationResponse("minimal", null, CREATED, null); 3763 testCreationResponse("minimal", "application/ld+json", CREATED, null); 3764 } 3765 3766 @Test 3767 public void testCreationResponseRepresentation() { 3768 testCreationResponse("representation", null, CREATED, "text/turtle"); 3769 testCreationResponse("representation", "application/ld+json", CREATED, "application/ld+json"); 3770 } 3771 3772 private static void testCreationResponse(final String prefer, 3773 final String accept, 3774 final Status expectedStatus, 3775 final String expectedType) { 3776 3777 final HttpPost createMethod = new HttpPost(serverAddress); 3778 if (prefer != null) { 3779 createMethod.addHeader("Prefer", "return=" + prefer); 3780 } 3781 if (accept != null) { 3782 createMethod.addHeader(ACCEPT, accept); 3783 } 3784 3785 try (final CloseableHttpResponse createResponse = execute(createMethod)) { 3786 assertEquals(expectedStatus.getStatusCode(), createResponse.getStatusLine().getStatusCode()); 3787 if (expectedType == null) { 3788 assertNull(createResponse.getFirstHeader(CONTENT_TYPE)); 3789 } else { 3790 assertTrue(createResponse.getFirstHeader(CONTENT_TYPE).getValue().startsWith(expectedType)); 3791 } 3792 } catch (final IOException e) { 3793 throw new AssertionError(e); 3794 } 3795 } 3796 3797 @Test 3798 public void testConcurrentPuts() throws InterruptedException, IOException { 3799 final String parent = getRandomUniqueId(); 3800 executeAndClose(putObjMethod(parent)); 3801 final String newResource = parent + "/test"; 3802 final Runnable updateRunnable = () -> { 3803 executeAndClose(putObjMethod(newResource)); 3804 }; 3805 final Thread t1 = new Thread(updateRunnable); 3806 final Thread t2 = new Thread(updateRunnable); 3807 final Thread t3 = new Thread(updateRunnable); 3808 final Thread t4 = new Thread(updateRunnable); 3809 t1.start(); 3810 t2.start(); 3811 t3.start(); 3812 t4.start(); 3813 t1.join(); 3814 t2.join(); 3815 t3.join(); 3816 t4.join(); 3817 3818 try (final CloseableDataset dataset = getDataset(getObjMethod(parent))) { 3819 final DatasetGraph graphStore = dataset.asDatasetGraph(); 3820 final Iterator<Quad> children = graphStore.find(ANY, ANY, CONTAINS.asNode(), ANY); 3821 assertTrue("One of the PUTs should have resulted in a child.", children.hasNext()); 3822 children.next(); 3823 if (children.hasNext()) { 3824 fail("Only one of the PUTs should have resulted in a child (unexpected child " 3825 + children.next().getObject().getURI() + ")!"); 3826 } 3827 } 3828 } 3829 3830 @Test 3831 public void testConcurrentPutsWithPairtrees() throws InterruptedException, IOException { 3832 final String parent = getRandomUniqueId(); 3833 executeAndClose(putObjMethod(parent)); 3834 final String first = parent + "/00/1"; 3835 final String second = parent + "/00/2"; 3836 final String third = parent + "/00/3"; 3837 final String fourth = parent + "/00/4"; 3838 final Thread t1 = new Thread(() -> { 3839 executeAndClose(putObjMethod(first)); 3840 }); 3841 final Thread t2 = new Thread(() -> { 3842 executeAndClose(putObjMethod(second)); 3843 }); 3844 final Thread t3 = new Thread(() -> { 3845 executeAndClose(putObjMethod(third)); 3846 }); 3847 final Thread t4 = new Thread(() -> { 3848 executeAndClose(putObjMethod(fourth)); 3849 }); 3850 t1.start(); 3851 t2.start(); 3852 t3.start(); 3853 t4.start(); 3854 t1.join(); 3855 t2.join(); 3856 t3.join(); 3857 t4.join(); 3858 3859 try (final CloseableDataset dataset = getDataset(getObjMethod(parent))) { 3860 final DatasetGraph graphStore = dataset.asDatasetGraph(); 3861 final List<String> childPaths = new ArrayList<>(); 3862 final Iterator<Quad> children = graphStore.find(ANY, ANY, CONTAINS.asNode(), ANY); 3863 assertTrue("Four children should have been created (none found).", children.hasNext()); 3864 childPaths.add(children.next().getObject().getURI()); 3865 LOGGER.info("Found child: {}", childPaths.get(0)); 3866 assertTrue("Four children should have been created (only one found).", children.hasNext()); 3867 childPaths.add(children.next().getObject().getURI()); 3868 LOGGER.info("Found child: {}", childPaths.get(1)); 3869 assertTrue("Four children should have been created (only two found).", children.hasNext()); 3870 childPaths.add(children.next().getObject().getURI()); 3871 LOGGER.info("Found child: {}", childPaths.get(2)); 3872 assertTrue("Four children should have been created. (only three found)", children.hasNext()); 3873 childPaths.add(children.next().getObject().getURI()); 3874 LOGGER.info("Found child: {}", childPaths.get(3)); 3875 assertFalse("Only four children should have been created.", children.hasNext()); 3876 assertTrue(childPaths.contains(serverAddress + first)); 3877 assertTrue(childPaths.contains(serverAddress + second)); 3878 assertTrue(childPaths.contains(serverAddress + third)); 3879 assertTrue(childPaths.contains(serverAddress + fourth)); 3880 } 3881 3882 } 3883 3884 @Test 3885 public void testConcurrentUpdatesToBinary() throws IOException, InterruptedException { 3886 // create a binary 3887 final String path = getRandomUniqueId(); 3888 3889 final HttpPut method = new HttpPut(serverAddress + path); 3890 method.setHeader(CONTENT_TYPE, "text/plain"); 3891 method.setHeader(LINK, NON_RDF_SOURCE_LINK_HEADER); 3892 method.setEntity(new StringEntity("initial value")); 3893 3894 final String binaryEtag; 3895 3896 try (final CloseableHttpResponse response = execute(method)) { 3897 binaryEtag = response.getFirstHeader("ETag").getValue(); 3898 } 3899 3900 final var phaser = new Phaser(5); 3901 3902 final RequestThread[] threads = new RequestThread[] { 3903 new RequestThread(putBinaryObjMethodIfMatch(path, binaryEtag, "thread 1"), phaser), 3904 new RequestThread(putBinaryObjMethodIfMatch(path, binaryEtag, "thread 2"), phaser), 3905 new RequestThread(putBinaryObjMethodIfMatch(path, binaryEtag, "thread 3"), phaser), 3906 new RequestThread(putBinaryObjMethodIfMatch(path, binaryEtag, "thread 4"), phaser) 3907 }; 3908 3909 for (final RequestThread t : threads) { 3910 t.start(); 3911 } 3912 3913 phaser.arriveAndAwaitAdvance(); 3914 3915 final List<RequestThread> successfulThreads = new ArrayList<>(); 3916 for (final RequestThread t : threads) { 3917 t.join(1000); 3918 assertFalse("Thread " + t.getId() + " could not perform its operation in time!", t.isAlive()); 3919 final int status = t.response.getStatusLine().getStatusCode(); 3920 LOGGER.info("{} received a {} status code.", t.getId(), status); 3921 if (status == 204) { 3922 successfulThreads.add(t); 3923 } 3924 } 3925 3926 assertEquals("Only one PUT request should have been successful!", 1, successfulThreads.size()); 3927 } 3928 3929 private static class RequestThread extends Thread { 3930 3931 private final HttpUriRequest request; 3932 3933 private final Phaser phaser; 3934 3935 private HttpResponse response; 3936 3937 public RequestThread(final HttpUriRequest request, final Phaser phaser) { 3938 this.request = request; 3939 this.phaser = phaser; 3940 } 3941 3942 @Override 3943 public void run() { 3944 try { 3945 phaser.arriveAndAwaitAdvance(); 3946 response = execute(request); 3947 } catch (final IOException e) { 3948 LOGGER.error("Thread " + Thread.currentThread().getId() + ", failed to request!", e); 3949 } 3950 3951 } 3952 } 3953 3954 private HttpPut putBinaryObjMethodIfMatch(final String location, final String etag, final String content) 3955 throws IOException { 3956 final HttpPut put = putObjMethod(location, "text/plain", content); 3957 put.setHeader("If-Match", etag); 3958 return put; 3959 } 3960 3961 @Test 3962 public void testBinaryLastModified() throws Exception { 3963 final String objid = getRandomUniqueId(); 3964 final String objURI = serverAddress + objid; 3965 final String binURI = objURI + "/binary1"; 3966 3967 final Instant lastmod1; 3968 try (final CloseableHttpResponse response = execute(putDSMethod(objid, "binary1", "some test content"))) { 3969 assertEquals(CREATED.getStatusCode(), getStatus(response)); 3970 lastmod1 = Instant.from(headerFormat.parse(response.getFirstHeader("Last-Modified").getValue())); 3971 } 3972 3973 sleep(1000); // wait a second to make sure last-modified value will be different 3974 3975 try (final CloseableDataset dataset = getDataset(new HttpGet(binURI + "/fcr:metadata"))) { 3976 verifyModifiedMatchesCreated(dataset); 3977 } 3978 3979 final HttpPatch patchBinary = new HttpPatch(binURI + "/fcr:metadata"); 3980 patchBinary.addHeader(CONTENT_TYPE, "application/sparql-update"); 3981 patchBinary.setEntity(new StringEntity("INSERT { <" + binURI + "> " + 3982 "<http://www.w3.org/TR/rdf-schema/label> \"this is a label\" } WHERE {}")); 3983 3984 final Instant lastmod2; 3985 try (final CloseableHttpResponse response = execute(patchBinary)) { 3986 assertEquals(NO_CONTENT.getStatusCode(), getStatus(response)); 3987 lastmod2 = Instant.from(headerFormat.parse(response.getFirstHeader("Last-Modified").getValue())); 3988 assertTrue(lastmod2.isAfter(lastmod1)); 3989 } 3990 3991 sleep(1000); // wait a second to make sure last-modified value will be different 3992 3993 final Instant lastmod3; 3994 try (final CloseableHttpResponse response = execute(putDSMethod(objid, "binary1", "new test content"))) { 3995 assertEquals(NO_CONTENT.getStatusCode(), getStatus(response)); 3996 lastmod3 = Instant.from(headerFormat.parse(response.getFirstHeader("Last-Modified").getValue())); 3997 assertTrue(lastmod3.isAfter(lastmod2)); 3998 } 3999 } 4000 4001 @Test 4002 public void testContainerLastModified() throws Exception { 4003 final String objid = getRandomUniqueId(); 4004 final String objURI = serverAddress + objid; 4005 4006 // create an object 4007 final long lastmod1; 4008 try (final CloseableHttpResponse response = execute(putObjMethod(objid))) { 4009 assertEquals(CREATED.getStatusCode(), getStatus(response)); 4010 lastmod1 = Instant.from( 4011 headerFormat.parse(response.getFirstHeader("Last-Modified").getValue())).toEpochMilli(); 4012 } 4013 4014 sleep(1000); // wait a second to make sure last-modified value will be different 4015 4016 // initial created and last-modified properties should match 4017 try (final CloseableDataset dataset = getDataset(getObjMethod(objid))) { 4018 verifyModifiedMatchesCreated(dataset); 4019 } 4020 4021 // update the object properties (last-modified should be updated) 4022 final HttpPatch patchObject = new HttpPatch(objURI); 4023 patchObject.addHeader(CONTENT_TYPE, "application/sparql-update"); 4024 patchObject.setEntity(new StringEntity("INSERT { <> " + 4025 "<http://www.w3.org/TR/rdf-schema/label> \"this is a label\" } WHERE {}")); 4026 final long lastmod2; 4027 try (final CloseableHttpResponse response = execute(patchObject)) { 4028 assertEquals(NO_CONTENT.getStatusCode(), getStatus(response)); 4029 lastmod2 = Instant.from( 4030 headerFormat.parse(response.getFirstHeader("Last-Modified").getValue())).toEpochMilli(); 4031 assertTrue(lastmod2 > lastmod1); 4032 } 4033 4034 sleep(1000); // wait a second to make sure last-modified value will be different 4035 4036 // create a direct container (last-modified should be updated) 4037 final long lastmod3; 4038 final HttpPut createContainer = new HttpPut(objURI + "/members"); 4039 createContainer.addHeader(CONTENT_TYPE, "text/turtle"); 4040 createContainer.addHeader(LINK, DIRECT_CONTAINER_LINK_HEADER); 4041 final String membersRDF = "<> <http://www.w3.org/ns/ldp#hasMemberRelation> <http://pcdm.org/models#hasMember>; " 4042 + "<http://www.w3.org/ns/ldp#membershipResource> <" + objURI + "> . "; 4043 createContainer.setEntity(new StringEntity(membersRDF)); 4044 try (final CloseableHttpResponse response = execute(createContainer)) { 4045 assertEquals(CREATED.getStatusCode(), getStatus(response)); 4046 lastmod3 = Instant.from( 4047 headerFormat.parse(response.getFirstHeader("Last-Modified").getValue())).toEpochMilli(); 4048 assertTrue(lastmod3 > lastmod2); 4049 } 4050 } 4051 4052 private static void verifyModifiedMatchesCreated(final Dataset dataset) { 4053 final DatasetGraph graph = dataset.asDatasetGraph(); 4054 final Node cre = graph.find(ANY, ANY, CREATED_DATE.asNode(), ANY).next().getObject(); 4055 final Node mod = graph.find(ANY, ANY, LAST_MODIFIED_DATE.asNode(), ANY).next().getObject(); 4056 assertEquals(cre.getLiteralValue(), mod.getLiteralValue()); 4057 } 4058 4059 @Test 4060 public void testDigestConsistency() throws IOException { 4061 final String id = getRandomUniqueId(); 4062 executeAndClose(putDSMethod(id, "binary1", "some test content")); 4063 4064 final String headDigestValue; 4065 final HttpHead headObjMethod = headObjMethod(id + "/binary1"); 4066 headObjMethod.addHeader(WANT_DIGEST, "sha, md5"); 4067 try (final CloseableHttpResponse response = execute(headObjMethod)) { 4068 assertTrue(response.getHeaders(DIGEST).length > 0); 4069 headDigestValue = response.getHeaders(DIGEST)[0].getValue(); 4070 } 4071 4072 final HttpGet getObjMethod = getObjMethod(id + "/binary1"); 4073 getObjMethod.addHeader(WANT_DIGEST, "sha, md5"); 4074 try (final CloseableHttpResponse response = execute(getObjMethod)) { 4075 assertTrue(response.getHeaders(DIGEST).length > 0); 4076 assertEquals(headDigestValue, response.getHeaders(DIGEST)[0].getValue()); 4077 } 4078 } 4079 4080 @Test 4081 public void testDigestAbsence() throws IOException { 4082 final String id = getRandomUniqueId(); 4083 executeAndClose(putDSMethod(id, "binary1", "some test content")); 4084 4085 final HttpHead headObjMethod = headObjMethod(id + "/binary1"); 4086 try (final CloseableHttpResponse response = execute(headObjMethod)) { 4087 assertTrue(response.getHeaders(DIGEST).length == 0); 4088 } 4089 4090 final HttpGet getObjMethod = getObjMethod(id + "/binary1"); 4091 try (final CloseableHttpResponse response = execute(getObjMethod)) { 4092 assertTrue(response.getHeaders(DIGEST).length == 0); 4093 } 4094 } 4095 4096 @Test 4097 public void testPostFcrSlug() throws IOException { 4098 final HttpPost httpPost = postObjMethod(); 4099 httpPost.addHeader("Slug", "fcr:path"); 4100 try (final CloseableHttpResponse response = execute(httpPost)) { 4101 assertEquals("Must not be able to POST with fcr namespaced Slug!", CONFLICT.getStatusCode(), 4102 getStatus(response)); 4103 } 4104 } 4105 4106 @Test 4107 public void testPutFcrPath() throws IOException { 4108 final HttpPut httpPut = putObjMethod("/fcr:path"); 4109 try (final CloseableHttpResponse response = execute(httpPut)) { 4110 assertEquals("Must not be able to PUT with fcr namespaced path!", CONFLICT.getStatusCode(), 4111 getStatus(response)); 4112 } 4113 } 4114 4115 @Test 4116 public void testPutFcrTx() throws IOException { 4117 final HttpPut httpPut = putObjMethod("hello/fcr:tx"); 4118 try (final CloseableHttpResponse response = execute(httpPut)) { 4119 assertEquals("Must not be able to PUT with fcr namespaced path!", CONFLICT.getStatusCode(), 4120 getStatus(response)); 4121 } 4122 } 4123 4124 @Test 4125 public void testDeleteWithFcrPath() throws IOException { 4126 final String id = getRandomUniqueId() + "/fcr:delete"; 4127 final HttpDelete httpDelete = deleteObjMethod(id); 4128 httpDelete.addHeader("Depth", "infinity"); 4129 try (final CloseableHttpResponse response = execute(httpDelete)) { 4130 assertEquals("Must not be able to DELETE with fcr namespaced path!", CONFLICT.getStatusCode(), 4131 getStatus(response)); 4132 } 4133 } 4134 4135 @Test 4136 public void testPostCreateNonRDFSourceWithAcl() throws IOException { 4137 final String aclURI = createAcl(); 4138 final String subjectURI = getRandomUniqueId(); 4139 final HttpPost createMethod = new HttpPost(serverAddress); 4140 createMethod.addHeader(CONTENT_TYPE, "text/plain"); 4141 createMethod.addHeader("Link", "<" + aclURI + ">; rel=\"acl\""); 4142 createMethod.addHeader("Slug", subjectURI); 4143 createMethod.setEntity(new StringEntity("test body")); 4144 4145 checkResponseForMethodWithAcl(createMethod); 4146 } 4147 4148 @Test 4149 public void testPostCreateRDFSourceWithAcl() throws IOException { 4150 final String aclURI = createAcl(); 4151 final String subjectURI = getRandomUniqueId(); 4152 final HttpPost createMethod = new HttpPost(serverAddress); 4153 createMethod.addHeader(CONTENT_TYPE, "text/n3"); 4154 createMethod.addHeader("Link", "<" + aclURI + ">; rel=\"acl\""); 4155 createMethod.addHeader("Slug", subjectURI); 4156 createMethod.setEntity(new StringEntity("<> <info:test#label> \"foo\"")); 4157 4158 checkResponseForMethodWithAcl(createMethod); 4159} 4160 4161 @Test 4162 public void testPutCreateNonRDFSourceWithAcl() throws IOException { 4163 final String aclURI = createAcl(); 4164 final String subjectURI = serverAddress + getRandomUniqueId(); 4165 final HttpPut putMethod = new HttpPut(subjectURI); 4166 putMethod.addHeader(CONTENT_TYPE, "text/plain"); 4167 putMethod.addHeader("Link", "<" + aclURI + ">; rel=\"acl\""); 4168 putMethod.setEntity(new StringEntity("test body")); 4169 4170 checkResponseForMethodWithAcl(putMethod); 4171 } 4172 4173 @Test 4174 public void testPutCreateRDFSourceWithAcl() throws IOException { 4175 final String aclURI = createAcl(); 4176 final String subjectURI = serverAddress + getRandomUniqueId(); 4177 final HttpPut putMethod = new HttpPut(subjectURI); 4178 putMethod.addHeader(CONTENT_TYPE, "text/n3"); 4179 putMethod.addHeader("Link", "<" + aclURI + ">; rel=\"acl\""); 4180 putMethod.setEntity(new StringEntity("<" + subjectURI + "> <info:test#label> \"foo\"")); 4181 4182 checkResponseForMethodWithAcl(putMethod); 4183 } 4184 4185 private void checkResponseForMethodWithAcl(final HttpUriRequest req) throws IOException { 4186 try (final CloseableHttpResponse response = execute(req)) { 4187 assertEquals(BAD_REQUEST.getStatusCode(), getStatus(response)); 4188 assertConstrainedByPresent(response); 4189 } 4190 } 4191 4192 @Test 4193 public void testPostCreateRDFSourceWithMementoNamespaceType() throws IOException { 4194 final String subjectURI = getRandomUniqueId(); 4195 final HttpPost createMethod = new HttpPost(serverAddress); 4196 createMethod.addHeader(CONTENT_TYPE, "text/turtle"); 4197 createMethod.addHeader("Slug", subjectURI); 4198 createMethod.setEntity(new StringEntity("<> a <" + MEMENTO_TYPE + "> .")); 4199 try (final CloseableHttpResponse response = execute(createMethod)) { 4200 assertEquals("Must not be able to POST RDF that contains an \"<> a <" + MEMENTO_TYPE + ">\"", 4201 CONFLICT.getStatusCode(), 4202 getStatus(response)); 4203 assertConstrainedByPresent(response); 4204 checkForLinkHeader(response, SERVER_MANAGED_TYPE_CONSTRAINT_URI, CONSTRAINED_BY.toString()); 4205 } 4206 } 4207 4208 @Test 4209 public void testPutCreateRDFSourceWithMementoNamespaceType() throws IOException { 4210 final String subjectURI = serverAddress + getRandomUniqueId(); 4211 final HttpPut putMethod = new HttpPut(subjectURI); 4212 putMethod.addHeader(CONTENT_TYPE, "text/turtle"); 4213 putMethod.setEntity(new StringEntity("<> a <" + MEMENTO_TYPE + "> .")); 4214 try (final CloseableHttpResponse response = execute(putMethod)) { 4215 assertEquals("Must not be able to PUT RDF that contains an \"<> a <" + MEMENTO_TYPE + ">\"", 4216 CONFLICT.getStatusCode(), 4217 getStatus(response)); 4218 checkForLinkHeader(response, SERVER_MANAGED_TYPE_CONSTRAINT_URI, CONSTRAINED_BY.toString()); 4219 } 4220 } 4221 4222 @Test 4223 public void testPatchInsertMementoNamespaceType() throws IOException { 4224 final String pid = getRandomUniqueId(); 4225 final String subjectURI = serverAddress + pid; 4226 createObjectAndClose(pid); 4227 final HttpPatch patch = new HttpPatch(subjectURI); 4228 patch.addHeader(CONTENT_TYPE, "application/sparql-update"); 4229 patch.setEntity(new StringEntity("INSERT { <> a <" + MEMENTO_TYPE + "> . } WHERE {} ")); 4230 4231 try (final CloseableHttpResponse response = execute(patch)) { 4232 assertEquals("Must not be able to INSERT \"<> a <" + MEMENTO_TYPE + ">\"", 4233 CONFLICT.getStatusCode(), getStatus(patch)); 4234 checkForLinkHeader(response, SERVER_MANAGED_TYPE_CONSTRAINT_URI, CONSTRAINED_BY.toString()); 4235 } 4236 } 4237 4238 @Test 4239 public void testPostCreateRDFSourceWithMementoNamespacePredicate() throws IOException { 4240 final String subjectURI = getRandomUniqueId(); 4241 final HttpPost createMethod = new HttpPost(serverAddress); 4242 createMethod.addHeader(CONTENT_TYPE, "text/turtle"); 4243 createMethod.addHeader("Slug", subjectURI); 4244 createMethod.setEntity( 4245 new StringEntity("<> <" + MEMENTO_NAMESPACE + "mementoDatetime" + "> \"Thu, 21 Jan 2010 00:09:40 GMT\"")); 4246 try (final CloseableHttpResponse response = execute(createMethod)) { 4247 assertEquals("Must not be able to POST RDF that contains a memento namespace predicate", 4248 CONFLICT.getStatusCode(), 4249 getStatus(response)); 4250 checkForLinkHeader(response, SERVER_MANAGED_PROPERTY_CONSTRAINT_URI, CONSTRAINED_BY.toString()); 4251 } 4252 } 4253 4254 @Test 4255 public void testPutCreateRDFSourceWithMementoNamespacePredicate() throws IOException { 4256 final String subjectURI = serverAddress + getRandomUniqueId(); 4257 final HttpPut putMethod = new HttpPut(subjectURI); 4258 putMethod.addHeader(CONTENT_TYPE, "text/turtle"); 4259 putMethod.setEntity( 4260 new StringEntity("<> <" + MEMENTO_NAMESPACE + "mementoDatetime" + "> \"Thu, 21 Jan 2010 00:09:40 GMT\"")); 4261 try (final CloseableHttpResponse response = execute(putMethod)) { 4262 assertEquals("Must not be able to PUT RDF that contains a memento namespace predicate", 4263 CONFLICT.getStatusCode(), 4264 getStatus(response)); 4265 checkForLinkHeader(response, SERVER_MANAGED_PROPERTY_CONSTRAINT_URI, CONSTRAINED_BY.toString()); 4266 } 4267 } 4268 4269 @Test 4270 public void testPatchInsertMementoNamespacePredicate() throws IOException { 4271 final String pid = getRandomUniqueId(); 4272 final String subjectURI = serverAddress + pid; 4273 createObjectAndClose(pid); 4274 final HttpPatch patch = new HttpPatch(subjectURI); 4275 patch.addHeader(CONTENT_TYPE, "application/sparql-update"); 4276 patch.setEntity(new StringEntity( 4277 "INSERT { <> <" + MEMENTO_NAMESPACE + "mementoDatetime" + 4278 "> \"Thu, 21 Jan 2010 00:09:40 GMT\". } WHERE {}")); 4279 try (final CloseableHttpResponse response = execute(patch)) { 4280 assertEquals("Must not be able to PATCH RDF that contains a memento namespace predicate", 4281 CONFLICT.getStatusCode(), getStatus(response)); 4282 checkForLinkHeader(response, SERVER_MANAGED_PROPERTY_CONSTRAINT_URI, CONSTRAINED_BY.toString()); 4283 } 4284 } 4285 4286 @Test 4287 public void testPostCreateRDFSourceWithMementoTypeAndProperty() throws IOException { 4288 final String subjectURI = getRandomUniqueId(); 4289 final HttpPost createMethod = new HttpPost(serverAddress); 4290 createMethod.addHeader(CONTENT_TYPE, "text/turtle"); 4291 createMethod.addHeader("Slug", subjectURI); 4292 createMethod.setEntity( 4293 new StringEntity("<> a <" + MEMENTO_TYPE + "> ; " + 4294 "<" + MEMENTO_NAMESPACE + "mementoDatetime" + "> \"Thu, 21 Jan 2010 00:09:40 GMT\"")); 4295 try (final CloseableHttpResponse response = execute(createMethod)) { 4296 assertEquals("Must not be able to POST RDF that contains a memento type and namespace predicate", 4297 CONFLICT.getStatusCode(), 4298 getStatus(response)); 4299 checkForLinkHeader(response, SERVER_MANAGED_PROPERTY_CONSTRAINT_URI, CONSTRAINED_BY.toString()); 4300 checkForLinkHeader(response, SERVER_MANAGED_TYPE_CONSTRAINT_URI, CONSTRAINED_BY.toString()); 4301 } 4302 } 4303 4304 @Test 4305 public void testPutCreateRDFSourceWithMementoTypeAndProperty() throws IOException { 4306 final String subjectURI = serverAddress + getRandomUniqueId(); 4307 final HttpPut putMethod = new HttpPut(subjectURI); 4308 putMethod.addHeader(CONTENT_TYPE, "text/turtle"); 4309 putMethod.setEntity( 4310 new StringEntity("<> a <" + MEMENTO_TYPE + "> ; " + 4311 "<" + MEMENTO_NAMESPACE + "mementoDatetime" + "> \"Thu, 21 Jan 2010 00:09:40 GMT\"")); 4312 try (final CloseableHttpResponse response = execute(putMethod)) { 4313 assertEquals("Must not be able to PUT RDF that contains a memento type and namespace predicate", 4314 CONFLICT.getStatusCode(), 4315 getStatus(response)); 4316 checkForLinkHeader(response, SERVER_MANAGED_PROPERTY_CONSTRAINT_URI, CONSTRAINED_BY.toString()); 4317 checkForLinkHeader(response, SERVER_MANAGED_TYPE_CONSTRAINT_URI, CONSTRAINED_BY.toString()); 4318 } 4319 } 4320 4321 @Test 4322 public void testPatchInsertMementoTypeAndProperty() throws IOException { 4323 final String pid = getRandomUniqueId(); 4324 final String subjectURI = serverAddress + pid; 4325 createObjectAndClose(pid); 4326 final HttpPatch patch = new HttpPatch(subjectURI); 4327 patch.addHeader(CONTENT_TYPE, "application/sparql-update"); 4328 patch.setEntity(new StringEntity( 4329 "INSERT { <> a <" + MEMENTO_TYPE + "> ; <" + MEMENTO_NAMESPACE + "mementoDatetime" + 4330 "> \"Thu, 21 Jan 2010 00:09:40 GMT\". } WHERE {}")); 4331 try (final CloseableHttpResponse response = execute(patch)) { 4332 assertEquals("Must not be able to PATCH RDF that contains a memento type and namespace predicate", 4333 CONFLICT.getStatusCode(), getStatus(response)); 4334 checkForLinkHeader(response, SERVER_MANAGED_PROPERTY_CONSTRAINT_URI, CONSTRAINED_BY.toString()); 4335 checkForLinkHeader(response, SERVER_MANAGED_TYPE_CONSTRAINT_URI, CONSTRAINED_BY.toString()); 4336 } 4337 } 4338 4339 @Test 4340 public void testPatchUpdateIndirectContainerServerManaged() throws IOException { 4341 final String pid = getRandomUniqueId(); 4342 final String subjectURI = serverAddress + pid; 4343 createObjectAndClose(pid); 4344 final String indirectContainerPid = getRandomUniqueId(); 4345 final String indirectContainerURI = serverAddress + indirectContainerPid; 4346 createObjectAndClose(indirectContainerPid, INDIRECT_CONTAINER_LINK_HEADER); 4347 final HttpPatch patch = new HttpPatch(indirectContainerURI); 4348 patch.addHeader(CONTENT_TYPE, "application/sparql-update"); 4349 patch.setEntity(new StringEntity( 4350 "PREFIX ldp: <http://www.w3.org/ns/ldp#>\n" + 4351 "PREFIX fedora: <http://fedora.info/definitions/v4/repository#>\n" + 4352 "PREFIX ore: <http://www.openarchives.org/ore/terms/>\n" + 4353 "INSERT { <> ldp:membershipResource <" + subjectURI + "> ;\n" + 4354 "ldp:hasMemberRelation fedora:createdBy ;\n" + 4355 "ldp:insertedContentRelation ore:proxyFor. } WHERE {}")); 4356 try (final CloseableHttpResponse response = execute(patch)) { 4357 assertEquals("Must not be able to PATCH IndirectContainer updating Server Managed triples", 4358 CONFLICT.getStatusCode(), getStatus(response)); 4359 } 4360 } 4361 4362 @Test 4363 public void testPutUpdateBinaryWithType() throws Exception { 4364 final String objid = getRandomUniqueId(); 4365 final String objURI = serverAddress + objid; 4366 final String binURI = objURI + "/binary1"; 4367 final String descURI = objURI + "/binary1/fcr:metadata"; 4368 4369 try (final CloseableHttpResponse response = execute(putDSMethod(objid, "binary1", "some test content"))) { 4370 assertEquals(CREATED.getStatusCode(), getStatus(response)); 4371 } 4372 4373 // Add type using PUT 4374 final Model model1 = getModel(objid + "/binary1/fcr:metadata", true); 4375 final Resource resc = model1.getResource(binURI); 4376 resc.addProperty(RDF.type, PCDM_FILE_TYPE); 4377 4378 final HttpPut addMethod = new HttpPut(descURI); 4379 addMethod.addHeader(CONTENT_TYPE, "text/turtle"); 4380 addMethod.setEntity(new InputStreamEntity(streamModel(model1, RDFFormat.TURTLE))); 4381 try (final CloseableHttpResponse response = execute(addMethod)) { 4382 assertEquals(NO_CONTENT.getStatusCode(), getStatus(response)); 4383 } 4384 4385 final Model model2 = getModel(objid + "/binary1/fcr:metadata", true); 4386 final Resource resc2 = model2.getResource(binURI); 4387 assertTrue(resc2.hasProperty(RDF.type, PCDM_FILE_TYPE)); 4388 4389 // Remove type using PUT 4390 model2.remove(resc2, RDF.type, PCDM_FILE_TYPE); 4391 4392 final HttpPut removeMethod = new HttpPut(descURI); 4393 removeMethod.addHeader(CONTENT_TYPE, "text/turtle"); 4394 removeMethod.setEntity(new InputStreamEntity(streamModel(model2, RDFFormat.TURTLE))); 4395 try (final CloseableHttpResponse response = execute(removeMethod)) { 4396 assertEquals(NO_CONTENT.getStatusCode(), getStatus(response)); 4397 } 4398 4399 final Model model3 = getModel(objid + "/binary1/fcr:metadata"); 4400 final Resource resc3 = model3.getResource(binURI); 4401 assertFalse(resc3.hasProperty(RDF.type, PCDM_FILE_TYPE)); 4402 } 4403 4404 @Test 4405 public void testPatchUpdateBinaryWithType() throws Exception { 4406 final String objid = getRandomUniqueId(); 4407 final String objURI = serverAddress + objid; 4408 final String binURI = objURI + "/binary1"; 4409 final String descURI = objURI + "/binary1/fcr:metadata"; 4410 4411 try (final CloseableHttpResponse response = execute(putDSMethod(objid, "binary1", "some test content"))) { 4412 assertEquals(CREATED.getStatusCode(), getStatus(response)); 4413 } 4414 4415 // Test adding an rdf:type 4416 final HttpPatch patchBinary = new HttpPatch(descURI); 4417 patchBinary.addHeader(CONTENT_TYPE, "application/sparql-update"); 4418 patchBinary.setEntity(new StringEntity("INSERT { <" + binURI + "> " + 4419 "<" + RDF.type.getURI() + "> <" + PCDM_FILE_TYPE.getURI() + "> } WHERE {}")); 4420 4421 try (final CloseableHttpResponse response = execute(patchBinary)) { 4422 assertEquals(NO_CONTENT.getStatusCode(), getStatus(response)); 4423 } 4424 4425 final Model model = getModel(objid + "/binary1/fcr:metadata"); 4426 final Resource resc = model.getResource(binURI); 4427 assertTrue(resc.hasProperty(RDF.type, PCDM_FILE_TYPE)); 4428 4429 // Test removing an rdf:type 4430 final HttpPatch patchBinary2 = new HttpPatch(descURI); 4431 patchBinary2.addHeader(CONTENT_TYPE, "application/sparql-update"); 4432 patchBinary2.setEntity(new StringEntity("DELETE { <" + binURI + "> " + 4433 "<" + RDF.type.getURI() + "> <" + PCDM_FILE_TYPE.getURI() + "> } WHERE {}")); 4434 4435 try (final CloseableHttpResponse response = execute(patchBinary2)) { 4436 assertEquals(NO_CONTENT.getStatusCode(), getStatus(response)); 4437 } 4438 4439 final Model model2 = getModel(objid + "/binary1/fcr:metadata"); 4440 final Resource resc2 = model2.getResource(binURI); 4441 assertFalse(resc2.hasProperty(RDF.type, PCDM_FILE_TYPE)); 4442 } 4443 4444 /** 4445 * Utility to assert a GET of id and id/fcr:versions 4446 * 4447 * @param id the path 4448 * @throws Exception issue with http communication. 4449 */ 4450 private void doGetIdAndVersions(final String id) throws Exception { 4451 final HttpGet getMethod = getObjMethod(id); 4452 try (final CloseableHttpResponse response = execute(getMethod)) { 4453 assertEquals(OK.getStatusCode(), getStatus(response)); 4454 } 4455 4456 final HttpGet getVersion = getObjMethod(id + "/" + FCR_VERSIONS); 4457 try (final CloseableHttpResponse response = execute(getVersion)) { 4458 assertEquals(OK.getStatusCode(), getStatus(response)); 4459 } 4460 } 4461 4462 @Test 4463 public void testPutGetRdfSourceWithUndefinedPrefix() throws Exception { 4464 final String id = "some_prefix:" + getRandomUniqueId(); 4465 final HttpPut putMethod = putObjMethod(id); 4466 try (final CloseableHttpResponse response = execute(putMethod)) { 4467 assertEquals(CREATED.getStatusCode(), getStatus(response)); 4468 } 4469 4470 doGetIdAndVersions(id); 4471 } 4472 4473 @Test 4474 public void testPutGetNonRdfSourceWithUndefinedPrefix() throws Exception { 4475 final String id = "some_prefix:" + getRandomUniqueId(); 4476 final String dsid = "anotherPrefix:" + getRandomUniqueId(); 4477 4478 try (final CloseableHttpResponse response = execute(putDSMethod(id, dsid, "some test content"))) { 4479 assertEquals(CREATED.getStatusCode(), getStatus(response)); 4480 } 4481 4482 // Check id/dsid 4483 doGetIdAndVersions(id + "/" + dsid); 4484 4485 // Check id/dsid/fcr:metadata 4486 doGetIdAndVersions(id + "/" + dsid + "/" + FCR_METADATA); 4487 4488 } 4489 4490 @Test 4491 public void testContentTypeWithCharset() throws Exception { 4492 final String uuid = getRandomUniqueId(); 4493 final HttpPost postMethod = postObjMethod(); 4494 postMethod.setHeader("Slug", uuid); 4495 postMethod.setHeader(CONTENT_TYPE, "text/turtle;charset=utf-8"); 4496 final HttpEntity body = new StringEntity("@prefix acl: <http://www.w3.org/ns/auth/acl#>.\n" + 4497 "@prefix vcard: <http://www.w3.org/2006/vcard/ns#>.\n" + 4498 "<> a vcard:Group;\n" + 4499 " vcard:hasMember \"someUser\"."); 4500 postMethod.setEntity(body); 4501 assertEquals(CREATED.getStatusCode(), getStatus(postMethod)); 4502 4503 final HttpGet getMethod = getObjMethod(uuid); 4504 try (final CloseableHttpResponse response = execute(getMethod)) { 4505 final Dataset dataset = getDataset(response); 4506 final DatasetGraph graph = dataset.asDatasetGraph(); 4507 checkForLinkHeader(response, BASIC_CONTAINER.toString(), "type"); 4508 assertTrue(graph.contains( 4509 ANY, 4510 ANY, 4511 NodeFactory.createURI("http://www.w3.org/2006/vcard/ns#hasMember"), 4512 NodeFactory.createLiteral("someUser"))); 4513 } 4514 } 4515 4516 @Test 4517 public void testGhostNodes() throws IOException { 4518 final String shallowId = getRandomUniqueId(); 4519 final String ghostSlug = getRandomUniqueId(); 4520 final String ghostId = shallowId + "/" + ghostSlug; 4521 final String deepId = ghostId + "/" + getRandomUniqueId(); 4522 // Create 'a' 4523 assertEquals(CREATED.getStatusCode(), getStatus(putObjMethod(shallowId))); 4524 assertEquals(OK.getStatusCode(), getStatus(getObjMethod(shallowId))); 4525 4526 // Create 'a/b/c' ('b' is a ghost node) 4527 assertEquals(CREATED.getStatusCode(), getStatus(putObjMethod(deepId))); 4528 // Ensure the resource exists. 4529 assertEquals(OK.getStatusCode(), getStatus(getObjMethod(deepId))); 4530 4531 // Ensure the ghost node does not exist. 4532 assertEquals(NOT_FOUND.getStatusCode(), getStatus(getObjMethod(ghostId))); 4533 // Ensure you can't create at the ghost node location. 4534 assertEquals(CONFLICT.getStatusCode(), getStatus(putObjMethod(ghostId))); 4535 // Ensure you can't POST with a slug to get the ghost node location. 4536 final HttpPost ghostPost = postObjMethod(shallowId); 4537 ghostPost.setHeader("Slug", ghostSlug); 4538 try (final CloseableHttpResponse response = execute(ghostPost)) { 4539 assertEquals(CREATED.getStatusCode(), getStatus(response)); 4540 assertFalse(getLocation(response).contains(ghostId)); 4541 } 4542 // Ensure you can't create a child of the ghost node. 4543 assertEquals(BAD_REQUEST.getStatusCode(), getStatus(postObjMethod(ghostId))); 4544 4545 // Delete 'a/b/c' 4546 assertEquals(NO_CONTENT.getStatusCode(), getStatus(deleteObjMethod(deepId))); 4547 4548 // Assert 'a/b/c' is gone. 4549 assertEquals(GONE.getStatusCode(), getStatus(getObjMethod(deepId))); 4550 4551 // Still can't create because there is a tombstone 4552 assertEquals(CONFLICT.getStatusCode(), getStatus(putObjMethod(ghostId))); 4553 4554 // Delete the tombstone 4555 assertEquals(NO_CONTENT.getStatusCode(), getStatus(deleteObjMethod(deepId + "/" + FCR_TOMBSTONE))); 4556 4557 // Now you can create a/b 4558 assertEquals(CREATED.getStatusCode(), getStatus(putObjMethod(ghostId))); 4559 4560 // 'a' exists 4561 assertEquals(OK.getStatusCode(), getStatus(getObjMethod(shallowId))); 4562 // 'a/b' exists 4563 assertEquals(OK.getStatusCode(), getStatus(getObjMethod(ghostId))); 4564 // 'a/b/c' does not exist 4565 assertEquals(NOT_FOUND.getStatusCode(), getStatus(getObjMethod(deepId))); 4566 } 4567 4568 @Test 4569 public void testRepositoryRootTypes() throws Exception { 4570 final HttpGet getRoot = new HttpGet(serverAddress); 4571 final Node rootNode = createURI(serverAddress); 4572 try (final CloseableHttpResponse response = execute(getRoot)) { 4573 final Dataset dataset = getDataset(response); 4574 final DatasetGraph graph = dataset.asDatasetGraph(); 4575 assertTrue(graph.contains(ANY, rootNode, type.asNode(), REPOSITORY_ROOT.asNode())); 4576 assertTrue(graph.contains(ANY, rootNode, type.asNode(), RESOURCE.asNode())); 4577 assertTrue(graph.contains(ANY, rootNode, type.asNode(), FEDORA_RESOURCE.asNode())); 4578 assertTrue(graph.contains(ANY, rootNode, type.asNode(), RDF_SOURCE.asNode())); 4579 assertTrue(graph.contains(ANY, rootNode, type.asNode(), CONTAINER.asNode())); 4580 assertTrue(graph.contains(ANY, rootNode, type.asNode(), FEDORA_CONTAINER.asNode())); 4581 assertTrue(graph.contains(ANY, rootNode, type.asNode(), BASIC_CONTAINER.asNode())); 4582 } 4583 } 4584 4585 private static String genLongUrl(final String prefix, final int numChars) { 4586 String ret = prefix; 4587 final Random rand = new Random(); 4588 int cnt = prefix.length(); 4589 4590 while (cnt <= numChars) { 4591 ret = ret + "/" + (char)(rand.nextInt(26) + 'a'); 4592 cnt = ret.length(); 4593 } 4594 return ret; 4595 } 4596 4597 @Test 4598 public void testLongIdentifier1() throws IOException { 4599 final String url = genLongUrl(serverAddress,476); 4600 final HttpPut putMethod = new HttpPut(url); 4601 assertEquals(CREATED.getStatusCode(), getStatus(putMethod)); 4602 final HttpPost postMethod = new HttpPost(url); 4603 postMethod.setHeader("Slug", getRandomUniqueId()); 4604 try (final CloseableHttpResponse response = execute(postMethod)) { 4605 assertEquals(CREATED.getStatusCode(), getStatus(response)); 4606 } 4607 } 4608 4609 @Test 4610 public void testLongIdentifier2() throws IOException { 4611 final String url = genLongUrl(serverAddress,477); 4612 final HttpPut putMethod = new HttpPut(url); 4613 assertEquals(CREATED.getStatusCode(), getStatus(putMethod)); 4614 final HttpPost postMethod = new HttpPost(url); 4615 postMethod.setHeader("Slug", getRandomUniqueId()); 4616 try (final CloseableHttpResponse response = execute(postMethod)) { 4617 assertEquals(BAD_REQUEST.getStatusCode(), getStatus(response)); 4618 } 4619 } 4620 4621 @Test 4622 public void testIdConstraints() throws IOException { 4623 assertIdStringConstraint(".fcrepo"); 4624 assertIdStringConstraint("fcr-root"); 4625 assertIdStringConstraint("fcr-container.nt"); 4626 4627 assertIdSuffixConstraint("~fcr-desc"); 4628 assertIdSuffixConstraint("~fcr-acl"); 4629 assertIdSuffixConstraint("~fcr-desc.nt"); 4630 assertIdSuffixConstraint("~fcr-acl.nt"); 4631 } 4632 4633 @Test 4634 public void testMapRelativeUris() throws Exception { 4635 final HttpPost post = postObjMethod(); 4636 final String body = "<> <http://example.org/related> </fcrepo/rest/test1> ; <http://example.org/related> " + 4637 "</rest/test2> ; <http://example.org/related> <http://something.else/outThere> ; " + 4638 "<http://example.org/related> <test3> ."; 4639 post.setHeader(CONTENT_TYPE, "text/turtle"); 4640 post.setEntity(new StringEntity(body, UTF_8)); 4641 final String location; 4642 try (final var response = execute(post)) { 4643 assertEquals(CREATED.getStatusCode(), getStatus(response)); 4644 location = getLocation(response); 4645 } 4646 final HttpGet get = new HttpGet(location); 4647 try (final var dataset = getDataset(get)) { 4648 final var graph = dataset.asDatasetGraph(); 4649 final var nodeIter = graph.find(ANY, ANY, createURI("http://example.org/related"), ANY); 4650 int nodeCounter = 0; 4651 final List<String> expectedUris = List.of(serverAddress + "fcrepo/rest/test1", 4652 serverAddress + "rest/test2", "http://something.else/outThere", serverAddress + "test3"); 4653 while (nodeIter.hasNext()) { 4654 final var quad = nodeIter.next(); 4655 nodeCounter += 1; 4656 assertTrue(expectedUris.contains(quad.getObject().getURI())); 4657 } 4658 assertEquals(4, nodeCounter); 4659 } 4660 } 4661 4662 @Test 4663 public void testMapRelativeUrisDeeper() throws Exception { 4664 final String location1; 4665 final HttpPost post = postObjMethod(); 4666 try (final var response = execute(post)) { 4667 assertEquals(CREATED.getStatusCode(), getStatus(response)); 4668 location1 = getLocation(response); 4669 } 4670 final String location2; 4671 final HttpPost post2 = new HttpPost(location1); 4672 try (final var response = execute(post2)) { 4673 assertEquals(CREATED.getStatusCode(), getStatus(response)); 4674 location2 = getLocation(response); 4675 } 4676 final HttpPost post3 = new HttpPost(location2); 4677 final String body = "<> <http://example.org/related> </fcrepo/rest/test1> ; <http://example.org/related> " + 4678 "</rest/test2> ; <http://example.org/related> <http://something.else/outThere> ; " + 4679 "<http://example.org/related> <test3> ."; 4680 post3.setHeader(CONTENT_TYPE, "text/turtle"); 4681 post3.setEntity(new StringEntity(body, UTF_8)); 4682 final String location3; 4683 try (final var response = execute(post3)) { 4684 assertEquals(CREATED.getStatusCode(), getStatus(response)); 4685 location3 = getLocation(response); 4686 } 4687 final HttpGet get = new HttpGet(location3); 4688 try (final var dataset = getDataset(get)) { 4689 final var graph = dataset.asDatasetGraph(); 4690 final var nodeIter = graph.find(ANY, ANY, createURI("http://example.org/related"), ANY); 4691 int nodeCounter = 0; 4692 final List<String> expectedUris = List.of(serverAddress + "fcrepo/rest/test1", 4693 serverAddress + "rest/test2", "http://something.else/outThere", location2 + "/test3"); 4694 while (nodeIter.hasNext()) { 4695 final var quad = nodeIter.next(); 4696 nodeCounter += 1; 4697 assertTrue(expectedUris.contains(quad.getObject().getURI())); 4698 } 4699 assertEquals(4, nodeCounter); 4700 } 4701 } 4702 4703 @Test 4704 public void testNonExistantFcrMetadata() throws Exception { 4705 final var id = getRandomUniqueId(); 4706 // Ensure the object does not exist 4707 assertEquals(NOT_FOUND.getStatusCode(), getStatus(getObjMethod(id))); 4708 // Ensure the metadata of a non-existant object also doesn't exist. 4709 assertEquals(NOT_FOUND.getStatusCode(), getStatus(getObjMethod(id + "/" + FCR_METADATA))); 4710 // Ensure you can't PUT at non-existant binary. 4711 assertEquals(NOT_FOUND.getStatusCode(), getStatus(putObjMethod(id + "/" + FCR_METADATA))); 4712 } 4713 4714 @Test 4715 public void createObjectWithUnicodeInSlug() throws IOException { 4716 final var id = "☞☕☜☑"; 4717 final var encodedId = URLEncoder.encode(id, UTF_8); 4718 4719 final String location; 4720 4721 try (final var response = createObject(id)) { 4722 assertEquals(SC_CREATED, response.getStatusLine().getStatusCode()); 4723 location = getLocation(response); 4724 assertTrue(String.format("Expected location to end with '/%s'. Found: %s", encodedId, location), 4725 location.endsWith("/" + encodedId)); 4726 } 4727 4728 try (final var response = execute(new HttpGet(location))) { 4729 assertEquals(SC_OK, response.getStatusLine().getStatusCode()); 4730 } 4731 } 4732 4733 @Test 4734 public void postToNonexistantResourceFails() throws IOException { 4735 final var id = getRandomUniqueId(); 4736 assertEquals(NOT_FOUND.getStatusCode(), getStatus(getObjMethod(id))); 4737 assertEquals(NOT_FOUND.getStatusCode(), getStatus(postObjMethod(id))); 4738 } 4739 4740 private void assertIdStringConstraint(final String id) throws IOException { 4741 assertInvalidId(id); 4742 assertValidId(id + "-suffix"); 4743 assertValidId("prefix-" + id); 4744 } 4745 4746 private void assertIdSuffixConstraint(final String suffix) throws IOException { 4747 assertInvalidId("prefix" + suffix); 4748 assertValidId(suffix); 4749 assertValidId(suffix + "-suffix"); 4750 } 4751 4752 private void assertInvalidId(final String id) throws IOException { 4753 final HttpPost postMethod = postObjMethod(); 4754 postMethod.setHeader("Slug", id); 4755 try (final CloseableHttpResponse response = execute(postMethod)) { 4756 assertEquals(BAD_REQUEST.getStatusCode(), getStatus(response)); 4757 } 4758 } 4759 4760 private void assertValidId(final String id) throws IOException { 4761 final HttpPost postMethod = postObjMethod(); 4762 postMethod.setHeader("Slug", id); 4763 try (final CloseableHttpResponse response = execute(postMethod)) { 4764 assertEquals(CREATED.getStatusCode(), getStatus(response)); 4765 } 4766 } 4767 4768}