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).lockResource(childId); 208 } 209 210 @Test 211 public void testParentAg() { 212 final FedoraId fedoraId = FedoraId.create(UUID.randomUUID().toString()); 213 final FedoraId childId = fedoraId.resolve("child"); 214 containmentIndex.addContainedBy(transaction, FedoraId.getRepositoryRootId(), fedoraId); 215 when(psSession.getHeaders(fedoraId, null)).thenReturn(resourceHeaders); 216 when(resourceHeaders.isArchivalGroup()).thenReturn(true); 217 createResourceService.perform(transaction, USER_PRINCIPAL, childId, null, model); 218 verify(transaction).lockResource(fedoraId); 219 verify(transaction).lockResource(childId); 220 } 221 222 @Test 223 public void testParentContainerInAg() { 224 final FedoraId agId = FedoraId.create(UUID.randomUUID().toString()); 225 final FedoraId fedoraId = agId.resolve(UUID.randomUUID().toString()); 226 final FedoraId childId = fedoraId.resolve("child"); 227 containmentIndex.addContainedBy(transaction, agId, fedoraId); 228 when(psSession.getHeaders(fedoraId, null)).thenReturn(resourceHeaders); 229 when(resourceHeaders.isArchivalGroup()).thenReturn(false); 230 when(resourceHeaders.getArchivalGroupId()).thenReturn(agId); 231 createResourceService.perform(transaction, USER_PRINCIPAL, childId, null, model); 232 verify(transaction).lockResource(agId); 233 verify(transaction).lockResource(childId); 234 } 235 236 /** 237 * Test creating a RDFSource with a NonRDFSource parent. 238 */ 239 @Test(expected = InteractionModelViolationException.class) 240 public void testParentIsBinaryRdf() throws Exception { 241 final FedoraId fedoraId = FedoraId.create(UUID.randomUUID().toString()); 242 final FedoraId childId = fedoraId.resolve("child"); 243 containmentIndex.addContainedBy(transaction, rootId, fedoraId); 244 when(resourceHeaders.getInteractionModel()).thenReturn(NON_RDF_SOURCE.toString()); 245 when(psSession.getHeaders(fedoraId, null)).thenReturn(resourceHeaders); 246 createResourceService.perform(transaction, USER_PRINCIPAL, childId, null, model); 247 } 248 249 /** 250 * Test creating a NonRDFSource with a NonRDFSource parent. 251 */ 252 @Test(expected = InteractionModelViolationException.class) 253 public void testParentIsBinary() throws Exception { 254 final FedoraId fedoraId = FedoraId.create(UUID.randomUUID().toString()); 255 final FedoraId childId = fedoraId.resolve("child"); 256 containmentIndex.addContainedBy(transaction, rootId, fedoraId); 257 when(resourceHeaders.getInteractionModel()).thenReturn(NON_RDF_SOURCE.toString()); 258 when(psSession.getHeaders(fedoraId, null)).thenReturn(resourceHeaders); 259 createResourceService.perform(transaction, USER_PRINCIPAL, childId, null, FILENAME, CONTENT_SIZE, null, 260 DIGESTS, null, null); 261 } 262 263 /** 264 * Test creating an external NonRDFSource with a NonRDFSource parent. 265 * TODO: put/post to a binary parent is tested above, might be a duplicate. 266 */ 267 @Test(expected = InteractionModelViolationException.class) 268 public void testParentIsExternal() throws Exception { 269 final FedoraId fedoraId = FedoraId.create(UUID.randomUUID().toString()); 270 final FedoraId childId = fedoraId.resolve("child"); 271 containmentIndex.addContainedBy(transaction, rootId, fedoraId); 272 when(resourceHeaders.getInteractionModel()).thenReturn(NON_RDF_SOURCE.toString()); 273 when(psSession.getHeaders(fedoraId, null)).thenReturn(resourceHeaders); 274 createResourceService.perform(transaction, USER_PRINCIPAL, childId, null, FILENAME, CONTENT_SIZE, null, 275 DIGESTS, null, extContent); 276 } 277 278 /** 279 * Test creating a RDFSource with a RDFSource parent. 280 */ 281 @Test 282 public void testParentIsRdf() throws Exception { 283 final FedoraId fedoraId = FedoraId.create(UUID.randomUUID().toString()); 284 final FedoraId childId = fedoraId.resolve("child"); 285 containmentIndex.addContainedBy(transaction, rootId, fedoraId); 286 when(psSession.getHeaders(fedoraId, null)).thenReturn(resourceHeaders); 287 when(resourceHeaders.getInteractionModel()).thenReturn(BASIC_CONTAINER.toString()); 288 createResourceService.perform(transaction, USER_PRINCIPAL, childId, null, model); 289 cleanupList.add(fedoraId); 290 verify(psSession).persist(operationCaptor.capture()); 291 final FedoraId persistedId = operationCaptor.getValue().getResourceId(); 292 assertNotEquals(fedoraId, persistedId); 293 assertTrue(persistedId.getFullId().startsWith(fedoraId.getFullId())); 294 assertEquals(1, containmentIndex.getContains(transaction, fedoraId).count()); 295 296 verify(transaction).lockResource(childId); 297 } 298 299 /** 300 * Test creating a NonRDFSource with a RDFSource parent. 301 */ 302 @Test 303 public void testParentIsRdfBinary() throws Exception { 304 final FedoraId fedoraId = FedoraId.create(UUID.randomUUID().toString()); 305 final FedoraId childId = fedoraId.resolve("child"); 306 containmentIndex.addContainedBy(transaction, rootId, fedoraId); 307 when(psSession.getHeaders(fedoraId, null)).thenReturn(resourceHeaders); 308 when(resourceHeaders.getInteractionModel()).thenReturn(BASIC_CONTAINER.toString()); 309 createResourceService.perform(transaction, USER_PRINCIPAL, childId, CONTENT_TYPE, 310 FILENAME, CONTENT_SIZE, null, DIGESTS, null, null); 311 cleanupList.add(fedoraId); 312 verify(psSession, times(2)).persist(operationCaptor.capture()); 313 final List<ResourceOperation> operations = operationCaptor.getAllValues(); 314 final var operation = getOperation(operations, CreateNonRdfSourceOperation.class); 315 final FedoraId persistedId = operation.getResourceId(); 316 assertNotEquals(fedoraId, persistedId); 317 assertTrue(persistedId.getFullId().startsWith(fedoraId.getFullId())); 318 assertBinaryPropertiesPresent(operation); 319 assertEquals(fedoraId, operation.getParentId()); 320 assertEquals(1, containmentIndex.getContains(transaction, fedoraId).count()); 321 verify(transaction).lockResource(childId); 322 } 323 324 /** 325 * Test setting some system properties only accessible in relaxed mode. 326 */ 327 @Test 328 public void testRdfSetRelaxedProperties_Post() throws Exception { 329 final var createdDate = Instant.parse("2019-11-12T10:00:30.0Z"); 330 final var calendar = Calendar.getInstance(); 331 calendar.setTime(Date.from(createdDate)); 332 final var createdDateXsd = new XSDDateTime(calendar); 333 final var lastModifiedDate = Instant.parse("2019-11-12T14:11:05.0Z"); 334 calendar.setTime(Date.from(lastModifiedDate)); 335 final var lastModifiedDateXsd = new XSDDateTime(calendar); 336 final String relaxedUser = "relaxedUser"; 337 final FedoraId fedoraId = FedoraId.create(UUID.randomUUID().toString()); 338 final FedoraId childId = fedoraId.resolve("testSlug"); 339 containmentIndex.addContainedBy(transaction, rootId, fedoraId); 340 341 final var resc = model.getResource(childId.getFullId()); 342 resc.addLiteral(LAST_MODIFIED_DATE, lastModifiedDateXsd); 343 resc.addLiteral(LAST_MODIFIED_BY, relaxedUser); 344 resc.addLiteral(CREATED_DATE, createdDateXsd); 345 resc.addLiteral(CREATED_BY, relaxedUser); 346 347 when(psSession.getHeaders(fedoraId, null)).thenReturn(resourceHeaders); 348 when(psSession.getHeaders(childId, null)).thenReturn(resourceHeaders); 349 350 when(resourceHeaders.getInteractionModel()).thenReturn(BASIC_CONTAINER.toString()); 351 propsConfig.setServerManagedPropsMode(ServerManagedPropsMode.RELAXED); 352 createResourceService.perform(transaction, USER_PRINCIPAL, childId, null, model); 353 cleanupList.add(fedoraId); 354 355 verify(psSession).persist(operationCaptor.capture()); 356 357 final var operation = operationCaptor.getValue(); 358 final FedoraId persistedId = operation.getResourceId(); 359 assertNotEquals(fedoraId, persistedId); 360 assertTrue(persistedId.getFullId().startsWith(fedoraId.getFullId())); 361 assertEquals(persistedId, childId); 362 363 final var rdfOp = (RdfSourceOperation) operation; 364 assertEquals(relaxedUser, rdfOp.getCreatedBy()); 365 assertEquals(relaxedUser, rdfOp.getLastModifiedBy()); 366 assertEquals(createdDate, rdfOp.getCreatedDate()); 367 assertEquals(lastModifiedDate, rdfOp.getLastModifiedDate()); 368 assertEquals(1, containmentIndex.getContains(transaction, fedoraId).count()); 369 verify(transaction).lockResource(childId); 370 } 371 372 /** 373 * This test now seems to ensure that the createResourceService will overwrite an existing object 374 * TODO: Review expectations 375 */ 376 @Test 377 public void testWithBinary() throws Exception { 378 final FedoraId fedoraId = FedoraId.create(UUID.randomUUID().toString()); 379 final FedoraId childId = fedoraId.resolve("testSlug"); 380 containmentIndex.addContainedBy(transaction, FedoraId.getRepositoryRootId(), fedoraId); 381 containmentIndex.commitTransaction(transaction); 382 when(psSession.getHeaders(fedoraId, null)).thenReturn(resourceHeaders); 383 when(psSession.getHeaders(childId, null)).thenReturn(resourceHeaders); 384 when(resourceHeaders.getInteractionModel()).thenReturn(BASIC_CONTAINER.toString()); 385 createResourceService.perform(transaction, USER_PRINCIPAL, childId, 386 CONTENT_TYPE, FILENAME, CONTENT_SIZE, null, DIGESTS, null, null); 387 cleanupList.add(fedoraId); 388 verify(psSession, times(2)).persist(operationCaptor.capture()); 389 final List<ResourceOperation> operations = operationCaptor.getAllValues(); 390 final var operation = getOperation(operations, CreateNonRdfSourceOperation.class); 391 final FedoraId persistedId = operation.getResourceId(); 392 assertNotEquals(fedoraId, persistedId); 393 assertEquals(childId, persistedId); 394 assertTrue(persistedId.getFullId().startsWith(fedoraId.getFullId())); 395 assertBinaryPropertiesPresent(operation); 396 assertEquals(fedoraId, operation.getParentId()); 397 398 final var descOperation = getOperation(operations, CreateRdfSourceOperation.class); 399 assertEquals(persistedId.asDescription(), descOperation.getResourceId()); 400 assertEquals(1, containmentIndex.getContains(transaction, fedoraId).count()); 401 verify(transaction).lockResource(childId); 402 } 403 404 @Test 405 public void testSendingValidInteractionModel() { 406 // If you provide a valid interaction model, you should always get it back. 407 final String expected = BASIC_CONTAINER.toString(); 408 final String model1 = createResourceService.determineInteractionModel(STRING_TYPES_VALID, false, false, false); 409 assertEquals(expected, model1); 410 final String model2 = createResourceService.determineInteractionModel(STRING_TYPES_VALID, false, false, true); 411 assertEquals(expected, model2); 412 final String model3 = createResourceService.determineInteractionModel(STRING_TYPES_VALID, false, true, false); 413 assertEquals(expected, model3); 414 final String model4 = createResourceService.determineInteractionModel(STRING_TYPES_VALID, false, true, true); 415 assertEquals(expected, model4); 416 final String model5 = createResourceService.determineInteractionModel(STRING_TYPES_VALID, true, false, false); 417 assertEquals(expected, model5); 418 final String model6 = createResourceService.determineInteractionModel(STRING_TYPES_VALID, true, false, true); 419 assertEquals(expected, model6); 420 final String model7 = createResourceService.determineInteractionModel(STRING_TYPES_VALID, true, true, false); 421 assertEquals(expected, model7); 422 final String model8 = createResourceService.determineInteractionModel(STRING_TYPES_VALID, true, true, true); 423 assertEquals(expected, model8); 424 } 425 426 @Test 427 public void testSendingInvalidInteractionModelIsNotRdf() { 428 final String model = createResourceService.determineInteractionModel(STRING_TYPES_NOT_VALID, false, false, 429 false); 430 assertEquals(defaultInteractionModel, model); 431 } 432 433 @Test 434 public void testNotRdfNoContentIsExternal() { 435 final String expected = NON_RDF_SOURCE.toString(); 436 final String model = createResourceService.determineInteractionModel(STRING_TYPES_NOT_VALID, false, false, 437 true); 438 assertEquals(expected, model); 439 } 440 441 @Test 442 public void testNotRdfContentPresentNotExternal() { 443 final String expected = NON_RDF_SOURCE.toString(); 444 final String model = createResourceService.determineInteractionModel(STRING_TYPES_NOT_VALID, false, true, 445 false); 446 assertEquals(expected, model); 447 } 448 449 @Test 450 public void testNotRdfContentPresentIsExternal() { 451 final String expected = NON_RDF_SOURCE.toString(); 452 final String model = createResourceService.determineInteractionModel(STRING_TYPES_NOT_VALID, false, true, true); 453 assertEquals(expected, model); 454 } 455 456 @Test 457 public void testIsRdfNoContentNotExternal() { 458 final String model = createResourceService.determineInteractionModel(STRING_TYPES_NOT_VALID, true, false, 459 false); 460 assertEquals(defaultInteractionModel, model); 461 } 462 463 @Test 464 public void testIsRdfNoContentIsExternal() { 465 final String expected = NON_RDF_SOURCE.toString(); 466 final String model = createResourceService.determineInteractionModel(STRING_TYPES_NOT_VALID, true, false, true); 467 assertEquals(expected, model); 468 } 469 470 @Test 471 public void testIsRdfHasContentNotExternal() { 472 final String model = createResourceService.determineInteractionModel(STRING_TYPES_NOT_VALID, true, true, false); 473 assertEquals(defaultInteractionModel, model); 474 } 475 476 @Test 477 public void testIsRdfHasContentIsExternal() { 478 final String expected = NON_RDF_SOURCE.toString(); 479 final String model = createResourceService.determineInteractionModel(STRING_TYPES_NOT_VALID, true, true, true); 480 assertEquals(expected, model); 481 } 482 483 @Test(expected = RequestWithAclLinkHeaderException.class) 484 public void testCheckAclLinkHeaderFailDblQ() throws Exception { 485 final List<String> links = Arrays.asList("<" + NON_RDF_SOURCE.toString() + ">; rel=\"type\"", 486 "<http" + "://example.org/some/location/image.tiff>; " + "rel=\"http://fedora" + 487 ".info/definitions/fcrepo#ExternalContent\"; " + "handling=\"proxy\"; type=\"image/tiff\"", 488 "<http" + "://example.org/some/otherlocation>; rel=\"acl\""); 489 createResourceService.checkAclLinkHeader(links); 490 } 491 492 @Test(expected = RequestWithAclLinkHeaderException.class) 493 public void testCheckAclLinkHeaderFailSingleQ() throws Exception { 494 final List<String> links = Arrays.asList("<" + NON_RDF_SOURCE.toString() + ">; rel=\"type\"", 495 "<http" + "://example.org/some/location/image.tiff>; " + "rel=\"http://fedora" + 496 ".info/definitions/fcrepo#ExternalContent\"; " + "handling=\"proxy\"; type=\"image/tiff\"", 497 "<http" + "://example.org/some/otherlocation>; rel='acl'"); 498 createResourceService.checkAclLinkHeader(links); 499 } 500 501 @Test(expected = RequestWithAclLinkHeaderException.class) 502 public void testCheckAclLinkHeaderFailNoQ() throws Exception { 503 final List<String> links = Arrays.asList("<" + NON_RDF_SOURCE.toString() + ">; rel=\"type\"", 504 "<http" + "://example.org/some/location/image.tiff>; " + "rel=\"http://fedora" + 505 ".info/definitions/fcrepo#ExternalContent\"; " + "handling=\"proxy\"; type=\"image/tiff\"", 506 "<http" + "://example.org/some/otherlocation>; rel=acl"); 507 createResourceService.checkAclLinkHeader(links); 508 } 509 510 @Test 511 public void testCheckAclLinkHeaderSuccess() throws Exception { 512 final List<String> links = Arrays.asList("<" + NON_RDF_SOURCE.toString() + ">; rel=\"type\"", 513 "<http" + "://example.org/some/location/image.tiff>; " + "rel=\"http://fedora" + 514 ".info/definitions/fcrepo#ExternalContent\"; " + "handling=\"proxy\"; type=\"image/tiff\""); 515 createResourceService.checkAclLinkHeader(links); 516 } 517 518 @Test 519 public void testCopyExternalBinary() throws Exception { 520 final var realDigests = asList(URI.create("urn:sha1:94e66df8cd09d410c62d9e0dc59d3a884e458e05")); 521 522 tempFolder.create(); 523 final File externalFile = tempFolder.newFile(); 524 final String contentString = "some content"; 525 FileUtils.write(externalFile, contentString, StandardCharsets.UTF_8); 526 final URI uri = externalFile.toURI(); 527 when(extContent.fetchExternalContent()).thenReturn(Files.newInputStream(externalFile.toPath())); 528 when(extContent.getURI()).thenReturn(uri); 529 when(extContent.isCopy()).thenReturn(true); 530 when(extContent.getHandling()).thenReturn(ExternalContent.COPY); 531 when(extContent.getContentType()).thenReturn("text/plain"); 532 533 final FedoraId fedoraId = FedoraId.create(UUID.randomUUID().toString()); 534 final FedoraId childId = fedoraId.resolve("child"); 535 536 createResourceService.perform(transaction, USER_PRINCIPAL, childId, 537 CONTENT_TYPE, FILENAME, contentString.length(), null, realDigests, null, extContent); 538 539 verify(psSession, times(2)).persist(operationCaptor.capture()); 540 final List<ResourceOperation> operations = operationCaptor.getAllValues(); 541 final var operation = getOperation(operations, CreateNonRdfSourceOperation.class); 542 final FedoraId persistedId = operation.getResourceId(); 543 assertNotEquals(fedoraId, persistedId); 544 assertTrue(persistedId.getFullId().startsWith(fedoraId.getFullId())); 545 assertBinaryPropertiesPresent(operation, "text/plain", FILENAME, contentString.length(), realDigests); 546 verify(transaction).lockResource(childId); 547 } 548 549 @Test 550 public void testProxyExternalBinary() throws Exception { 551 final var realDigests = asList(URI.create("urn:sha1:94e66df8cd09d410c62d9e0dc59d3a884e458e05")); 552 553 tempFolder.create(); 554 final File externalFile = tempFolder.newFile(); 555 final String contentString = "some content"; 556 FileUtils.write(externalFile, contentString, StandardCharsets.UTF_8); 557 final URI uri = externalFile.toURI(); 558 when(extContent.fetchExternalContent()).thenReturn(Files.newInputStream(externalFile.toPath())); 559 when(extContent.getURI()).thenReturn(uri); 560 when(extContent.getHandling()).thenReturn(ExternalContent.PROXY); 561 when(extContent.getContentType()).thenReturn(EXTERNAL_CONTENT_TYPE); 562 563 final FedoraId fedoraId = FedoraId.create(UUID.randomUUID().toString()); 564 final FedoraId childId = fedoraId.resolve("child"); 565 566 createResourceService.perform(transaction, USER_PRINCIPAL, childId, 567 CONTENT_TYPE, FILENAME, contentString.length(), null, realDigests, null, extContent); 568 569 verify(psSession, times(2)).persist(operationCaptor.capture()); 570 final List<ResourceOperation> operations = operationCaptor.getAllValues(); 571 final var operation = getOperation(operations, CreateNonRdfSourceOperation.class); 572 final FedoraId persistedId = operation.getResourceId(); 573 assertNotEquals(fedoraId, persistedId); 574 assertTrue(persistedId.getFullId().startsWith(fedoraId.getFullId())); 575 assertBinaryPropertiesPresent(operation, EXTERNAL_CONTENT_TYPE, FILENAME, contentString.length(), 576 realDigests); 577 578 assertExternalBinaryPropertiesPresent(operation, uri, ExternalContent.PROXY); 579 verify(transaction).lockResource(childId); 580 } 581 582 private void assertBinaryPropertiesPresent(final ResourceOperation operation, final String exMimetype, 583 final String exFilename, final long exContentSize, final Collection<URI> exDigests) { 584 final var nonRdfOperation = (NonRdfSourceOperation) operation; 585 assertEquals(exContentSize, nonRdfOperation.getContentSize()); 586 assertEquals(exFilename, nonRdfOperation.getFilename()); 587 assertEquals(exMimetype, nonRdfOperation.getMimeType()); 588 assertTrue(exDigests.containsAll(nonRdfOperation.getContentDigests())); 589 } 590 591 private void assertBinaryPropertiesPresent(final ResourceOperation operation) { 592 assertBinaryPropertiesPresent(operation, CONTENT_TYPE, FILENAME, CONTENT_SIZE, DIGESTS); 593 } 594 595 private void assertExternalBinaryPropertiesPresent(final ResourceOperation operation, final URI exExternalUri, 596 final String exHandling) { 597 final var nonRdfOperation = (NonRdfSourceOperation) operation; 598 assertEquals(exExternalUri, nonRdfOperation.getContentUri()); 599 assertEquals(exHandling, nonRdfOperation.getExternalHandling()); 600 } 601 602 private <T extends ResourceOperation> T getOperation(final List<ResourceOperation> operations, 603 final Class<T> clazz) { 604 return clazz.cast(operations.stream() 605 .filter(o -> (clazz.isInstance(o))) 606 .findFirst() 607 .get()); 608 } 609 610}