001/* 002 * The contents of this file are subject to the license and copyright 003 * detailed in the LICENSE and NOTICE files at the root of the source 004 * tree. 005 */ 006package org.fcrepo.kernel.impl.services; 007 008import static java.util.Arrays.asList; 009import static java.util.Collections.singleton; 010import static org.fcrepo.kernel.api.RdfLexicon.BASIC_CONTAINER; 011import static org.fcrepo.kernel.api.RdfLexicon.CONTAINER; 012import static org.fcrepo.kernel.api.RdfLexicon.CREATED_BY; 013import static org.fcrepo.kernel.api.RdfLexicon.CREATED_DATE; 014import static org.fcrepo.kernel.api.RdfLexicon.DEFAULT_INTERACTION_MODEL; 015import static org.fcrepo.kernel.api.RdfLexicon.FEDORA_RESOURCE; 016import static org.fcrepo.kernel.api.RdfLexicon.LAST_MODIFIED_BY; 017import static org.fcrepo.kernel.api.RdfLexicon.LAST_MODIFIED_DATE; 018import static org.fcrepo.kernel.api.RdfLexicon.MEMENTO_TYPE; 019import static org.fcrepo.kernel.api.RdfLexicon.NON_RDF_SOURCE; 020import static org.fcrepo.kernel.api.RdfLexicon.RESOURCE; 021import static org.junit.Assert.assertEquals; 022import static org.junit.Assert.assertNotEquals; 023import static org.junit.Assert.assertTrue; 024import static org.mockito.Mockito.times; 025import static org.mockito.Mockito.verify; 026import static org.mockito.Mockito.when; 027import static org.springframework.test.util.ReflectionTestUtils.setField; 028 029import java.io.File; 030import java.net.URI; 031import java.nio.charset.StandardCharsets; 032import java.nio.file.Files; 033import java.time.Instant; 034import java.util.ArrayList; 035import java.util.Arrays; 036import java.util.Calendar; 037import java.util.Collection; 038import java.util.Date; 039import java.util.List; 040import java.util.UUID; 041 042import javax.inject.Inject; 043 044import org.fcrepo.config.FedoraPropsConfig; 045import org.fcrepo.config.ServerManagedPropsMode; 046import org.fcrepo.kernel.api.ContainmentIndex; 047import org.fcrepo.kernel.api.Transaction; 048import org.fcrepo.kernel.api.cache.UserTypesCache; 049import org.fcrepo.kernel.api.exception.InteractionModelViolationException; 050import org.fcrepo.kernel.api.exception.RequestWithAclLinkHeaderException; 051import org.fcrepo.kernel.api.identifiers.FedoraId; 052import org.fcrepo.kernel.api.models.ExternalContent; 053import org.fcrepo.kernel.api.models.FedoraResource; 054import org.fcrepo.kernel.api.models.ResourceHeaders; 055import org.fcrepo.kernel.api.observer.EventAccumulator; 056import org.fcrepo.kernel.api.operations.CreateRdfSourceOperation; 057import org.fcrepo.kernel.api.operations.NonRdfSourceOperation; 058import org.fcrepo.kernel.api.operations.NonRdfSourceOperationFactory; 059import org.fcrepo.kernel.api.operations.RdfSourceOperation; 060import org.fcrepo.kernel.api.operations.RdfSourceOperationFactory; 061import org.fcrepo.kernel.api.operations.ResourceOperation; 062import org.fcrepo.kernel.api.services.MembershipService; 063import org.fcrepo.kernel.api.services.ReferenceService; 064import org.fcrepo.kernel.impl.TestTransactionHelper; 065import org.fcrepo.kernel.impl.operations.CreateNonRdfSourceOperation; 066import org.fcrepo.kernel.impl.operations.NonRdfSourceOperationFactoryImpl; 067import org.fcrepo.kernel.impl.operations.RdfSourceOperationFactoryImpl; 068import org.fcrepo.persistence.api.PersistentStorageSession; 069import org.fcrepo.persistence.api.PersistentStorageSessionManager; 070import org.fcrepo.persistence.api.exceptions.PersistentItemNotFoundException; 071import org.fcrepo.search.api.SearchIndex; 072 073import org.apache.commons.io.FileUtils; 074import org.apache.jena.datatypes.xsd.XSDDateTime; 075import org.apache.jena.rdf.model.Model; 076import org.apache.jena.rdf.model.ModelFactory; 077import org.flywaydb.test.annotation.FlywayTest; 078import org.junit.Before; 079import org.junit.Rule; 080import org.junit.Test; 081import org.junit.rules.TemporaryFolder; 082import org.junit.runner.RunWith; 083import org.mockito.ArgumentCaptor; 084import org.mockito.ArgumentMatchers; 085import org.mockito.Captor; 086import org.mockito.InjectMocks; 087import org.mockito.Mock; 088import org.mockito.MockitoAnnotations; 089import org.springframework.test.context.ContextConfiguration; 090import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 091 092/** 093 * @author bseeger 094 */ 095@RunWith(SpringJUnit4ClassRunner.class) 096@ContextConfiguration("/containmentIndexTest.xml") 097public class CreateResourceServiceImplTest { 098 099 private static final List<String> STRING_TYPES_NOT_VALID = Arrays.asList(MEMENTO_TYPE, RESOURCE.toString(), 100 FEDORA_RESOURCE.toString()); 101 102 private static final List<String> STRING_TYPES_VALID = Arrays.asList(MEMENTO_TYPE, CONTAINER.toString(), 103 BASIC_CONTAINER.toString()); 104 105 private final String defaultInteractionModel = DEFAULT_INTERACTION_MODEL.toString(); 106 107 @Rule 108 public TemporaryFolder tempFolder = new TemporaryFolder(); 109 110 @Mock 111 private PersistentStorageSessionManager psManager; 112 113 private RdfSourceOperationFactory rdfSourceOperationFactory; 114 115 private NonRdfSourceOperationFactory nonRdfSourceOperationFactory; 116 117 @Inject 118 private ContainmentIndex containmentIndex; 119 120 @Mock 121 private SearchIndex searchIndex; 122 123 @Mock 124 private PersistentStorageSession psSession; 125 126 @Mock 127 private ResourceHeaders resourceHeaders; 128 129 @Mock 130 private ExternalContent extContent; 131 132 @Mock 133 private FedoraResource fedoraResource; 134 135 private Transaction transaction; 136 137 @Mock 138 private ReferenceService referenceService; 139 140 @Mock 141 private MembershipService membershipService; 142 143 @Captor 144 private ArgumentCaptor<ResourceOperation> operationCaptor; 145 146 @Mock 147 private EventAccumulator eventAccumulator; 148 149 @Mock 150 private UserTypesCache userTypesCache; 151 152 @InjectMocks 153 private CreateResourceServiceImpl createResourceService; 154 155 private static final String TX_ID = "tx1234"; 156 157 private static final String USER_PRINCIPAL = "fedoraUser"; 158 159 private static final String FILENAME = "file.html"; 160 161 private static final String CONTENT_TYPE = "text/html"; 162 163 private static final Long CONTENT_SIZE = 100L; 164 165 private static final String EXTERNAL_CONTENT_TYPE = "text/plain"; 166 167 private final Model model = ModelFactory.createDefaultModel(); 168 169 private static final Collection<URI> DIGESTS = singleton(URI.create("urn:sha1:12345abced")); 170 171 private static final List<FedoraId> cleanupList = new ArrayList<>() ; 172 173 private final FedoraId rootId = FedoraId.getRepositoryRootId(); 174 175 private FedoraPropsConfig propsConfig = new FedoraPropsConfig(); 176 177 @Before 178 @FlywayTest 179 public void setUp() { 180 MockitoAnnotations.openMocks(this); 181 rdfSourceOperationFactory = new RdfSourceOperationFactoryImpl(); 182 setField(createResourceService, "rdfSourceOperationFactory", rdfSourceOperationFactory); 183 nonRdfSourceOperationFactory = new NonRdfSourceOperationFactoryImpl(); 184 setField(createResourceService, "nonRdfSourceOperationFactory", nonRdfSourceOperationFactory); 185 setField(createResourceService, "containmentIndex", containmentIndex); 186 setField(createResourceService, "searchIndex", searchIndex); 187 setField(createResourceService, "eventAccumulator", eventAccumulator); 188 setField(createResourceService, "referenceService", referenceService); 189 setField(createResourceService, "membershipService", membershipService); 190 setField(createResourceService, "fedoraPropsConfig", propsConfig); 191 setField(createResourceService, "userTypesCache", userTypesCache); 192 propsConfig.setServerManagedPropsMode(ServerManagedPropsMode.STRICT); 193 when(psManager.getSession(ArgumentMatchers.any())).thenReturn(psSession); 194 transaction = TestTransactionHelper.mockTransaction(TX_ID, false); 195 } 196 197 /** 198 * Test trying to add a child to a non-existant parent. 199 * We recursive to repository root for a parent, so this is now just creating a ghost node? 200 */ 201 @Test 202 public void testNoParentRdf() throws Exception { 203 final FedoraId fedoraId = FedoraId.create(UUID.randomUUID().toString()); 204 final FedoraId childId = fedoraId.resolve("child"); 205 when(psSession.getHeaders(fedoraId, null)).thenThrow(PersistentItemNotFoundException.class); 206 createResourceService.perform(transaction, USER_PRINCIPAL, childId, null, model); 207 verify(transaction).lockResourceAndGhostNodes(childId); 208 } 209 210 /** 211 * Test creating a new container directly inside an AG. 212 */ 213 @Test 214 public void testParentAg() { 215 final FedoraId fedoraId = FedoraId.create(UUID.randomUUID().toString()); 216 final FedoraId childId = fedoraId.resolve("child"); 217 containmentIndex.addContainedBy(transaction, FedoraId.getRepositoryRootId(), fedoraId); 218 when(psSession.getHeaders(fedoraId, null)).thenReturn(resourceHeaders); 219 when(resourceHeaders.isArchivalGroup()).thenReturn(true); 220 createResourceService.perform(transaction, USER_PRINCIPAL, childId, null, model); 221 verify(transaction).lockResource(fedoraId); 222 verify(transaction).lockResourceAndGhostNodes(childId); 223 } 224 225 /** 226 * Test creating a new container inside a container inside an AG. 227 */ 228 @Test 229 public void testParentContainerInAg() { 230 final FedoraId agId = FedoraId.create(UUID.randomUUID().toString()); 231 final FedoraId fedoraId = agId.resolve(UUID.randomUUID().toString()); 232 final FedoraId childId = fedoraId.resolve("child"); 233 containmentIndex.addContainedBy(transaction, agId, fedoraId); 234 when(psSession.getHeaders(fedoraId, null)).thenReturn(resourceHeaders); 235 when(resourceHeaders.isArchivalGroup()).thenReturn(false); 236 when(resourceHeaders.getArchivalGroupId()).thenReturn(agId); 237 createResourceService.perform(transaction, USER_PRINCIPAL, childId, null, model); 238 verify(transaction).lockResource(agId); 239 verify(transaction).lockResourceAndGhostNodes(childId); 240 } 241 242 /** 243 * Test creating a RDFSource with a NonRDFSource parent. 244 */ 245 @Test(expected = InteractionModelViolationException.class) 246 public void testParentIsBinaryRdf() throws Exception { 247 final FedoraId fedoraId = FedoraId.create(UUID.randomUUID().toString()); 248 final FedoraId childId = fedoraId.resolve("child"); 249 containmentIndex.addContainedBy(transaction, rootId, fedoraId); 250 when(resourceHeaders.getInteractionModel()).thenReturn(NON_RDF_SOURCE.toString()); 251 when(psSession.getHeaders(fedoraId, null)).thenReturn(resourceHeaders); 252 createResourceService.perform(transaction, USER_PRINCIPAL, childId, null, model); 253 } 254 255 /** 256 * Test creating a NonRDFSource with a NonRDFSource parent. 257 */ 258 @Test(expected = InteractionModelViolationException.class) 259 public void testParentIsBinary() throws Exception { 260 final FedoraId fedoraId = FedoraId.create(UUID.randomUUID().toString()); 261 final FedoraId childId = fedoraId.resolve("child"); 262 containmentIndex.addContainedBy(transaction, rootId, fedoraId); 263 when(resourceHeaders.getInteractionModel()).thenReturn(NON_RDF_SOURCE.toString()); 264 when(psSession.getHeaders(fedoraId, null)).thenReturn(resourceHeaders); 265 createResourceService.perform(transaction, USER_PRINCIPAL, childId, null, FILENAME, CONTENT_SIZE, null, 266 DIGESTS, null, null); 267 } 268 269 /** 270 * Test creating an external NonRDFSource with a NonRDFSource parent. 271 * TODO: put/post to a binary parent is tested above, might be a duplicate. 272 */ 273 @Test(expected = InteractionModelViolationException.class) 274 public void testParentIsExternal() throws Exception { 275 final FedoraId fedoraId = FedoraId.create(UUID.randomUUID().toString()); 276 final FedoraId childId = fedoraId.resolve("child"); 277 containmentIndex.addContainedBy(transaction, rootId, fedoraId); 278 when(resourceHeaders.getInteractionModel()).thenReturn(NON_RDF_SOURCE.toString()); 279 when(psSession.getHeaders(fedoraId, null)).thenReturn(resourceHeaders); 280 createResourceService.perform(transaction, USER_PRINCIPAL, childId, null, FILENAME, CONTENT_SIZE, null, 281 DIGESTS, null, extContent); 282 } 283 284 /** 285 * Test creating a RDFSource with a RDFSource parent. 286 */ 287 @Test 288 public void testParentIsRdf() throws Exception { 289 final FedoraId fedoraId = FedoraId.create(UUID.randomUUID().toString()); 290 final FedoraId childId = fedoraId.resolve("child"); 291 containmentIndex.addContainedBy(transaction, rootId, fedoraId); 292 when(psSession.getHeaders(fedoraId, null)).thenReturn(resourceHeaders); 293 when(resourceHeaders.getInteractionModel()).thenReturn(BASIC_CONTAINER.toString()); 294 createResourceService.perform(transaction, USER_PRINCIPAL, childId, null, model); 295 cleanupList.add(fedoraId); 296 verify(psSession).persist(operationCaptor.capture()); 297 final FedoraId persistedId = operationCaptor.getValue().getResourceId(); 298 assertNotEquals(fedoraId, persistedId); 299 assertTrue(persistedId.getFullId().startsWith(fedoraId.getFullId())); 300 assertEquals(1, containmentIndex.getContains(transaction, fedoraId).count()); 301 302 verify(transaction).lockResourceAndGhostNodes(childId); 303 } 304 305 /** 306 * Test creating a NonRDFSource with a RDFSource parent. 307 */ 308 @Test 309 public void testParentIsRdfBinary() throws Exception { 310 final FedoraId fedoraId = FedoraId.create(UUID.randomUUID().toString()); 311 final FedoraId childId = fedoraId.resolve("child"); 312 containmentIndex.addContainedBy(transaction, rootId, fedoraId); 313 when(psSession.getHeaders(fedoraId, null)).thenReturn(resourceHeaders); 314 when(resourceHeaders.getInteractionModel()).thenReturn(BASIC_CONTAINER.toString()); 315 createResourceService.perform(transaction, USER_PRINCIPAL, childId, CONTENT_TYPE, 316 FILENAME, CONTENT_SIZE, null, DIGESTS, null, null); 317 cleanupList.add(fedoraId); 318 verify(psSession, times(2)).persist(operationCaptor.capture()); 319 final List<ResourceOperation> operations = operationCaptor.getAllValues(); 320 final var operation = getOperation(operations, CreateNonRdfSourceOperation.class); 321 final FedoraId persistedId = operation.getResourceId(); 322 assertNotEquals(fedoraId, persistedId); 323 assertTrue(persistedId.getFullId().startsWith(fedoraId.getFullId())); 324 assertBinaryPropertiesPresent(operation); 325 assertEquals(fedoraId, operation.getParentId()); 326 assertEquals(1, containmentIndex.getContains(transaction, fedoraId).count()); 327 verify(transaction).lockResourceAndGhostNodes(childId); 328 } 329 330 /** 331 * Test setting some system properties only accessible in relaxed mode. 332 */ 333 @Test 334 public void testRdfSetRelaxedProperties_Post() throws Exception { 335 final var createdDate = Instant.parse("2019-11-12T10:00:30.0Z"); 336 final var calendar = Calendar.getInstance(); 337 calendar.setTime(Date.from(createdDate)); 338 final var createdDateXsd = new XSDDateTime(calendar); 339 final var lastModifiedDate = Instant.parse("2019-11-12T14:11:05.0Z"); 340 calendar.setTime(Date.from(lastModifiedDate)); 341 final var lastModifiedDateXsd = new XSDDateTime(calendar); 342 final String relaxedUser = "relaxedUser"; 343 final FedoraId fedoraId = FedoraId.create(UUID.randomUUID().toString()); 344 final FedoraId childId = fedoraId.resolve("testSlug"); 345 containmentIndex.addContainedBy(transaction, rootId, fedoraId); 346 347 final var resc = model.getResource(childId.getFullId()); 348 resc.addLiteral(LAST_MODIFIED_DATE, lastModifiedDateXsd); 349 resc.addLiteral(LAST_MODIFIED_BY, relaxedUser); 350 resc.addLiteral(CREATED_DATE, createdDateXsd); 351 resc.addLiteral(CREATED_BY, relaxedUser); 352 353 when(psSession.getHeaders(fedoraId, null)).thenReturn(resourceHeaders); 354 when(psSession.getHeaders(childId, null)).thenReturn(resourceHeaders); 355 356 when(resourceHeaders.getInteractionModel()).thenReturn(BASIC_CONTAINER.toString()); 357 propsConfig.setServerManagedPropsMode(ServerManagedPropsMode.RELAXED); 358 createResourceService.perform(transaction, USER_PRINCIPAL, childId, null, model); 359 cleanupList.add(fedoraId); 360 361 verify(psSession).persist(operationCaptor.capture()); 362 363 final var operation = operationCaptor.getValue(); 364 final FedoraId persistedId = operation.getResourceId(); 365 assertNotEquals(fedoraId, persistedId); 366 assertTrue(persistedId.getFullId().startsWith(fedoraId.getFullId())); 367 assertEquals(persistedId, childId); 368 369 final var rdfOp = (RdfSourceOperation) operation; 370 assertEquals(relaxedUser, rdfOp.getCreatedBy()); 371 assertEquals(relaxedUser, rdfOp.getLastModifiedBy()); 372 assertEquals(createdDate, rdfOp.getCreatedDate()); 373 assertEquals(lastModifiedDate, rdfOp.getLastModifiedDate()); 374 assertEquals(1, containmentIndex.getContains(transaction, fedoraId).count()); 375 verify(transaction).lockResourceAndGhostNodes(childId); 376 } 377 378 /** 379 * This test now seems to ensure that the createResourceService will overwrite an existing object 380 * TODO: Review expectations 381 */ 382 @Test 383 public void testWithBinary() throws Exception { 384 final FedoraId fedoraId = FedoraId.create(UUID.randomUUID().toString()); 385 final FedoraId childId = fedoraId.resolve("testSlug"); 386 containmentIndex.addContainedBy(transaction, FedoraId.getRepositoryRootId(), fedoraId); 387 containmentIndex.commitTransaction(transaction); 388 when(psSession.getHeaders(fedoraId, null)).thenReturn(resourceHeaders); 389 when(psSession.getHeaders(childId, null)).thenReturn(resourceHeaders); 390 when(resourceHeaders.getInteractionModel()).thenReturn(BASIC_CONTAINER.toString()); 391 createResourceService.perform(transaction, USER_PRINCIPAL, childId, 392 CONTENT_TYPE, FILENAME, CONTENT_SIZE, null, DIGESTS, null, null); 393 cleanupList.add(fedoraId); 394 verify(psSession, times(2)).persist(operationCaptor.capture()); 395 final List<ResourceOperation> operations = operationCaptor.getAllValues(); 396 final var operation = getOperation(operations, CreateNonRdfSourceOperation.class); 397 final FedoraId persistedId = operation.getResourceId(); 398 assertNotEquals(fedoraId, persistedId); 399 assertEquals(childId, persistedId); 400 assertTrue(persistedId.getFullId().startsWith(fedoraId.getFullId())); 401 assertBinaryPropertiesPresent(operation); 402 assertEquals(fedoraId, operation.getParentId()); 403 404 final var descOperation = getOperation(operations, CreateRdfSourceOperation.class); 405 assertEquals(persistedId.asDescription(), descOperation.getResourceId()); 406 assertEquals(1, containmentIndex.getContains(transaction, fedoraId).count()); 407 verify(transaction).lockResourceAndGhostNodes(childId); 408 } 409 410 @Test 411 public void testSendingValidInteractionModel() { 412 // If you provide a valid interaction model, you should always get it back. 413 final String expected = BASIC_CONTAINER.toString(); 414 final String model1 = createResourceService.determineInteractionModel(STRING_TYPES_VALID, false, false, false); 415 assertEquals(expected, model1); 416 final String model2 = createResourceService.determineInteractionModel(STRING_TYPES_VALID, false, false, true); 417 assertEquals(expected, model2); 418 final String model3 = createResourceService.determineInteractionModel(STRING_TYPES_VALID, false, true, false); 419 assertEquals(expected, model3); 420 final String model4 = createResourceService.determineInteractionModel(STRING_TYPES_VALID, false, true, true); 421 assertEquals(expected, model4); 422 final String model5 = createResourceService.determineInteractionModel(STRING_TYPES_VALID, true, false, false); 423 assertEquals(expected, model5); 424 final String model6 = createResourceService.determineInteractionModel(STRING_TYPES_VALID, true, false, true); 425 assertEquals(expected, model6); 426 final String model7 = createResourceService.determineInteractionModel(STRING_TYPES_VALID, true, true, false); 427 assertEquals(expected, model7); 428 final String model8 = createResourceService.determineInteractionModel(STRING_TYPES_VALID, true, true, true); 429 assertEquals(expected, model8); 430 } 431 432 @Test 433 public void testSendingInvalidInteractionModelIsNotRdf() { 434 final String model = createResourceService.determineInteractionModel(STRING_TYPES_NOT_VALID, false, false, 435 false); 436 assertEquals(defaultInteractionModel, model); 437 } 438 439 @Test 440 public void testNotRdfNoContentIsExternal() { 441 final String expected = NON_RDF_SOURCE.toString(); 442 final String model = createResourceService.determineInteractionModel(STRING_TYPES_NOT_VALID, false, false, 443 true); 444 assertEquals(expected, model); 445 } 446 447 @Test 448 public void testNotRdfContentPresentNotExternal() { 449 final String expected = NON_RDF_SOURCE.toString(); 450 final String model = createResourceService.determineInteractionModel(STRING_TYPES_NOT_VALID, false, true, 451 false); 452 assertEquals(expected, model); 453 } 454 455 @Test 456 public void testNotRdfContentPresentIsExternal() { 457 final String expected = NON_RDF_SOURCE.toString(); 458 final String model = createResourceService.determineInteractionModel(STRING_TYPES_NOT_VALID, false, true, true); 459 assertEquals(expected, model); 460 } 461 462 @Test 463 public void testIsRdfNoContentNotExternal() { 464 final String model = createResourceService.determineInteractionModel(STRING_TYPES_NOT_VALID, true, false, 465 false); 466 assertEquals(defaultInteractionModel, model); 467 } 468 469 @Test 470 public void testIsRdfNoContentIsExternal() { 471 final String expected = NON_RDF_SOURCE.toString(); 472 final String model = createResourceService.determineInteractionModel(STRING_TYPES_NOT_VALID, true, false, true); 473 assertEquals(expected, model); 474 } 475 476 @Test 477 public void testIsRdfHasContentNotExternal() { 478 final String model = createResourceService.determineInteractionModel(STRING_TYPES_NOT_VALID, true, true, false); 479 assertEquals(defaultInteractionModel, model); 480 } 481 482 @Test 483 public void testIsRdfHasContentIsExternal() { 484 final String expected = NON_RDF_SOURCE.toString(); 485 final String model = createResourceService.determineInteractionModel(STRING_TYPES_NOT_VALID, true, true, true); 486 assertEquals(expected, model); 487 } 488 489 @Test(expected = RequestWithAclLinkHeaderException.class) 490 public void testCheckAclLinkHeaderFailDblQ() throws Exception { 491 final List<String> links = Arrays.asList("<" + NON_RDF_SOURCE.toString() + ">; rel=\"type\"", 492 "<http" + "://example.org/some/location/image.tiff>; " + "rel=\"http://fedora" + 493 ".info/definitions/fcrepo#ExternalContent\"; " + "handling=\"proxy\"; type=\"image/tiff\"", 494 "<http" + "://example.org/some/otherlocation>; rel=\"acl\""); 495 createResourceService.checkAclLinkHeader(links); 496 } 497 498 @Test(expected = RequestWithAclLinkHeaderException.class) 499 public void testCheckAclLinkHeaderFailSingleQ() throws Exception { 500 final List<String> links = Arrays.asList("<" + NON_RDF_SOURCE.toString() + ">; rel=\"type\"", 501 "<http" + "://example.org/some/location/image.tiff>; " + "rel=\"http://fedora" + 502 ".info/definitions/fcrepo#ExternalContent\"; " + "handling=\"proxy\"; type=\"image/tiff\"", 503 "<http" + "://example.org/some/otherlocation>; rel='acl'"); 504 createResourceService.checkAclLinkHeader(links); 505 } 506 507 @Test(expected = RequestWithAclLinkHeaderException.class) 508 public void testCheckAclLinkHeaderFailNoQ() throws Exception { 509 final List<String> links = Arrays.asList("<" + NON_RDF_SOURCE.toString() + ">; rel=\"type\"", 510 "<http" + "://example.org/some/location/image.tiff>; " + "rel=\"http://fedora" + 511 ".info/definitions/fcrepo#ExternalContent\"; " + "handling=\"proxy\"; type=\"image/tiff\"", 512 "<http" + "://example.org/some/otherlocation>; rel=acl"); 513 createResourceService.checkAclLinkHeader(links); 514 } 515 516 @Test 517 public void testCheckAclLinkHeaderSuccess() throws Exception { 518 final List<String> links = Arrays.asList("<" + NON_RDF_SOURCE.toString() + ">; rel=\"type\"", 519 "<http" + "://example.org/some/location/image.tiff>; " + "rel=\"http://fedora" + 520 ".info/definitions/fcrepo#ExternalContent\"; " + "handling=\"proxy\"; type=\"image/tiff\""); 521 createResourceService.checkAclLinkHeader(links); 522 } 523 524 @Test 525 public void testCopyExternalBinary() throws Exception { 526 final var realDigests = asList(URI.create("urn:sha1:94e66df8cd09d410c62d9e0dc59d3a884e458e05")); 527 528 tempFolder.create(); 529 final File externalFile = tempFolder.newFile(); 530 final String contentString = "some content"; 531 FileUtils.write(externalFile, contentString, StandardCharsets.UTF_8); 532 final URI uri = externalFile.toURI(); 533 when(extContent.fetchExternalContent()).thenReturn(Files.newInputStream(externalFile.toPath())); 534 when(extContent.getURI()).thenReturn(uri); 535 when(extContent.isCopy()).thenReturn(true); 536 when(extContent.getHandling()).thenReturn(ExternalContent.COPY); 537 when(extContent.getContentType()).thenReturn("text/plain"); 538 539 final FedoraId fedoraId = FedoraId.create(UUID.randomUUID().toString()); 540 final FedoraId childId = fedoraId.resolve("child"); 541 containmentIndex.addContainedBy(transaction, FedoraId.getRepositoryRootId(), fedoraId); 542 containmentIndex.commitTransaction(transaction); 543 544 when(resourceHeaders.getArchivalGroupId()).thenReturn(null); 545 when(resourceHeaders.isArchivalGroup()).thenReturn(false); 546 when(psSession.getHeaders(fedoraId, null)).thenReturn(resourceHeaders); 547 548 createResourceService.perform(transaction, USER_PRINCIPAL, childId, 549 CONTENT_TYPE, FILENAME, contentString.length(), null, realDigests, null, extContent); 550 551 verify(psSession, times(2)).persist(operationCaptor.capture()); 552 final List<ResourceOperation> operations = operationCaptor.getAllValues(); 553 final var operation = getOperation(operations, CreateNonRdfSourceOperation.class); 554 final FedoraId persistedId = operation.getResourceId(); 555 assertNotEquals(fedoraId, persistedId); 556 assertTrue(persistedId.getFullId().startsWith(fedoraId.getFullId())); 557 assertBinaryPropertiesPresent(operation, "text/plain", FILENAME, contentString.length(), realDigests); 558 verify(transaction).lockResourceAndGhostNodes(childId); 559 } 560 561 @Test 562 public void testProxyExternalBinary() throws Exception { 563 final var realDigests = asList(URI.create("urn:sha1:94e66df8cd09d410c62d9e0dc59d3a884e458e05")); 564 565 tempFolder.create(); 566 final File externalFile = tempFolder.newFile(); 567 final String contentString = "some content"; 568 FileUtils.write(externalFile, contentString, StandardCharsets.UTF_8); 569 final URI uri = externalFile.toURI(); 570 when(extContent.fetchExternalContent()).thenReturn(Files.newInputStream(externalFile.toPath())); 571 when(extContent.getURI()).thenReturn(uri); 572 when(extContent.getHandling()).thenReturn(ExternalContent.PROXY); 573 when(extContent.getContentType()).thenReturn(EXTERNAL_CONTENT_TYPE); 574 575 final FedoraId fedoraId = FedoraId.create(UUID.randomUUID().toString()); 576 final FedoraId childId = fedoraId.resolve("child"); 577 containmentIndex.addContainedBy(transaction, FedoraId.getRepositoryRootId(), fedoraId); 578 containmentIndex.commitTransaction(transaction); 579 580 when(resourceHeaders.getArchivalGroupId()).thenReturn(null); 581 when(resourceHeaders.isArchivalGroup()).thenReturn(false); 582 when(psSession.getHeaders(fedoraId, null)).thenReturn(resourceHeaders); 583 584 createResourceService.perform(transaction, USER_PRINCIPAL, childId, 585 CONTENT_TYPE, FILENAME, contentString.length(), null, realDigests, null, extContent); 586 587 verify(psSession, times(2)).persist(operationCaptor.capture()); 588 final List<ResourceOperation> operations = operationCaptor.getAllValues(); 589 final var operation = getOperation(operations, CreateNonRdfSourceOperation.class); 590 final FedoraId persistedId = operation.getResourceId(); 591 assertNotEquals(fedoraId, persistedId); 592 assertTrue(persistedId.getFullId().startsWith(fedoraId.getFullId())); 593 assertBinaryPropertiesPresent(operation, EXTERNAL_CONTENT_TYPE, FILENAME, contentString.length(), 594 realDigests); 595 596 assertExternalBinaryPropertiesPresent(operation, uri, ExternalContent.PROXY); 597 verify(transaction).lockResourceAndGhostNodes(childId); 598 } 599 600 private void assertBinaryPropertiesPresent(final ResourceOperation operation, final String exMimetype, 601 final String exFilename, final long exContentSize, final Collection<URI> exDigests) { 602 final var nonRdfOperation = (NonRdfSourceOperation) operation; 603 assertEquals(exContentSize, nonRdfOperation.getContentSize()); 604 assertEquals(exFilename, nonRdfOperation.getFilename()); 605 assertEquals(exMimetype, nonRdfOperation.getMimeType()); 606 assertTrue(exDigests.containsAll(nonRdfOperation.getContentDigests())); 607 } 608 609 private void assertBinaryPropertiesPresent(final ResourceOperation operation) { 610 assertBinaryPropertiesPresent(operation, CONTENT_TYPE, FILENAME, CONTENT_SIZE, DIGESTS); 611 } 612 613 private void assertExternalBinaryPropertiesPresent(final ResourceOperation operation, final URI exExternalUri, 614 final String exHandling) { 615 final var nonRdfOperation = (NonRdfSourceOperation) operation; 616 assertEquals(exExternalUri, nonRdfOperation.getContentUri()); 617 assertEquals(exHandling, nonRdfOperation.getExternalHandling()); 618 } 619 620 private <T extends ResourceOperation> T getOperation(final List<ResourceOperation> operations, 621 final Class<T> clazz) { 622 return clazz.cast(operations.stream() 623 .filter(o -> (clazz.isInstance(o))) 624 .findFirst() 625 .get()); 626 } 627 628}