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 org.apache.jena.rdf.model.ResourceFactory.createProperty; 009import static org.fcrepo.kernel.api.RdfLexicon.BASIC_CONTAINER; 010import static org.fcrepo.kernel.api.rdf.DefaultRdfStream.fromModel; 011import static org.junit.Assert.assertEquals; 012import static org.junit.Assert.assertNotEquals; 013import static org.junit.Assert.assertNotNull; 014import static org.junit.Assert.assertNull; 015import static org.junit.Assert.assertTrue; 016import static org.mockito.ArgumentMatchers.any; 017import static org.mockito.ArgumentMatchers.eq; 018import static org.mockito.ArgumentMatchers.isNull; 019import static org.mockito.ArgumentMatchers.nullable; 020import static org.mockito.Mockito.when; 021import static org.springframework.test.util.ReflectionTestUtils.setField; 022 023import java.time.Instant; 024import java.util.Arrays; 025import java.util.List; 026import java.util.UUID; 027import java.util.stream.Collectors; 028import java.util.stream.Stream; 029 030import javax.inject.Inject; 031 032import org.fcrepo.config.OcflPropsConfig; 033import org.fcrepo.kernel.api.ContainmentIndex; 034import org.fcrepo.kernel.api.RdfLexicon; 035import org.fcrepo.kernel.api.RdfStream; 036import org.fcrepo.kernel.api.ReadOnlyTransaction; 037import org.fcrepo.kernel.api.Transaction; 038import org.fcrepo.kernel.api.identifiers.FedoraId; 039import org.fcrepo.kernel.api.models.ResourceHeaders; 040import org.fcrepo.kernel.api.rdf.DefaultRdfStream; 041import org.fcrepo.kernel.api.services.MembershipService; 042import org.fcrepo.kernel.impl.TestTransactionHelper; 043import org.fcrepo.persistence.api.PersistentStorageSession; 044import org.fcrepo.persistence.api.PersistentStorageSessionManager; 045import org.fcrepo.persistence.api.exceptions.PersistentItemNotFoundException; 046import org.fcrepo.persistence.common.ResourceHeadersImpl; 047 048import org.apache.jena.graph.NodeFactory; 049import org.apache.jena.graph.Triple; 050import org.apache.jena.rdf.model.Model; 051import org.apache.jena.rdf.model.ModelFactory; 052import org.apache.jena.rdf.model.Property; 053import org.apache.jena.rdf.model.Resource; 054import org.flywaydb.test.FlywayTestExecutionListener; 055import org.flywaydb.test.annotation.FlywayTest; 056import org.junit.Before; 057import org.junit.Test; 058import org.junit.runner.RunWith; 059import org.mockito.Mock; 060import org.mockito.MockitoAnnotations; 061import org.mockito.invocation.InvocationOnMock; 062import org.mockito.stubbing.Answer; 063import org.springframework.test.context.ContextConfiguration; 064import org.springframework.test.context.TestExecutionListeners; 065import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 066import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; 067 068/** 069 * @author bbpennel 070 */ 071@RunWith(SpringJUnit4ClassRunner.class) 072@ContextConfiguration("/membershipServiceTest.xml") 073@TestExecutionListeners({DependencyInjectionTestExecutionListener.class, FlywayTestExecutionListener.class }) 074public class MembershipServiceImplTest { 075 076 private final static Instant CREATED_DATE = Instant.parse("2019-11-12T10:00:30.0Z"); 077 078 private final static String CREATED_BY = "user1"; 079 080 private final static Instant LAST_MODIFIED_DATE = Instant.parse("2019-11-12T14:11:05.0Z"); 081 082 private final static Instant LAST_MODIFIED_DATE2 = Instant.parse("2019-11-12T16:10:00.0Z"); 083 084 private final static String LAST_MODIFIED_BY = "user2"; 085 086 private final static String STATE_TOKEN = "stately_value"; 087 088 private final static Property MEMBER_OF = createProperty("http://example.com/memberOf"); 089 090 private final static Property OTHER_MEMBER_OF = createProperty("http://example.com/otherMemberOf"); 091 092 private final static Property OTHER_HAS_MEMBER = createProperty("http://example.com/anotherHasMember"); 093 094 public static final Property PROXY_FOR = createProperty("http://example.com/proxyFor"); 095 096 @Inject 097 private PersistentStorageSessionManager pSessionManager; 098 @Mock 099 private PersistentStorageSession psSession; 100 @Inject 101 private MembershipService membershipService; 102 @Inject 103 private MembershipIndexManager indexManager; 104 @Inject 105 private ContainmentIndex containmentIndex; 106 @Inject 107 private OcflPropsConfig propsConfig; 108 109 private Transaction transaction; 110 111 private Transaction shortLivedTx; 112 113 private final FedoraId rootId = FedoraId.getRepositoryRootId(); 114 115 private FedoraId membershipRescId; 116 117 private String txId; 118 private String shortLivedTxId; 119 120 private Transaction readOnlyTx; 121 122 @Before 123 @FlywayTest 124 public void setup() { 125 MockitoAnnotations.openMocks(this); 126 127 txId = UUID.randomUUID().toString(); 128 transaction = TestTransactionHelper.mockTransaction(txId, false); 129 shortLivedTxId = UUID.randomUUID().toString(); 130 shortLivedTx = TestTransactionHelper.mockTransaction(shortLivedTxId, true); 131 132 when(pSessionManager.getSession(transaction)).thenReturn(psSession); 133 when(pSessionManager.getSession(shortLivedTx)).thenReturn(psSession); 134 135 mockGetHeaders(populateHeaders(rootId, BASIC_CONTAINER)); 136 when(psSession.getTriples(any(FedoraId.class), nullable(Instant.class))).thenAnswer(new Answer<RdfStream>() { 137 @Override 138 public RdfStream answer(final InvocationOnMock invocation) throws Throwable { 139 final var fedoraId = (FedoraId) invocation.getArgument(0); 140 final var subject = NodeFactory.createURI(fedoraId.getFullId()); 141 return new DefaultRdfStream(subject, Stream.empty()); 142 } 143 }); 144 145 membershipRescId = mintFedoraId(); 146 147 setField(propsConfig, "autoVersioningEnabled", Boolean.TRUE); 148 readOnlyTx = ReadOnlyTransaction.INSTANCE; 149 } 150 151 @Test 152 public void getMembers_NoMembership() throws Exception { 153 mockGetHeaders(populateHeaders(membershipRescId, BASIC_CONTAINER)); 154 containmentIndex.addContainedBy(transaction, rootId, membershipRescId); 155 membershipService.resourceCreated(transaction, membershipRescId); 156 157 assertUncommittedMembershipCount(transaction, membershipRescId, 0); 158 159 assertNull(membershipService.getLastUpdatedTimestamp(transaction, membershipRescId)); 160 } 161 162 @Test 163 public void getMembers_WithDC_NoMembers() throws Exception { 164 mockGetHeaders(populateHeaders(membershipRescId, BASIC_CONTAINER)); 165 membershipService.resourceCreated(transaction, membershipRescId); 166 167 final var dcId = createDirectContainer(membershipRescId, RdfLexicon.LDP_MEMBER, false); 168 membershipService.resourceCreated(transaction, dcId); 169 170 assertUncommittedMembershipCount(transaction, membershipRescId, 0); 171 172 assertNull(membershipService.getLastUpdatedTimestamp(transaction, membershipRescId)); 173 } 174 175 @Test 176 public void getMembers_WithDC_AddedMembers_HasMemberRelation() throws Exception { 177 mockGetHeaders(populateHeaders(membershipRescId, BASIC_CONTAINER)); 178 membershipService.resourceCreated(transaction, membershipRescId); 179 180 final var dcId = createDirectContainer(membershipRescId, RdfLexicon.LDP_MEMBER, false); 181 membershipService.resourceCreated(transaction, dcId); 182 183 final var member1Id = createDCMember(dcId, BASIC_CONTAINER); 184 final var member2Id = createDCMember(dcId, RdfLexicon.NON_RDF_SOURCE); 185 186 assertHasMembers(transaction, membershipRescId, RdfLexicon.LDP_MEMBER, member1Id, member2Id); 187 188 final var lastUpdated = membershipService.getLastUpdatedTimestamp(transaction, membershipRescId); 189 assertNotNull(lastUpdated); 190 191 // Commit the transaction and verify we can still get the added members 192 membershipService.commitTransaction(transaction); 193 194 assertHasMembersNoTx(membershipRescId, RdfLexicon.LDP_MEMBER, member1Id, member2Id); 195 assertEquals("Last updated timestamp should not change during commit", 196 lastUpdated, membershipService.getLastUpdatedTimestamp(readOnlyTx, membershipRescId)); 197 } 198 199 @Test 200 public void getMembers_WithDC_AddedMembers_DefaultHasMemberRelation() throws Exception { 201 mockGetHeaders(populateHeaders(membershipRescId, BASIC_CONTAINER)); 202 membershipService.resourceCreated(transaction, membershipRescId); 203 204 // Don't specify a membership relation 205 final var dcId = createDirectContainer(membershipRescId, null, false); 206 membershipService.resourceCreated(transaction, dcId); 207 208 final var member1Id = createDCMember(dcId, BASIC_CONTAINER); 209 210 assertHasMembers(transaction, membershipRescId, RdfLexicon.LDP_MEMBER, member1Id); 211 } 212 213 @Test 214 public void getMembers_WithDC_AddedMembers_IsMemberOfRelation() throws Exception { 215 mockGetHeaders(populateHeaders(membershipRescId, BASIC_CONTAINER)); 216 membershipService.resourceCreated(transaction, membershipRescId); 217 218 final var dcId = createDirectContainer(membershipRescId, MEMBER_OF, true); 219 membershipService.resourceCreated(transaction, dcId); 220 221 final var member1Id = createDCMember(dcId, BASIC_CONTAINER); 222 final var member2Id = createDCMember(dcId, RdfLexicon.NON_RDF_SOURCE); 223 224 assertUncommittedMembershipCount(transaction, membershipRescId, 0); 225 assertIsMemberOf(transaction, member1Id, MEMBER_OF, membershipRescId); 226 assertIsMemberOf(transaction, member2Id, MEMBER_OF, membershipRescId); 227 228 final var member1Updated = membershipService.getLastUpdatedTimestamp(transaction, member1Id); 229 assertNotNull(member1Updated); 230 final var member2Updated = membershipService.getLastUpdatedTimestamp(transaction, member2Id); 231 assertNotNull(member2Updated); 232 assertNull("No membership expected for the membership resource", 233 membershipService.getLastUpdatedTimestamp(transaction, membershipRescId)); 234 235 // Commit the transaction and verify we can still get the added members 236 membershipService.commitTransaction(transaction); 237 238 assertCommittedMembershipCount(membershipRescId, 0); 239 240 assertHasMembersNoTx(member1Id, MEMBER_OF, membershipRescId); 241 assertHasMembersNoTx(member2Id, MEMBER_OF, membershipRescId); 242 243 assertEquals(member1Updated, membershipService.getLastUpdatedTimestamp(readOnlyTx, member1Id)); 244 assertEquals(member2Updated, membershipService.getLastUpdatedTimestamp(readOnlyTx, member2Id)); 245 } 246 247 @Test 248 public void getMembers_WithDC_BinaryAsMembershipResc() throws Exception { 249 mockGetHeaders(populateHeaders(membershipRescId, RdfLexicon.NON_RDF_SOURCE)); 250 membershipService.resourceCreated(transaction, membershipRescId); 251 252 final var dcId = createDirectContainer(membershipRescId, RdfLexicon.LDP_MEMBER, false); 253 membershipService.resourceCreated(transaction, dcId); 254 255 final var member1Id = createDCMember(dcId, BASIC_CONTAINER); 256 257 final var descId = membershipRescId.asDescription(); 258 259 assertHasMembers(transaction, descId, RdfLexicon.LDP_MEMBER, member1Id); 260 261 membershipService.commitTransaction(transaction); 262 263 assertHasMembersNoTx(descId, RdfLexicon.LDP_MEMBER, member1Id); 264 } 265 266 @Test 267 public void deleteMember_InDC_AddedInSameTx_HasMemberRelation() throws Exception { 268 mockGetHeaders(populateHeaders(membershipRescId, BASIC_CONTAINER)); 269 membershipService.resourceCreated(transaction, membershipRescId); 270 271 final var dcId = createDirectContainer(membershipRescId, RdfLexicon.LDP_MEMBER, false); 272 membershipService.resourceCreated(transaction, dcId); 273 274 final var member1Id = createDCMember(dcId, BASIC_CONTAINER); 275 276 assertUncommittedMembershipCount(transaction, membershipRescId, 1); 277 278 assertNotNull(membershipService.getLastUpdatedTimestamp(transaction, membershipRescId)); 279 280 mockDeleteHeaders(member1Id, dcId, BASIC_CONTAINER); 281 // Notify that the member was deleted 282 membershipService.resourceDeleted(transaction, member1Id); 283 284 assertUncommittedMembershipCount(transaction, membershipRescId, 0); 285 assertCommittedMembershipCount(membershipRescId, 0); 286 287 assertNull(membershipService.getLastUpdatedTimestamp(transaction, membershipRescId)); 288 289 membershipService.commitTransaction(transaction); 290 291 assertCommittedMembershipCount(membershipRescId, 0); 292 293 assertNull(membershipService.getLastUpdatedTimestamp(readOnlyTx, membershipRescId)); 294 } 295 296 @Test 297 public void deleteExistingMember_InDC_HasMemberRelation() throws Exception { 298 mockGetHeaders(populateHeaders(membershipRescId, BASIC_CONTAINER)); 299 membershipService.resourceCreated(transaction, membershipRescId); 300 301 final var dcId = createDirectContainer(membershipRescId, RdfLexicon.LDP_MEMBER, false); 302 membershipService.resourceCreated(transaction, dcId); 303 304 final var member1Id = createDCMember(dcId, BASIC_CONTAINER); 305 306 membershipService.commitTransaction(transaction); 307 308 assertCommittedMembershipCount(membershipRescId, 1); 309 310 final var lastUpdated = membershipService.getLastUpdatedTimestamp(readOnlyTx, membershipRescId); 311 assertNotNull(lastUpdated); 312 313 mockDeleteHeaders(member1Id, dcId, BASIC_CONTAINER); 314 // Notify that the member was deleted 315 membershipService.resourceDeleted(transaction, member1Id); 316 317 assertUncommittedMembershipCount(transaction, membershipRescId, 0); 318 assertCommittedMembershipCount(membershipRescId, 1); 319 320 membershipService.commitTransaction(transaction); 321 322 assertCommittedMembershipCount(membershipRescId, 0); 323 324 final var afterDeleteUpdated = membershipService.getLastUpdatedTimestamp(readOnlyTx, membershipRescId); 325 assertNotNull(afterDeleteUpdated); 326 assertNotEquals(lastUpdated, afterDeleteUpdated); 327 } 328 329 @Test 330 public void deleteExistingMember_InDC_MultipleMembers_HasMemberRelation() throws Exception { 331 mockGetHeaders(populateHeaders(membershipRescId, BASIC_CONTAINER)); 332 membershipService.resourceCreated(transaction, membershipRescId); 333 334 final var dcId = createDirectContainer(membershipRescId, RdfLexicon.LDP_MEMBER, false); 335 membershipService.resourceCreated(transaction, dcId); 336 337 final var member1Id = createDCMember(dcId, BASIC_CONTAINER); 338 final var member2Id = createDCMember(dcId, BASIC_CONTAINER); 339 340 membershipService.commitTransaction(transaction); 341 342 assertCommittedMembershipCount(membershipRescId, 2); 343 344 mockDeleteHeaders(member1Id, dcId, BASIC_CONTAINER); 345 // Notify that the member was deleted 346 membershipService.resourceDeleted(transaction, member1Id); 347 348 assertUncommittedMembershipCount(transaction, membershipRescId, 1); 349 assertCommittedMembershipCount(membershipRescId, 2); 350 351 membershipService.commitTransaction(transaction); 352 353 assertHasMembersNoTx(membershipRescId, RdfLexicon.LDP_MEMBER, member2Id); 354 } 355 356 @Test 357 public void deleteExistingMember_InDC_IsMemberOfRelation() throws Exception { 358 mockGetHeaders(populateHeaders(membershipRescId, BASIC_CONTAINER)); 359 membershipService.resourceCreated(transaction, membershipRescId); 360 361 final var dcId = createDirectContainer(membershipRescId, MEMBER_OF, true); 362 membershipService.resourceCreated(transaction, dcId); 363 364 final var member1Id = createDCMember(dcId, BASIC_CONTAINER); 365 366 membershipService.commitTransaction(transaction); 367 368 assertCommittedMembershipCount(member1Id, 1); 369 370 mockDeleteHeaders(member1Id, dcId, BASIC_CONTAINER); 371 // Notify that the member was deleted 372 membershipService.resourceDeleted(transaction, member1Id); 373 374 assertCommittedMembershipCount(member1Id, 1); 375 assertUncommittedMembershipCount(transaction, member1Id, 0); 376 377 membershipService.commitTransaction(transaction); 378 379 assertCommittedMembershipCount(member1Id, 0); 380 } 381 382 @Test 383 public void deleteDC_WithMember_CreatedInSameTx() throws Exception { 384 mockGetHeaders(populateHeaders(membershipRescId, BASIC_CONTAINER)); 385 membershipService.resourceCreated(transaction, membershipRescId); 386 387 final var dcId = createDirectContainer(membershipRescId, RdfLexicon.LDP_MEMBER, false); 388 membershipService.resourceCreated(transaction, dcId); 389 390 createDCMember(dcId, BASIC_CONTAINER); 391 392 assertCommittedMembershipCount(membershipRescId, 0); 393 assertUncommittedMembershipCount(transaction, membershipRescId, 1); 394 395 mockDeleteHeaders(dcId, rootId, RdfLexicon.DIRECT_CONTAINER); 396 397 // Notify that the DC was deleted 398 membershipService.resourceDeleted(transaction, dcId); 399 400 assertUncommittedMembershipCount(transaction, membershipRescId, 0); 401 assertCommittedMembershipCount(membershipRescId, 0); 402 403 membershipService.commitTransaction(transaction); 404 405 assertCommittedMembershipCount(membershipRescId, 0); 406 } 407 408 @Test 409 public void deleteExistingDC_WithExistingMember() throws Exception { 410 mockGetHeaders(populateHeaders(membershipRescId, BASIC_CONTAINER)); 411 membershipService.resourceCreated(transaction, membershipRescId); 412 413 final var dcId = createDirectContainer(membershipRescId, RdfLexicon.LDP_MEMBER, false); 414 membershipService.resourceCreated(transaction, dcId); 415 416 createDCMember(dcId, BASIC_CONTAINER); 417 418 membershipService.commitTransaction(transaction); 419 420 assertCommittedMembershipCount(membershipRescId, 1); 421 422 mockDeleteHeaders(dcId, rootId, RdfLexicon.DIRECT_CONTAINER); 423 // Notify that the DC was deleted 424 membershipService.resourceDeleted(transaction, dcId); 425 426 assertUncommittedMembershipCount(transaction, membershipRescId, 0); 427 assertCommittedMembershipCount(membershipRescId, 1); 428 429 membershipService.commitTransaction(transaction); 430 431 assertCommittedMembershipCount(membershipRescId, 0); 432 } 433 434 @Test 435 public void deleteExistingMemberAndDC_InSameTx() throws Exception { 436 mockGetHeaders(populateHeaders(membershipRescId, BASIC_CONTAINER)); 437 membershipService.resourceCreated(transaction, membershipRescId); 438 439 final var dcId = createDirectContainer(membershipRescId, RdfLexicon.LDP_MEMBER, false); 440 membershipService.resourceCreated(transaction, dcId); 441 442 final var member1Id = createDCMember(dcId, BASIC_CONTAINER); 443 444 membershipService.commitTransaction(transaction); 445 446 mockDeleteHeaders(dcId, rootId, RdfLexicon.DIRECT_CONTAINER); 447 mockDeleteHeaders(member1Id, dcId, BASIC_CONTAINER); 448 // Delete the member 449 membershipService.resourceDeleted(transaction, member1Id); 450 // Delete the DC itself 451 membershipService.resourceDeleted(transaction, dcId); 452 453 assertCommittedMembershipCount(membershipRescId, 1); 454 assertUncommittedMembershipCount(transaction, membershipRescId, 0); 455 456 membershipService.commitTransaction(transaction); 457 458 assertCommittedMembershipCount(membershipRescId, 0); 459 } 460 461 @Test 462 public void purgeDC() throws Exception { 463 mockGetHeaders(populateHeaders(membershipRescId, BASIC_CONTAINER)); 464 membershipService.resourceCreated(transaction, membershipRescId); 465 466 final var dcId = createDirectContainer(membershipRescId, RdfLexicon.LDP_MEMBER, false); 467 membershipService.resourceCreated(transaction, dcId); 468 469 createDCMember(dcId, BASIC_CONTAINER); 470 471 membershipService.commitTransaction(transaction); 472 473 assertCommittedMembershipCount(membershipRescId, 1); 474 475 when(psSession.getHeaders(eq(dcId), nullable(Instant.class))).thenThrow( 476 new PersistentItemNotFoundException("")); 477 478 membershipService.resourceDeleted(transaction, dcId); 479 480 assertCommittedMembershipCount(membershipRescId, 0); 481 assertUncommittedMembershipCount(transaction, membershipRescId, 0); 482 } 483 484 @Test 485 public void purgeMember() throws Exception { 486 mockGetHeaders(populateHeaders(membershipRescId, BASIC_CONTAINER)); 487 membershipService.resourceCreated(transaction, membershipRescId); 488 489 final var dcId = createDirectContainer(membershipRescId, RdfLexicon.LDP_MEMBER, false); 490 membershipService.resourceCreated(transaction, dcId); 491 492 final var member1Id = createDCMember(dcId, BASIC_CONTAINER); 493 final var member2Id = createDCMember(dcId, BASIC_CONTAINER); 494 495 membershipService.commitTransaction(transaction); 496 497 assertCommittedMembershipCount(membershipRescId, 2); 498 499 final var lastUpdated = membershipService.getLastUpdatedTimestamp(readOnlyTx, membershipRescId); 500 assertNotNull(lastUpdated); 501 502 when(psSession.getHeaders(eq(member1Id), nullable(Instant.class))).thenThrow( 503 new PersistentItemNotFoundException("")); 504 505 membershipService.resourceDeleted(transaction, member1Id); 506 507 assertHasMembersNoTx(membershipRescId, RdfLexicon.LDP_MEMBER, member2Id); 508 assertUncommittedMembershipCount(transaction, membershipRescId, 1); 509 510 assertEquals(lastUpdated, membershipService.getLastUpdatedTimestamp(readOnlyTx, membershipRescId)); 511 } 512 513 @Test 514 public void purgeMembershipResource_isMemberOfRelation() throws Exception { 515 mockGetHeaders(populateHeaders(membershipRescId, BASIC_CONTAINER)); 516 membershipService.resourceCreated(transaction, membershipRescId); 517 518 final var dcId = createDirectContainer(membershipRescId, MEMBER_OF, true); 519 membershipService.resourceCreated(transaction, dcId); 520 521 final var member1Id = createDCMember(dcId, BASIC_CONTAINER); 522 523 membershipService.commitTransaction(transaction); 524 525 assertCommittedMembershipCount(member1Id, 1); 526 527 when(psSession.getHeaders(eq(membershipRescId), nullable(Instant.class))).thenThrow( 528 new PersistentItemNotFoundException("")); 529 530 membershipService.resourceDeleted(transaction, membershipRescId); 531 532 assertCommittedMembershipCount(member1Id, 0); 533 assertUncommittedMembershipCount(transaction, member1Id, 0); 534 } 535 536 @Test 537 public void recreateExistingMember_InDC_HasMemberRelation() throws Exception { 538 mockGetHeaders(populateHeaders(membershipRescId, BASIC_CONTAINER)); 539 membershipService.resourceCreated(transaction, membershipRescId); 540 541 final var dcId = createDirectContainer(membershipRescId, RdfLexicon.LDP_MEMBER, false); 542 membershipService.resourceCreated(transaction, dcId); 543 544 final var member1Id = createDCMember(dcId, BASIC_CONTAINER); 545 546 membershipService.commitTransaction(transaction); 547 548 assertCommittedMembershipCount(membershipRescId, 1); 549 550 // Notify that the member was deleted 551 membershipService.resourceDeleted(transaction, member1Id); 552 553 assertUncommittedMembershipCount(transaction, membershipRescId, 0); 554 assertCommittedMembershipCount(membershipRescId, 1); 555 556 // Recreate the resource in the same TX 557 membershipService.resourceCreated(transaction, member1Id); 558 559 assertUncommittedMembershipCount(transaction, membershipRescId, 1); 560 assertCommittedMembershipCount(membershipRescId, 1); 561 562 membershipService.commitTransaction(transaction); 563 564 assertCommittedMembershipCount(membershipRescId, 1); 565 } 566 567 @Test 568 public void getMembers_MultipleDCsSameMembershipResource_HasMemberRelation() throws Exception { 569 mockGetHeaders(populateHeaders(membershipRescId, BASIC_CONTAINER)); 570 membershipService.resourceCreated(transaction, membershipRescId); 571 572 final var dc1Id = createDirectContainer(membershipRescId, RdfLexicon.LDP_MEMBER, false); 573 membershipService.resourceCreated(transaction, dc1Id); 574 575 // Add a child to the outer DC 576 final var member1Id = createDCMember(dc1Id, BASIC_CONTAINER); 577 578 final var dc2Id = createDirectContainer(membershipRescId, RdfLexicon.LDP_MEMBER, false); 579 membershipService.resourceCreated(transaction, dc2Id); 580 581 // Add a child to the outer DC 582 final var member2Id = createDCMember(dc2Id, BASIC_CONTAINER); 583 584 assertUncommittedMembershipCount(transaction, membershipRescId, 2); 585 586 membershipService.commitTransaction(transaction); 587 588 assertHasMembersNoTx(membershipRescId, RdfLexicon.LDP_MEMBER, member1Id, member2Id); 589 590 // Delete one to ensure only those members are cleaned up 591 membershipService.resourceDeleted(transaction, member2Id); 592 593 membershipService.commitTransaction(transaction); 594 595 assertHasMembersNoTx(membershipRescId, RdfLexicon.LDP_MEMBER, member1Id); 596 } 597 598 @Test 599 public void getMembers_MultipleDCsSameMembershipResource_IsMemberOfRelation() throws Exception { 600 mockGetHeaders(populateHeaders(membershipRescId, BASIC_CONTAINER)); 601 membershipService.resourceCreated(transaction, membershipRescId); 602 603 final var dc1Id = createDirectContainer(membershipRescId, MEMBER_OF, true); 604 membershipService.resourceCreated(transaction, dc1Id); 605 606 // Add a child to the outer DC 607 final var member1Id = createDCMember(dc1Id, BASIC_CONTAINER); 608 609 final var dc2Id = createDirectContainer(membershipRescId, MEMBER_OF, true); 610 membershipService.resourceCreated(transaction, dc2Id); 611 612 // Add a child to the outer DC 613 final var member2Id = createDCMember(dc2Id, BASIC_CONTAINER); 614 615 assertUncommittedMembershipCount(transaction, membershipRescId, 0); 616 assertUncommittedMembershipCount(transaction, member1Id, 1); 617 assertUncommittedMembershipCount(transaction, member2Id, 1); 618 619 membershipService.commitTransaction(transaction); 620 621 assertIsMemberOfNoTx(member1Id, MEMBER_OF, membershipRescId); 622 assertIsMemberOfNoTx(member2Id, MEMBER_OF, membershipRescId); 623 624 // Delete one to ensure only those members are cleaned up 625 membershipService.resourceDeleted(transaction, member2Id); 626 627 membershipService.commitTransaction(transaction); 628 629 assertIsMemberOfNoTx(member1Id, MEMBER_OF, membershipRescId); 630 assertCommittedMembershipCount(member2Id, 0); 631 } 632 633 @Test 634 public void getMembers_DCmemberOfDC() throws Exception { 635 mockGetHeaders(populateHeaders(membershipRescId, BASIC_CONTAINER)); 636 membershipService.resourceCreated(transaction, membershipRescId); 637 638 final var dcId = createDirectContainer(membershipRescId, RdfLexicon.LDP_MEMBER, false); 639 membershipService.resourceCreated(transaction, dcId); 640 641 // Add a child to the outer DC 642 final var outerMemberId = createDCMember(dcId, BASIC_CONTAINER); 643 644 // Add a DC as the child of the first DC 645 final var nestedDcId = createDirectContainer(dcId, membershipRescId, RdfLexicon.LDP_MEMBER, false); 646 membershipService.resourceCreated(transaction, nestedDcId); 647 648 // Add a child to the nested DC 649 final var nestedMemberId = createDCMember(nestedDcId, BASIC_CONTAINER); 650 651 assertCommittedMembershipCount(membershipRescId, 0); 652 assertUncommittedMembershipCount(transaction, membershipRescId, 3); 653 654 membershipService.commitTransaction(transaction); 655 656 assertHasMembersNoTx(membershipRescId, RdfLexicon.LDP_MEMBER, outerMemberId, nestedDcId, nestedMemberId); 657 658 // Delete the nested DC to ensure that it gets cleaned up as both a DC and a member 659 membershipService.resourceDeleted(transaction, nestedDcId); 660 661 membershipService.commitTransaction(transaction); 662 663 assertHasMembersNoTx(membershipRescId, RdfLexicon.LDP_MEMBER, outerMemberId); 664 } 665 666 @Test 667 public void changeMembershipResource_ForDC() throws Exception { 668 mockGetHeaders(populateHeaders(membershipRescId, BASIC_CONTAINER)); 669 membershipService.resourceCreated(transaction, membershipRescId); 670 671 final var membershipResc2Id = mintFedoraId(); 672 mockGetHeaders(populateHeaders(membershipResc2Id, BASIC_CONTAINER)); 673 membershipService.resourceCreated(transaction, membershipResc2Id); 674 675 final var dcId = createDirectContainer(membershipRescId, RdfLexicon.LDP_MEMBER, false); 676 membershipService.resourceCreated(transaction, dcId); 677 678 final var member1Id = createDCMember(dcId, BASIC_CONTAINER); 679 final var member2Id = createDCMember(dcId, BASIC_CONTAINER); 680 681 membershipService.commitTransaction(transaction); 682 683 assertCommittedMembershipCount(membershipRescId, 2); 684 assertCommittedMembershipCount(membershipResc2Id, 0); 685 686 final var msRescUpdated = membershipService.getLastUpdatedTimestamp(readOnlyTx, membershipRescId); 687 assertNotNull(msRescUpdated); 688 assertNull(membershipService.getLastUpdatedTimestamp(readOnlyTx, membershipResc2Id)); 689 690 // Change the membership resource for the DC 691 mockGetTriplesForDC(dcId, LAST_MODIFIED_DATE, membershipResc2Id, RdfLexicon.LDP_MEMBER, false); 692 membershipService.resourceModified(transaction, dcId); 693 694 assertHasMembers(transaction, membershipResc2Id, RdfLexicon.LDP_MEMBER, member1Id, member2Id); 695 696 membershipService.commitTransaction(transaction); 697 698 assertCommittedMembershipCount(membershipRescId, 0); 699 assertHasMembersNoTx(membershipResc2Id, RdfLexicon.LDP_MEMBER, member1Id, member2Id); 700 701 final var msRescUpdatedAfter = membershipService.getLastUpdatedTimestamp(readOnlyTx, membershipRescId); 702 assertNotNull(msRescUpdatedAfter); 703 assertNotEquals("First membership resc should have changed last_updated timestamp", 704 msRescUpdated, msRescUpdatedAfter); 705 assertNotNull(membershipService.getLastUpdatedTimestamp(readOnlyTx, membershipResc2Id)); 706 } 707 708 @Test 709 public void changeMembershipResource_ForDC_ManualVersioning() throws Exception { 710 setField(propsConfig, "autoVersioningEnabled", Boolean.FALSE); 711 712 mockGetHeaders(populateHeaders(membershipRescId, BASIC_CONTAINER)); 713 membershipService.resourceCreated(transaction, membershipRescId); 714 715 final var membershipResc2Id = mintFedoraId(); 716 mockGetHeaders(populateHeaders(membershipResc2Id, BASIC_CONTAINER)); 717 membershipService.resourceCreated(transaction, membershipResc2Id); 718 719 final var dcId = createDirectContainer(membershipRescId, RdfLexicon.LDP_MEMBER, false); 720 membershipService.resourceCreated(transaction, dcId); 721 722 final var member1Id = createDCMember(dcId, BASIC_CONTAINER); 723 724 membershipService.commitTransaction(transaction); 725 726 assertCommittedMembershipCount(membershipRescId, 1); 727 assertCommittedMembershipCount(membershipResc2Id, 0); 728 729 // Change the membership resource for the DC without creating a version 730 mockListVersion(dcId); 731 mockGetTriplesForDCHead(dcId, CREATED_DATE, membershipResc2Id, RdfLexicon.LDP_MEMBER, false); 732 membershipService.resourceModified(transaction, dcId); 733 734 assertHasMembersNoTx(membershipRescId, RdfLexicon.LDP_MEMBER, member1Id); 735 assertHasMembers(transaction, membershipResc2Id, RdfLexicon.LDP_MEMBER, member1Id); 736 737 membershipService.commitTransaction(transaction); 738 739 assertCommittedMembershipCount(membershipRescId, 0); 740 assertHasMembersNoTx(membershipResc2Id, RdfLexicon.LDP_MEMBER, member1Id); 741 742 // Change membership property without versioning 743 mockGetTriplesForDCHead(dcId, CREATED_DATE, membershipResc2Id, OTHER_HAS_MEMBER, false); 744 membershipService.resourceModified(transaction, dcId); 745 746 assertHasMembersNoTx(membershipResc2Id, RdfLexicon.LDP_MEMBER, member1Id); 747 assertHasMembers(transaction, membershipResc2Id, OTHER_HAS_MEMBER, member1Id); 748 749 membershipService.commitTransaction(transaction); 750 751 assertCommittedMembershipCount(membershipRescId, 0); 752 assertHasMembersNoTx(membershipResc2Id, OTHER_HAS_MEMBER, member1Id); 753 754 // Create version from former head version 755 final var versionChangeTime = Instant.parse("2019-11-13T12:00:00.0Z"); 756 mockListVersion(dcId, versionChangeTime); 757 // New head state matches previous head state for the moment 758 mockGetTriplesForDC(dcId, versionChangeTime, membershipResc2Id, OTHER_HAS_MEMBER, false); 759 mockGetHeaders(transaction, dcId.asMemento(versionChangeTime), populateHeaders(dcId, rootId, 760 RdfLexicon.DIRECT_CONTAINER, CREATED_DATE, versionChangeTime), rootId); 761 762 // Change membership resource after having created version 763 final var afterVersionChangeTime = Instant.parse("2019-11-13T14:00:00.0Z"); 764 mockGetHeaders(transaction, dcId, populateHeaders(dcId, rootId, 765 RdfLexicon.DIRECT_CONTAINER, CREATED_DATE, afterVersionChangeTime), rootId); 766 mockGetTriplesForDCHead(dcId, afterVersionChangeTime, membershipRescId, OTHER_HAS_MEMBER, false); 767 membershipService.resourceModified(transaction, dcId); 768 769 // Membership resc 2 should still have a member prior to the version creation/last property update 770 assertHasMembers(transaction, membershipResc2Id.asMemento(CREATED_DATE), OTHER_HAS_MEMBER, 771 member1Id); 772 assertUncommittedMembershipCount(transaction, membershipResc2Id, 0); 773 assertHasMembersNoTx(membershipResc2Id, OTHER_HAS_MEMBER, member1Id); 774 assertHasMembers(transaction, membershipRescId, OTHER_HAS_MEMBER, member1Id); 775 776 membershipService.commitTransaction(transaction); 777 778 assertCommittedMembershipCount(membershipResc2Id, 0); 779 assertHasMembersNoTx(membershipResc2Id.asMemento(CREATED_DATE), OTHER_HAS_MEMBER, 780 member1Id); 781 assertCommittedMembershipCount(membershipRescId.asMemento(CREATED_DATE), 0); 782 assertHasMembersNoTx(membershipRescId, OTHER_HAS_MEMBER, member1Id); 783 } 784 785 @Test 786 public void changeMembershipRelation_DC_HasMember() throws Exception { 787 mockGetHeaders(populateHeaders(membershipRescId, BASIC_CONTAINER)); 788 membershipService.resourceCreated(transaction, membershipRescId); 789 790 final var dcId = createDirectContainer(membershipRescId, RdfLexicon.LDP_MEMBER, false); 791 membershipService.resourceCreated(transaction, dcId); 792 793 final var member1Id = createDCMember(dcId, BASIC_CONTAINER); 794 final var member2Id = createDCMember(dcId, BASIC_CONTAINER); 795 796 assertUncommittedMembershipCount(transaction, membershipRescId, 2); 797 798 membershipService.commitTransaction(transaction); 799 800 assertHasMembersNoTx(membershipRescId, RdfLexicon.LDP_MEMBER, member1Id, member2Id); 801 802 // Change the membership relation 803 mockGetTriplesForDC(dcId, LAST_MODIFIED_DATE, membershipRescId, OTHER_HAS_MEMBER, false); 804 membershipService.resourceModified(transaction, dcId); 805 806 assertHasMembers(transaction, membershipRescId, OTHER_HAS_MEMBER, member1Id, member2Id); 807 808 membershipService.commitTransaction(transaction); 809 810 assertHasMembersNoTx(membershipRescId, OTHER_HAS_MEMBER, member1Id, member2Id); 811 } 812 813 @Test 814 public void changeResource_DC_HasMemberToIsMemberOf() throws Exception { 815 mockGetHeaders(populateHeaders(membershipRescId, BASIC_CONTAINER)); 816 membershipService.resourceCreated(transaction, membershipRescId); 817 818 final var dcId = createDirectContainer(membershipRescId, RdfLexicon.LDP_MEMBER, false); 819 membershipService.resourceCreated(transaction, dcId); 820 821 final var member1Id = createDCMember(dcId, BASIC_CONTAINER); 822 823 assertUncommittedMembershipCount(transaction, membershipRescId, 1); 824 825 membershipService.commitTransaction(transaction); 826 827 assertHasMembers(readOnlyTx, membershipRescId, RdfLexicon.LDP_MEMBER, member1Id); 828 829 final var msRescUpdated1 = indexManager.getLastUpdated(readOnlyTx, membershipRescId); 830 assertNotNull(msRescUpdated1); 831 assertNull(indexManager.getLastUpdated(readOnlyTx, member1Id)); 832 833 // Change the membership direction from a ldp:hasMemberRelation to a ldp:isMemberOfRelation 834 mockGetTriplesForDC(dcId, LAST_MODIFIED_DATE, membershipRescId, MEMBER_OF, true); 835 membershipService.resourceModified(transaction, dcId); 836 837 assertCommittedMembershipCount(membershipRescId, 1); 838 assertUncommittedMembershipCount(transaction, membershipRescId, 0); 839 assertIsMemberOf(transaction, member1Id, MEMBER_OF, membershipRescId); 840 841 membershipService.commitTransaction(transaction); 842 843 assertIsMemberOfNoTx(member1Id, MEMBER_OF, membershipRescId); 844 845 final var memRescUpdated1 = indexManager.getLastUpdated(readOnlyTx, member1Id); 846 assertNotNull(memRescUpdated1); 847 final var msRescUpdated2 = indexManager.getLastUpdated(readOnlyTx, membershipRescId); 848 assertNotNull(msRescUpdated2); 849 assertNotEquals(msRescUpdated1, msRescUpdated2); 850 851 // Reverse the membership direction again 852 mockGetTriplesForDC(dcId, LAST_MODIFIED_DATE2, membershipRescId, OTHER_HAS_MEMBER, false); 853 mockGetHeaders(transaction, dcId, rootId, RdfLexicon.DIRECT_CONTAINER, CREATED_DATE, LAST_MODIFIED_DATE2); 854 membershipService.resourceModified(transaction, dcId); 855 856 assertCommittedMembershipCount(member1Id, 1); 857 assertUncommittedMembershipCount(transaction, member1Id, 0); 858 assertHasMembers(transaction, membershipRescId, OTHER_HAS_MEMBER, member1Id); 859 860 membershipService.commitTransaction(transaction); 861 862 assertCommittedMembershipCount(member1Id, 0); 863 assertHasMembersNoTx(membershipRescId, OTHER_HAS_MEMBER, member1Id); 864 865 final var memRescUpdated2 = indexManager.getLastUpdated(readOnlyTx, member1Id); 866 assertNotNull(memRescUpdated2); 867 assertNotEquals(memRescUpdated1, memRescUpdated2); 868 final var msRescUpdated3 = indexManager.getLastUpdated(readOnlyTx, membershipRescId); 869 assertNotNull(msRescUpdated3); 870 assertNotEquals(msRescUpdated2, msRescUpdated3); 871 } 872 873 @Test 874 public void changeResource_DC_IsMemberOf() throws Exception { 875 mockGetHeaders(populateHeaders(membershipRescId, BASIC_CONTAINER)); 876 membershipService.resourceCreated(transaction, membershipRescId); 877 878 final var dcId = createDirectContainer(membershipRescId, MEMBER_OF, true); 879 membershipService.resourceCreated(transaction, dcId); 880 881 final var member1Id = createDCMember(dcId, BASIC_CONTAINER); 882 883 membershipService.commitTransaction(transaction); 884 885 assertIsMemberOfNoTx(member1Id, MEMBER_OF, membershipRescId); 886 887 // Switch DC to a different ldp:isMemberOfRelation 888 mockGetTriplesForDC(dcId, LAST_MODIFIED_DATE, membershipRescId, OTHER_MEMBER_OF, true); 889 membershipService.resourceModified(transaction, dcId); 890 891 assertIsMemberOf(transaction, member1Id, OTHER_MEMBER_OF, membershipRescId); 892 893 membershipService.commitTransaction(transaction); 894 895 assertIsMemberOfNoTx(member1Id, OTHER_MEMBER_OF, membershipRescId); 896 897 // Switch back again 898 mockGetTriplesForDC(dcId, LAST_MODIFIED_DATE, membershipRescId, MEMBER_OF, true); 899 membershipService.resourceModified(transaction, dcId); 900 901 assertIsMemberOf(transaction, member1Id, MEMBER_OF, membershipRescId); 902 903 membershipService.commitTransaction(transaction); 904 905 assertIsMemberOfNoTx(member1Id, MEMBER_OF, membershipRescId); 906 } 907 908 @Test 909 public void getMementoMembership_AllCreatedAtSameTime_NoChanges() throws Exception { 910 mockGetHeaders(populateHeaders(membershipRescId, BASIC_CONTAINER)); 911 membershipService.resourceCreated(transaction, membershipRescId); 912 913 final var dcId = createDirectContainer(membershipRescId, RdfLexicon.LDP_MEMBER, false); 914 membershipService.resourceCreated(transaction, dcId); 915 916 final var member1Id = createDCMember(dcId, BASIC_CONTAINER); 917 918 final var beforeCreated = Instant.parse("2019-11-10T00:00:00.0Z"); 919 final var beforeCreatedId = membershipRescId.asMemento(beforeCreated); 920 921 final var afterLastModified = Instant.parse("2019-12-10T00:00:00.0Z"); 922 final var afterLastModifiedId = membershipRescId.asMemento(afterLastModified); 923 924 // No membership before creation time 925 assertUncommittedMembershipCount(transaction, beforeCreatedId, 0); 926 assertUncommittedMembershipCount(transaction, membershipRescId, 1); 927 assertUncommittedMembershipCount(transaction, afterLastModifiedId, 1); 928 929 membershipService.commitTransaction(transaction); 930 931 assertCommittedMembershipCount(beforeCreatedId, 0); 932 assertHasMembersNoTx(membershipRescId, RdfLexicon.LDP_MEMBER, member1Id); 933 assertCommittedMembershipCount(afterLastModifiedId, 1); 934 } 935 936 @Test 937 public void getMementoMembership_OneMembershipAddition_hasMemberRelation() throws Exception { 938 mockGetHeaders(populateHeaders(membershipRescId, BASIC_CONTAINER)); 939 membershipService.resourceCreated(transaction, membershipRescId); 940 941 final var headMementoId = membershipRescId.asMemento(LAST_MODIFIED_DATE); 942 943 final var beforeAddMementoInstant = Instant.parse("2019-11-12T12:00:00.0Z"); 944 final var beforeAddMementoId = membershipRescId.asMemento(beforeAddMementoInstant); 945 mockGetHeaders(populateHeaders(membershipRescId, rootId, BASIC_CONTAINER, CREATED_DATE, 946 beforeAddMementoInstant)); 947 948 final var dcId = createDirectContainer(membershipRescId, RdfLexicon.LDP_MEMBER, false); 949 membershipService.resourceCreated(transaction, dcId); 950 951 final var memberCreated = Instant.parse("2019-11-12T13:00:00.0Z"); 952 final var member1Id = createDCMember(dcId, BASIC_CONTAINER, memberCreated); 953 954 // No membership at first memento timestamp 955 assertUncommittedMembershipCount(transaction, beforeAddMementoId, 0); 956 assertUncommittedMembershipCount(transaction, membershipRescId, 1); 957 958 membershipService.commitTransaction(transaction); 959 960 // No membership at first memento timestamp 961 assertCommittedMembershipCount(beforeAddMementoId, 0); 962 assertHasMembersNoTx(membershipRescId, RdfLexicon.LDP_MEMBER, member1Id); 963 // Memento request for the head version should return the membership 964 assertHasMembersNoTx(headMementoId, RdfLexicon.LDP_MEMBER, member1Id); 965 // Request at the exact time of the membership addition should return member 966 final var atAddMementoId = membershipRescId.asMemento(memberCreated); 967 assertHasMembersNoTx(atAddMementoId, RdfLexicon.LDP_MEMBER, member1Id); 968 } 969 970 @Test 971 public void getMementoMembership_AddAndDelete_isMemberOfRelation() throws Exception { 972 mockGetHeaders(populateHeaders(membershipRescId, BASIC_CONTAINER)); 973 membershipService.resourceCreated(transaction, membershipRescId); 974 975 final var dcId = createDirectContainer(membershipRescId, MEMBER_OF, true); 976 membershipService.resourceCreated(transaction, dcId); 977 978 final var memberCreated = Instant.parse("2019-11-12T13:00:00.0Z"); 979 final var member1Id = createDCMember(dcId, BASIC_CONTAINER, memberCreated); 980 final var memberCreatedId = member1Id.asMemento(memberCreated); 981 final var beforeCreate = Instant.parse("2019-11-10T00:00:00.0Z"); 982 final var beforeCreateId = member1Id.asMemento(beforeCreate); 983 984 assertUncommittedMembershipCount(transaction, beforeCreateId, 0); 985 assertUncommittedMembershipCount(transaction, member1Id, 1); 986 987 membershipService.commitTransaction(transaction); 988 989 // No membership before member created 990 assertCommittedMembershipCount(beforeCreateId, 0); 991 assertIsMemberOfNoTx(member1Id, MEMBER_OF, membershipRescId); 992 // Explicitly request memento of head 993 assertIsMemberOfNoTx(memberCreatedId, MEMBER_OF, membershipRescId); 994 // Request memento after last modified 995 final var afterModified = Instant.parse("2019-12-10T00:00:00.0Z"); 996 final var afterModifiedId = member1Id.asMemento(afterModified); 997 assertIsMemberOfNoTx(afterModifiedId, MEMBER_OF, membershipRescId); 998 999 final var deleteInstant = Instant.parse("2019-11-13T12:00:00.0Z"); 1000 final var deletedMemberId = member1Id.asMemento(deleteInstant); 1001 mockDeleteHeaders(member1Id, dcId, BASIC_CONTAINER, memberCreated, deleteInstant); 1002 1003 final var afterDeleteMemberId = member1Id.asMemento(Instant.parse("2019-11-13T16:00:00.0Z")); 1004 1005 membershipService.resourceDeleted(transaction, member1Id); 1006 1007 // Make sure delete hasn't leaked 1008 assertCommittedMembershipCount(member1Id, 1); 1009 1010 assertIsMemberOf(transaction, memberCreatedId, MEMBER_OF, membershipRescId); 1011 assertUncommittedMembershipCount(transaction, deletedMemberId, 0); 1012 assertUncommittedMembershipCount(transaction, afterDeleteMemberId, 0); 1013 assertUncommittedMembershipCount(transaction, member1Id, 0); 1014 1015 membershipService.commitTransaction(transaction); 1016 1017 assertCommittedMembershipCount(beforeCreateId, 0); 1018 assertIsMemberOfNoTx(memberCreatedId, MEMBER_OF, membershipRescId); 1019 assertCommittedMembershipCount(deletedMemberId, 0); 1020 assertCommittedMembershipCount(afterDeleteMemberId, 0); 1021 assertCommittedMembershipCount(member1Id, 0); 1022 } 1023 1024 @Test 1025 public void getMementoMembership_AddAndDelete_hasMemberRelation() throws Exception { 1026 mockGetHeaders(populateHeaders(membershipRescId, BASIC_CONTAINER)); 1027 membershipService.resourceCreated(transaction, membershipRescId); 1028 1029 final var beforeAddMementoInstant = Instant.parse("2019-11-12T12:00:00.0Z"); 1030 final var beforeAddMementoId = membershipRescId.asMemento(beforeAddMementoInstant); 1031 1032 final var dcId = createDirectContainer(membershipRescId, RdfLexicon.LDP_MEMBER, false); 1033 membershipService.resourceCreated(transaction, dcId); 1034 1035 final var member1Created = Instant.parse("2019-11-12T13:00:00.0Z"); 1036 final var member1Id = createDCMember(dcId, BASIC_CONTAINER, member1Created); 1037 1038 final var member2Created = Instant.parse("2019-11-12T20:00:00.0Z"); 1039 final var member2Id = createDCMember(dcId, BASIC_CONTAINER, member2Created); 1040 1041 final var membershipRescAtMember1Create1Id = membershipRescId.asMemento(member1Created); 1042 final var membershipRescAtMember2Create1Id = membershipRescId.asMemento(member2Created); 1043 1044 // No membership before members added 1045 assertUncommittedMembershipCount(transaction, beforeAddMementoId, 0); 1046 // Check membership at the times members were added 1047 assertUncommittedMembershipCount(transaction, membershipRescAtMember1Create1Id, 1); 1048 assertUncommittedMembershipCount(transaction, membershipRescAtMember2Create1Id, 2); 1049 assertUncommittedMembershipCount(transaction, membershipRescId, 2); 1050 1051 membershipService.commitTransaction(transaction); 1052 1053 // No membership before members added 1054 assertCommittedMembershipCount(beforeAddMementoId, 0); 1055 assertHasMembersNoTx(membershipRescAtMember1Create1Id, RdfLexicon.LDP_MEMBER, member1Id); 1056 // Inbetween the members being added 1057 assertHasMembersNoTx(membershipRescId.asMemento(Instant.parse("2019-11-12T15:00:00.0Z")), 1058 RdfLexicon.LDP_MEMBER, member1Id); 1059 assertHasMembersNoTx(membershipRescAtMember2Create1Id, RdfLexicon.LDP_MEMBER, member1Id, member2Id); 1060 assertHasMembersNoTx(membershipRescId, RdfLexicon.LDP_MEMBER, member1Id, member2Id); 1061 1062 // Delete one of the members 1063 final var deleteInstant = Instant.parse("2019-11-13T12:00:00.0Z"); 1064 mockDeleteHeaders(member2Id, dcId, BASIC_CONTAINER, member2Created, deleteInstant); 1065 1066 membershipService.resourceDeleted(transaction, member2Id); 1067 1068 final var membershipRescAtDeleteId = membershipRescId.asMemento(deleteInstant); 1069 final var membershipRescAfterDeleteId = membershipRescId.asMemento(Instant.parse("2019-11-13T15:00:00.0Z")); 1070 1071 assertUncommittedMembershipCount(transaction, membershipRescAtMember2Create1Id, 2); 1072 assertUncommittedMembershipCount(transaction, membershipRescAtDeleteId, 1); 1073 assertUncommittedMembershipCount(transaction, membershipRescAfterDeleteId, 1); 1074 1075 membershipService.commitTransaction(transaction); 1076 1077 // No membership before members added 1078 assertCommittedMembershipCount(beforeAddMementoId, 0); 1079 assertHasMembersNoTx(membershipRescAtMember1Create1Id, RdfLexicon.LDP_MEMBER, member1Id); 1080 // Inbetween the members being added 1081 assertHasMembersNoTx(membershipRescId.asMemento(Instant.parse("2019-11-12T15:00:00.0Z")), 1082 RdfLexicon.LDP_MEMBER, member1Id); 1083 assertHasMembersNoTx(membershipRescAtMember2Create1Id, RdfLexicon.LDP_MEMBER, member1Id, member2Id); 1084 assertHasMembersNoTx(membershipRescAtDeleteId, RdfLexicon.LDP_MEMBER, member1Id); 1085 assertHasMembersNoTx(membershipRescAfterDeleteId, RdfLexicon.LDP_MEMBER, member1Id); 1086 assertHasMembersNoTx(membershipRescId, RdfLexicon.LDP_MEMBER, member1Id); 1087 } 1088 1089 @Test 1090 public void rollbackTransaction() throws Exception { 1091 mockGetHeaders(populateHeaders(membershipRescId, BASIC_CONTAINER)); 1092 membershipService.resourceCreated(transaction, membershipRescId); 1093 1094 final var dcId = createDirectContainer(membershipRescId, RdfLexicon.LDP_MEMBER, false); 1095 membershipService.resourceCreated(transaction, dcId); 1096 1097 final var member1Id = createDCMember(dcId, BASIC_CONTAINER); 1098 1099 membershipService.commitTransaction(transaction); 1100 1101 assertHasMembersNoTx(membershipRescId, RdfLexicon.LDP_MEMBER, member1Id); 1102 1103 final var member2Id = createDCMember(dcId, RdfLexicon.NON_RDF_SOURCE); 1104 1105 assertHasMembers(transaction, membershipRescId, RdfLexicon.LDP_MEMBER, member1Id, member2Id); 1106 1107 membershipService.rollbackTransaction(transaction); 1108 1109 assertHasMembers(transaction, membershipRescId, RdfLexicon.LDP_MEMBER, member1Id); 1110 1111 // Commit the transaction and verify the non-rollback entries persist 1112 membershipService.commitTransaction(transaction); 1113 1114 assertHasMembersNoTx(membershipRescId, RdfLexicon.LDP_MEMBER, member1Id); 1115 } 1116 1117 @Test 1118 public void resetMembershipIndex() throws Exception { 1119 mockGetHeaders(populateHeaders(membershipRescId, BASIC_CONTAINER)); 1120 membershipService.resourceCreated(transaction, membershipRescId); 1121 1122 final var dcId = createDirectContainer(membershipRescId, RdfLexicon.LDP_MEMBER, false); 1123 membershipService.resourceCreated(transaction, dcId); 1124 1125 final var member1Id = createDCMember(dcId, BASIC_CONTAINER); 1126 1127 membershipService.commitTransaction(transaction); 1128 1129 assertHasMembersNoTx(membershipRescId, RdfLexicon.LDP_MEMBER, member1Id); 1130 1131 final var member2Id = createDCMember(dcId, RdfLexicon.NON_RDF_SOURCE); 1132 1133 assertHasMembers(transaction, membershipRescId, RdfLexicon.LDP_MEMBER, member1Id, member2Id); 1134 1135 membershipService.reset(); 1136 1137 assertUncommittedMembershipCount(transaction, membershipRescId, 0); 1138 assertCommittedMembershipCount(membershipRescId, 0); 1139 } 1140 1141 @Test 1142 public void populateMembershipHistory_DC_DeletedMember() throws Exception { 1143 mockGetHeaders(populateHeaders(membershipRescId, BASIC_CONTAINER)); 1144 membershipService.resourceCreated(transaction, membershipRescId); 1145 1146 final var beforeAddMementoInstant = Instant.parse("2019-11-12T12:00:00.0Z"); 1147 final var beforeAddMementoId = membershipRescId.asMemento(beforeAddMementoInstant); 1148 1149 final var dcId = createDirectContainer(membershipRescId, RdfLexicon.LDP_MEMBER, false); 1150 membershipService.resourceCreated(transaction, dcId); 1151 1152 final var member1Created = Instant.parse("2019-11-12T13:00:00.0Z"); 1153 final var member1Id = createDCMember(dcId, BASIC_CONTAINER, member1Created); 1154 1155 final var member2Created = Instant.parse("2019-11-12T20:00:00.0Z"); 1156 final var member2Id = createDCMember(dcId, BASIC_CONTAINER, member2Created); 1157 1158 final var membershipRescAtMember1Create1Id = membershipRescId.asMemento(member1Created); 1159 final var membershipRescAtMember2Create1Id = membershipRescId.asMemento(member2Created); 1160 1161 membershipService.commitTransaction(transaction); 1162 1163 // Delete one of the members 1164 final var deleteInstant = Instant.parse("2019-11-13T12:00:00.0Z"); 1165 mockDeleteHeaders(member2Id, dcId, BASIC_CONTAINER, member2Created, deleteInstant); 1166 1167 membershipService.resourceDeleted(transaction, member2Id); 1168 1169 final var membershipRescAtDeleteId = membershipRescId.asMemento(deleteInstant); 1170 final var membershipRescAfterDeleteId = membershipRescId.asMemento(Instant.parse("2019-11-13T15:00:00.0Z")); 1171 1172 membershipService.commitTransaction(transaction); 1173 1174 // Clear the index 1175 membershipService.reset(); 1176 1177 mockListVersion(dcId, CREATED_DATE); 1178 1179 // Repopulate index 1180 membershipService.populateMembershipHistory(transaction, dcId); 1181 1182 membershipService.commitTransaction(transaction); 1183 1184 // No membership before members added 1185 assertCommittedMembershipCount(beforeAddMementoId, 0); 1186 assertHasMembersNoTx(membershipRescAtMember1Create1Id, RdfLexicon.LDP_MEMBER, member1Id); 1187 // Inbetween the members being added 1188 assertHasMembersNoTx(membershipRescId.asMemento(Instant.parse("2019-11-12T15:00:00.0Z")), 1189 RdfLexicon.LDP_MEMBER, member1Id); 1190 assertHasMembersNoTx(membershipRescAtMember2Create1Id, RdfLexicon.LDP_MEMBER, member1Id, member2Id); 1191 assertHasMembersNoTx(membershipRescAtDeleteId, RdfLexicon.LDP_MEMBER, member1Id); 1192 assertHasMembersNoTx(membershipRescAfterDeleteId, RdfLexicon.LDP_MEMBER, member1Id); 1193 assertHasMembersNoTx(membershipRescId, RdfLexicon.LDP_MEMBER, member1Id); 1194 } 1195 1196 @Test 1197 public void populateMembershipHistory_DC_ChangeRelation_AddedMemberAfter() throws Exception { 1198 mockGetHeaders(populateHeaders(membershipRescId, BASIC_CONTAINER)); 1199 membershipService.resourceCreated(transaction, membershipRescId); 1200 1201 final var dcId = createDirectContainer(membershipRescId, RdfLexicon.LDP_MEMBER, false); 1202 membershipService.resourceCreated(transaction, dcId); 1203 1204 membershipService.commitTransaction(transaction); 1205 1206 // Change the membership relation 1207 final var changeRelationInstant = Instant.parse("2019-11-14T12:00:00.0Z"); 1208 final var dcAtChangeRelation = dcId.asMemento(changeRelationInstant); 1209 1210 final var member2Id = createDCMember(dcId, BASIC_CONTAINER, changeRelationInstant); 1211 1212 mockGetTriplesForDC(dcId, changeRelationInstant, membershipRescId, OTHER_HAS_MEMBER, false); 1213 mockGetHeaders(transaction, dcAtChangeRelation, populateHeaders(dcId, rootId, 1214 RdfLexicon.DIRECT_CONTAINER, CREATED_DATE, changeRelationInstant), rootId); 1215 membershipService.resourceModified(transaction, dcId); 1216 1217 membershipService.commitTransaction(transaction); 1218 1219 assertHasMembersNoTx(membershipRescId.asMemento(changeRelationInstant), OTHER_HAS_MEMBER, member2Id); 1220 1221 final var member1Created = Instant.parse("2019-11-15T12:00:00.0Z"); 1222 final var member1Id = createDCMember(dcId, BASIC_CONTAINER, member1Created); 1223 1224 membershipService.commitTransaction(transaction); 1225 1226 membershipService.reset(); 1227 1228 mockListVersion(dcId, CREATED_DATE, changeRelationInstant); 1229 1230 membershipService.populateMembershipHistory(transaction, dcId); 1231 1232 membershipService.commitTransaction(transaction); 1233 1234 // No membership before member added 1235 assertCommittedMembershipCount(membershipRescId.asMemento(Instant.parse("2019-11-13T12:00:00.0Z")), 0); 1236 assertHasMembersNoTx(membershipRescId.asMemento(changeRelationInstant), OTHER_HAS_MEMBER, member2Id); 1237 assertHasMembersNoTx(membershipRescId.asMemento(member1Created), OTHER_HAS_MEMBER, 1238 member1Id, member2Id); 1239 assertHasMembersNoTx(membershipRescId.asMemento(member1Created), OTHER_HAS_MEMBER, 1240 member1Id, member2Id); 1241 assertHasMembersNoTx(membershipRescId, OTHER_HAS_MEMBER, member1Id, member2Id); 1242 } 1243 1244 @Test 1245 public void populateMembershipHistory_DC_ChangeRelation_AddMemberBefore() throws Exception { 1246 mockGetHeaders(populateHeaders(membershipRescId, BASIC_CONTAINER)); 1247 membershipService.resourceCreated(transaction, membershipRescId); 1248 1249 final var dcId = createDirectContainer(membershipRescId, RdfLexicon.LDP_MEMBER, false); 1250 membershipService.resourceCreated(transaction, dcId); 1251 1252 final var member1Id = createDCMember(dcId, BASIC_CONTAINER); 1253 1254 membershipService.commitTransaction(transaction); 1255 1256 final var member2Created = Instant.parse("2019-11-13T12:00:00.0Z"); 1257 final var member2Id = createDCMember(dcId, BASIC_CONTAINER, member2Created); 1258 1259 membershipService.commitTransaction(transaction); 1260 1261 // Change the membership relation 1262 final var changeRelationInstant = Instant.parse("2019-11-14T12:00:00.0Z"); 1263 final var dcAtChangeRelation = dcId.asMemento(changeRelationInstant); 1264 1265 // Mock triples change for changed DC 1266 mockGetTriplesForDC(dcId, changeRelationInstant, membershipRescId, OTHER_HAS_MEMBER, false); 1267 mockGetHeaders(transaction, dcAtChangeRelation, populateHeaders(dcId, rootId, 1268 RdfLexicon.DIRECT_CONTAINER, CREATED_DATE, changeRelationInstant), rootId); 1269 membershipService.resourceModified(transaction, dcId); 1270 1271 membershipService.commitTransaction(transaction); 1272 1273 membershipService.reset(); 1274 1275 mockListVersion(dcId, CREATED_DATE, changeRelationInstant); 1276 1277 membershipService.populateMembershipHistory(transaction, dcId); 1278 1279 membershipService.commitTransaction(transaction); 1280 1281 // No membership before creation 1282 assertCommittedMembershipCount(membershipRescId.asMemento(Instant.parse("2019-01-01T12:00:00.0Z")), 0); 1283 assertHasMembersNoTx(membershipRescId.asMemento(CREATED_DATE), RdfLexicon.LDP_MEMBER, member1Id); 1284 assertHasMembersNoTx(membershipRescId.asMemento(changeRelationInstant), OTHER_HAS_MEMBER, 1285 member1Id, member2Id); 1286 assertHasMembersNoTx(membershipRescId, OTHER_HAS_MEMBER, member1Id, member2Id); 1287 } 1288 1289 @Test 1290 public void populateMembershipHistory_DC_ChangedRelation_DeleteMemberBefore() throws Exception { 1291 mockGetHeaders(populateHeaders(membershipRescId, BASIC_CONTAINER)); 1292 membershipService.resourceCreated(transaction, membershipRescId); 1293 1294 final var dcId = createDirectContainer(membershipRescId, RdfLexicon.LDP_MEMBER, false); 1295 membershipService.resourceCreated(transaction, dcId); 1296 1297 final var member1Id = createDCMember(dcId, BASIC_CONTAINER); 1298 1299 membershipService.commitTransaction(transaction); 1300 1301 final var member2Created = Instant.parse("2019-11-13T12:00:00.0Z"); 1302 final var member2Id = createDCMember(dcId, BASIC_CONTAINER, member2Created); 1303 1304 // Delete one of the members 1305 final var deleteInstant = Instant.parse("2019-11-13T20:00:00.0Z"); 1306 mockDeleteHeaders(member1Id, dcId, BASIC_CONTAINER, CREATED_DATE, deleteInstant); 1307 membershipService.resourceDeleted(transaction, member1Id); 1308 1309 membershipService.commitTransaction(transaction); 1310 1311 // Change the membership relation 1312 final var changeRelationInstant = Instant.parse("2019-11-14T12:00:00.0Z"); 1313 final var dcAtChangeRelation = dcId.asMemento(changeRelationInstant); 1314 1315 // Mock triples change for changed DC 1316 mockGetTriplesForDC(dcId, changeRelationInstant, membershipRescId, OTHER_HAS_MEMBER, false); 1317 mockGetHeaders(transaction, dcAtChangeRelation, populateHeaders(dcId, rootId, 1318 RdfLexicon.DIRECT_CONTAINER, CREATED_DATE, changeRelationInstant), rootId); 1319 membershipService.resourceModified(transaction, dcId); 1320 1321 membershipService.commitTransaction(transaction); 1322 1323 membershipService.reset(); 1324 1325 mockListVersion(dcId, CREATED_DATE, changeRelationInstant); 1326 1327 membershipService.populateMembershipHistory(transaction, dcId); 1328 1329 membershipService.commitTransaction(transaction); 1330 1331 // No membership before creation 1332 assertCommittedMembershipCount(membershipRescId.asMemento(Instant.parse("2019-01-01T12:00:00.0Z")), 0); 1333 // Member 1 is deleted before relationship change, but after member 2 is created 1334 assertHasMembersNoTx(membershipRescId.asMemento(CREATED_DATE), RdfLexicon.LDP_MEMBER, 1335 member1Id); 1336 assertHasMembersNoTx(membershipRescId.asMemento(member2Created), RdfLexicon.LDP_MEMBER, 1337 member1Id, member2Id); 1338 // Member 2 exists before and after the relation change, so its history contains both 1339 assertHasMembersNoTx(membershipRescId.asMemento(deleteInstant), RdfLexicon.LDP_MEMBER, 1340 member2Id); 1341 assertHasMembersNoTx(membershipRescId.asMemento(changeRelationInstant), OTHER_HAS_MEMBER, 1342 member2Id); 1343 assertHasMembersNoTx(membershipRescId, OTHER_HAS_MEMBER, member2Id); 1344 } 1345 1346 @Test 1347 public void changeMembershipResource_ForIDC_ManualVersioning() throws Exception { 1348 setField(propsConfig, "autoVersioningEnabled", Boolean.FALSE); 1349 1350 mockGetHeaders(populateHeaders(membershipRescId, BASIC_CONTAINER)); 1351 membershipService.resourceCreated(transaction, membershipRescId); 1352 1353 final var membershipResc2Id = mintFedoraId(); 1354 mockGetHeaders(populateHeaders(membershipResc2Id, BASIC_CONTAINER)); 1355 membershipService.resourceCreated(transaction, membershipResc2Id); 1356 1357 final var idcId = createIndirectContainer(membershipRescId, RdfLexicon.LDP_MEMBER, false); 1358 membershipService.resourceCreated(transaction, idcId); 1359 1360 final var member1Id = createDCMember(rootId, BASIC_CONTAINER); 1361 1362 createProxy(idcId, member1Id, CREATED_DATE, true); 1363 1364 membershipService.commitTransaction(transaction); 1365 1366 assertCommittedMembershipCount(membershipRescId, 1); 1367 assertCommittedMembershipCount(membershipResc2Id, 0); 1368 1369 // Change the membership resource for the IDC without creating a version 1370 mockListVersion(idcId); 1371 mockGetTriplesForDC(idcId, CREATED_DATE, membershipResc2Id, RdfLexicon.LDP_MEMBER, false, PROXY_FOR, true); 1372 membershipService.resourceModified(transaction, idcId); 1373 1374 assertHasMembersNoTx(membershipRescId, RdfLexicon.LDP_MEMBER, member1Id); 1375 assertHasMembers(transaction, membershipResc2Id, RdfLexicon.LDP_MEMBER, member1Id); 1376 1377 membershipService.commitTransaction(transaction); 1378 1379 assertCommittedMembershipCount(membershipRescId, 0); 1380 assertHasMembersNoTx(membershipResc2Id, RdfLexicon.LDP_MEMBER, member1Id); 1381 1382 // Change membership property without versioning 1383 mockGetTriplesForDC(idcId, CREATED_DATE, membershipResc2Id, OTHER_HAS_MEMBER, false, PROXY_FOR, true); 1384 membershipService.resourceModified(transaction, idcId); 1385 1386 assertHasMembersNoTx(membershipResc2Id, RdfLexicon.LDP_MEMBER, member1Id); 1387 assertHasMembers(transaction, membershipResc2Id, OTHER_HAS_MEMBER, member1Id); 1388 1389 membershipService.commitTransaction(transaction); 1390 1391 assertCommittedMembershipCount(membershipRescId, 0); 1392 assertHasMembersNoTx(membershipResc2Id, OTHER_HAS_MEMBER, member1Id); 1393 1394 // Create version from former head version 1395 final var versionChangeTime = Instant.parse("2019-11-13T12:00:00.0Z"); 1396 mockListVersion(idcId, versionChangeTime); 1397 // New head state matches previous head state for the moment 1398 mockGetTriplesForDC(idcId, versionChangeTime, membershipResc2Id, OTHER_HAS_MEMBER, false, PROXY_FOR, false); 1399 mockGetHeaders(transaction, idcId.asMemento(versionChangeTime), populateHeaders(idcId, rootId, 1400 RdfLexicon.INDIRECT_CONTAINER, CREATED_DATE, versionChangeTime), rootId); 1401 1402 // Change membership resource after having created version 1403 final var afterVersionChangeTime = Instant.parse("2019-11-13T14:00:00.0Z"); 1404 mockGetHeaders(transaction, idcId, populateHeaders(idcId, rootId, 1405 RdfLexicon.INDIRECT_CONTAINER, CREATED_DATE, afterVersionChangeTime), rootId); 1406 mockGetTriplesForDC(idcId, afterVersionChangeTime, membershipRescId, OTHER_HAS_MEMBER, false, PROXY_FOR, true); 1407 membershipService.resourceModified(transaction, idcId); 1408 1409 // Membership resc 2 should still have a member prior to the version creation/last property update 1410 assertHasMembers(transaction, membershipResc2Id.asMemento(CREATED_DATE), OTHER_HAS_MEMBER, 1411 member1Id); 1412 assertUncommittedMembershipCount(transaction, membershipResc2Id, 0); 1413 assertHasMembersNoTx(membershipResc2Id, OTHER_HAS_MEMBER, member1Id); 1414 assertHasMembers(transaction, membershipRescId, OTHER_HAS_MEMBER, member1Id); 1415 1416 membershipService.commitTransaction(transaction); 1417 1418 assertCommittedMembershipCount(membershipResc2Id, 0); 1419 assertHasMembersNoTx(membershipResc2Id.asMemento(CREATED_DATE), OTHER_HAS_MEMBER, 1420 member1Id); 1421 assertCommittedMembershipCount(membershipRescId.asMemento(CREATED_DATE), 0); 1422 assertHasMembersNoTx(membershipRescId, OTHER_HAS_MEMBER, member1Id); 1423 } 1424 1425 private void mockListVersion(final FedoraId fedoraId, final Instant... versions) { 1426 when(psSession.listVersions(fedoraId.asResourceId())).thenReturn(Arrays.asList(versions)); 1427 } 1428 1429 private void assertHasMembersNoTx(final FedoraId membershipRescId, 1430 final Property hasMemberRelation, final FedoraId... memberIds) { 1431 assertHasMembers(shortLivedTx, membershipRescId, hasMemberRelation, memberIds); 1432 } 1433 1434 private void assertHasMembers(final Transaction transaction, final FedoraId membershipRescId, 1435 final Property hasMemberRelation, final FedoraId... memberIds) { 1436 final var membershipList = getMembershipList(transaction, membershipRescId); 1437 assertEquals(memberIds.length, membershipList.size()); 1438 final var subjectId = membershipRescId.asBaseId(); 1439 for (final FedoraId memberId : memberIds) { 1440 assertContainsMembership(membershipList, subjectId, hasMemberRelation, memberId); 1441 } 1442 } 1443 1444 private void assertIsMemberOfNoTx(final FedoraId memberId, final Property isMemberOf, 1445 final FedoraId membershipRescId) { 1446 assertIsMemberOf(shortLivedTx, memberId, isMemberOf, membershipRescId); 1447 } 1448 1449 private void assertIsMemberOf(final Transaction transaction, final FedoraId memberId, final Property isMemberOf, 1450 final FedoraId membershipRescId) { 1451 final var membershipList = getMembershipList(transaction, memberId); 1452 assertEquals(1, membershipList.size()); 1453 assertContainsMembership(membershipList, memberId.asBaseId(), isMemberOf, membershipRescId); 1454 } 1455 1456 private List<Triple> getMembershipList(final Transaction transaction, final FedoraId fedoraId) { 1457 final var results = membershipService.getMembership(transaction, fedoraId); 1458 return results.collect(Collectors.toList()); 1459 } 1460 1461 private FedoraId mintFedoraId() { 1462 return FedoraId.create(UUID.randomUUID().toString()); 1463 } 1464 1465 private void mockGetHeaders(final ResourceHeaders headers) { 1466 mockGetHeaders(transaction, headers.getId(), headers, rootId); 1467 } 1468 1469 private void mockGetHeaders(final Transaction transaction, final FedoraId fedoraId, final ResourceHeaders headers, 1470 final FedoraId parentId) { 1471 when(psSession.getHeaders(eq(fedoraId), nullable(Instant.class))).thenReturn(headers); 1472 if (!fedoraId.isMemento()) { 1473 when(psSession.getHeaders(eq(headers.getId().asMemento(headers.getCreatedDate())), 1474 nullable(Instant.class))).thenReturn(headers); 1475 } 1476 containmentIndex.addContainedBy(transaction, parentId, fedoraId); 1477 } 1478 1479 private void mockGetHeaders(final Transaction transaction, final FedoraId fedoraId, final FedoraId parentId, 1480 final Resource ixModel, final Instant createdDate, final Instant lastModified) { 1481 final var headers = populateHeaders(fedoraId, parentId, ixModel, createdDate, lastModified); 1482 mockGetHeaders(transaction, fedoraId, headers, parentId); 1483 } 1484 1485 private void mockDeleteHeaders(final FedoraId fedoraId, final FedoraId parentId, final Resource ixModel) { 1486 mockDeleteHeaders(fedoraId, parentId, ixModel, CREATED_DATE, LAST_MODIFIED_DATE); 1487 } 1488 1489 private void mockDeleteHeaders(final FedoraId fedoraId, final FedoraId parentId, final Resource ixModel, 1490 final Instant createdDate, final Instant deleteInstant) { 1491 final var deletedHeaders = populateHeaders(fedoraId, parentId, ixModel, createdDate, deleteInstant); 1492 deletedHeaders.setDeleted(true); 1493 when(psSession.getHeaders(eq(fedoraId), isNull())).thenReturn(deletedHeaders); 1494 } 1495 1496 private FedoraId createDCMember(final FedoraId dcId, final Resource ixModel) { 1497 final var memberId = mintFedoraId(); 1498 mockGetHeaders(transaction, memberId, dcId, ixModel, CREATED_DATE, LAST_MODIFIED_DATE); 1499 membershipService.resourceCreated(transaction, memberId); 1500 return memberId; 1501 } 1502 1503 private FedoraId createDCMember(final FedoraId dcId, final Resource ixModel, final Instant lastModified) { 1504 final var memberId = mintFedoraId(); 1505 mockGetHeaders(transaction, memberId, dcId, ixModel, lastModified, lastModified); 1506 membershipService.resourceCreated(transaction, memberId); 1507 return memberId; 1508 } 1509 1510 private ResourceHeaders populateHeaders(final FedoraId fedoraId, final Resource ixModel) { 1511 return populateHeaders(fedoraId, rootId, ixModel); 1512 } 1513 1514 private static ResourceHeaders populateHeaders(final FedoraId fedoraId, final FedoraId parentId, 1515 final Resource ixModel) { 1516 return populateHeaders(fedoraId, parentId, ixModel, CREATED_DATE, LAST_MODIFIED_DATE); 1517 } 1518 1519 private static ResourceHeadersImpl populateHeaders(final FedoraId fedoraId, final FedoraId parentId, 1520 final Resource ixModel, final Instant createdDate, final Instant lastModifiedDate) { 1521 final var headers = new ResourceHeadersImpl(); 1522 headers.setId(fedoraId); 1523 headers.setParent(parentId); 1524 headers.setInteractionModel(ixModel.getURI()); 1525 headers.setCreatedBy(CREATED_BY); 1526 headers.setCreatedDate(createdDate); 1527 headers.setLastModifiedBy(LAST_MODIFIED_BY); 1528 headers.setLastModifiedDate(lastModifiedDate); 1529 headers.setStateToken(STATE_TOKEN); 1530 return headers; 1531 } 1532 1533 private FedoraId createIndirectContainer(final FedoraId membershipRescId, final Property relation, 1534 final boolean useIsMemberOf) { 1535 return createIndirectContainer(rootId, membershipRescId, relation, useIsMemberOf); 1536 } 1537 1538 private FedoraId createIndirectContainer(final FedoraId parentId, final FedoraId membershipRescId, 1539 final Property relation, final boolean useIsMemberOf) { 1540 final var dcId = mintFedoraId(); 1541 mockGetHeaders(populateHeaders(dcId, parentId, RdfLexicon.INDIRECT_CONTAINER)); 1542 mockGetTriplesForDC(dcId, CREATED_DATE, membershipRescId, relation, useIsMemberOf, PROXY_FOR, false); 1543 return dcId; 1544 } 1545 1546 private FedoraId createProxy(final FedoraId idcId, final FedoraId memberId, 1547 final Instant lastModified, final boolean isHead) { 1548 final var proxyId = mintFedoraId(); 1549 mockGetHeaders(transaction, proxyId, idcId, BASIC_CONTAINER, lastModified, lastModified); 1550 final var model = ModelFactory.createDefaultModel(); 1551 final var proxyRdfResc = model.getResource(proxyId.getBaseId()); 1552 final var memberRdfResc = model.getResource(memberId.getFullId()); 1553 proxyRdfResc.addProperty(PROXY_FOR, memberRdfResc); 1554 mockGetTriplesForDC(proxyId, null, model); 1555 if (!isHead) { 1556 mockGetTriplesForDC(proxyId, lastModified, model); 1557 } 1558 membershipService.resourceCreated(transaction, proxyId); 1559 return memberId; 1560 } 1561 1562 private FedoraId createDirectContainer(final FedoraId membershipRescId, final Property relation, 1563 final boolean useIsMemberOf) { 1564 return createDirectContainer(rootId, membershipRescId, relation, useIsMemberOf); 1565 } 1566 1567 private FedoraId createDirectContainer(final FedoraId parentId, final FedoraId membershipRescId, 1568 final Property relation, final boolean useIsMemberOf) { 1569 final var dcId = mintFedoraId(); 1570 mockGetHeaders(populateHeaders(dcId, parentId, RdfLexicon.DIRECT_CONTAINER)); 1571 mockGetTriplesForDC(dcId, CREATED_DATE, membershipRescId, relation, useIsMemberOf); 1572 return dcId; 1573 } 1574 1575 private void mockGetTriplesForDCHead(final FedoraId dcId, final Instant startTime, final FedoraId membershipRescId, 1576 final Property relation, final boolean useIsMemberOf) { 1577 mockGetTriplesForDC(dcId, startTime, membershipRescId, relation, useIsMemberOf, true); 1578 } 1579 1580 private void mockGetTriplesForDC(final FedoraId dcId, final Instant startTime, final FedoraId membershipRescId, 1581 final Property relation, final boolean useIsMemberOf) { 1582 mockGetTriplesForDC(dcId, startTime, membershipRescId, relation, useIsMemberOf, false); 1583 } 1584 1585 private void mockGetTriplesForDC(final FedoraId dcId, final Instant startTime, final FedoraId membershipRescId, 1586 final Property relation, final boolean useIsMemberOf, final boolean isHead) { 1587 mockGetTriplesForDC(dcId, startTime, membershipRescId, relation, useIsMemberOf, null, isHead); 1588 } 1589 1590 private void mockGetTriplesForDC(final FedoraId dcId, final Instant startTime, final FedoraId membershipRescId, 1591 final Property relation, final boolean useIsMemberOf, final Property insertedContentRelation, 1592 final boolean isHead) { 1593 final var model = ModelFactory.createDefaultModel(); 1594 final var dcRdfResc = model.getResource(dcId.getBaseId()); 1595 final var membershipRdfResc = model.getResource(membershipRescId.getFullId()); 1596 dcRdfResc.addProperty(RdfLexicon.MEMBERSHIP_RESOURCE, membershipRdfResc); 1597 if (relation != null) { 1598 if (useIsMemberOf) { 1599 dcRdfResc.addProperty(RdfLexicon.IS_MEMBER_OF_RELATION, relation); 1600 } else { 1601 dcRdfResc.addProperty(RdfLexicon.HAS_MEMBER_RELATION, relation); 1602 } 1603 } 1604 if (insertedContentRelation != null) { 1605 dcRdfResc.addProperty(RdfLexicon.INSERTED_CONTENT_RELATION, insertedContentRelation); 1606 } 1607 1608 mockGetTriplesForDC(dcId, null, model); 1609 if (!isHead) { 1610 mockGetTriplesForDC(dcId, startTime, model); 1611 } 1612 } 1613 1614 private void mockGetTriplesForDC(final FedoraId dcId, final Instant startTime, final Model model) { 1615 when(psSession.getTriples(eq(dcId), eq(startTime))).thenAnswer(new Answer<RdfStream>() { 1616 @Override 1617 public RdfStream answer(final InvocationOnMock invocation) throws Throwable { 1618 return fromModel(model.getResource(dcId.getBaseId()).asNode(), model); 1619 } 1620 }); 1621 } 1622 1623 private void assertContainsMembership(final List<Triple> membershipList, final FedoraId subjectId, 1624 final Property property, final FedoraId objectId) { 1625 1626 final var subjectNode = NodeFactory.createURI(subjectId.getFullId()); 1627 final var objectNode = NodeFactory.createURI(objectId.getFullId()); 1628 1629 assertTrue("Membership set did not contain: " + subjectId + " " + property.getURI() + " " + objectId, 1630 membershipList.stream().anyMatch(t -> t.getSubject().equals(subjectNode) 1631 && t.getPredicate().equals(property.asNode()) 1632 && t.getObject().equals(objectNode))); 1633 } 1634 1635 private void assertCommittedMembershipCount(final FedoraId subjectId, final int expected) { 1636 final var results = membershipService.getMembership(shortLivedTx, subjectId); 1637 assertEquals("Incorrect number of committed membership properties for " + subjectId, 1638 expected, results.count()); 1639 } 1640 1641 private void assertUncommittedMembershipCount(final Transaction transaction, 1642 final FedoraId subjectId, 1643 final int expected) { 1644 final var results = membershipService.getMembership(transaction, subjectId); 1645 assertEquals("Incorrect number of uncommitted membership properties for " + subjectId, 1646 expected, results.count()); 1647 } 1648}