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