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