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;
019
020import static java.time.ZoneOffset.UTC;
021import static java.util.stream.Collectors.toList;
022import static org.fcrepo.kernel.api.services.VersionService.MEMENTO_LABEL_FORMATTER;
023import static org.junit.Assert.assertEquals;
024import static org.junit.Assert.assertFalse;
025import static org.junit.Assert.assertNotEquals;
026import static org.junit.Assert.assertNotNull;
027import static org.junit.Assert.assertNull;
028import static org.junit.Assert.assertTrue;
029import static org.mockito.Mockito.when;
030
031import java.time.Instant;
032import java.util.ArrayList;
033import java.util.HashMap;
034import java.util.List;
035import java.util.Map;
036import java.util.UUID;
037import java.util.concurrent.TimeUnit;
038
039import javax.inject.Inject;
040
041import org.fcrepo.kernel.api.Transaction;
042import org.fcrepo.kernel.api.identifiers.FedoraId;
043import org.fcrepo.kernel.api.models.FedoraResource;
044
045import org.flywaydb.test.FlywayTestExecutionListener;
046import org.flywaydb.test.annotation.FlywayTest;
047import org.junit.Before;
048import org.junit.Rule;
049import org.junit.Test;
050import org.junit.runner.RunWith;
051import org.mockito.Mock;
052import org.mockito.junit.MockitoJUnit;
053import org.mockito.junit.MockitoRule;
054import org.springframework.test.context.ContextConfiguration;
055import org.springframework.test.context.TestExecutionListeners;
056import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
057import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
058
059/**
060 * @author peichman
061 */
062@RunWith(SpringJUnit4ClassRunner.class)
063@ContextConfiguration("/containmentIndexTest.xml")
064@TestExecutionListeners({DependencyInjectionTestExecutionListener.class, FlywayTestExecutionListener.class })
065public class ContainmentIndexImplTest {
066
067    @Mock
068    private FedoraResource parent1;
069
070    @Mock
071    private FedoraResource child1;
072
073    @Mock
074    private FedoraResource parent2;
075
076    @Mock
077    private FedoraResource child2;
078
079    private Transaction transaction1;
080
081    private Transaction transaction2;
082
083    private Transaction shortLivedTx;
084
085    @Inject
086    private ContainmentIndexImpl containmentIndex;
087
088    @Rule
089    public MockitoRule rule = MockitoJUnit.rule().silent();
090
091    private final Map<String, FedoraResource> id_to_resource = new HashMap<>();
092    private final Map<String, Transaction> id_to_transaction = new HashMap<>();
093
094    @Before
095    @FlywayTest
096    public void setUp() {
097        id_to_resource.put("parent1", parent1);
098        id_to_resource.put("parent2", parent2);
099        id_to_resource.put("child1", child1);
100        id_to_resource.put("child2", child2);
101
102        transaction1 = TestTransactionHelper.mockTransaction("transaction1", false);
103        transaction2 = TestTransactionHelper.mockTransaction("transaction2", false);
104        shortLivedTx = TestTransactionHelper.mockTransaction("shortLived", true);
105
106        id_to_transaction.put("transaction1", transaction1);
107        id_to_transaction.put("transaction2", transaction2);
108    }
109
110    /**
111     * Utility method to make it easier to stub the getFedoraId() method and avoid MockitoHints.
112     * @param id The resource|transaction ID/name
113     */
114    private void stubObject(final String id) {
115        // Use unique ids for resources and transactions.
116        final String uuid = UUID.randomUUID().toString();
117        if (id_to_resource.containsKey(id)) {
118            final FedoraId fID = FedoraId.create(uuid);
119            when(id_to_resource.get(id).getFedoraId()).thenReturn(fID);
120        } else if (id_to_transaction.containsKey(id)) {
121            when(id_to_transaction.get(id).getId()).thenReturn(uuid);
122        }
123    }
124
125    @Test
126    public void testAddChildInTransaction() {
127        stubObject("parent1");
128        stubObject("child1");
129        stubObject("transaction1");
130        assertEquals(0, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
131        containmentIndex.addContainedBy(transaction1, parent1.getFedoraId(), child1.getFedoraId());
132        assertEquals(1, containmentIndex.getContains(transaction1, parent1.getFedoraId()).count());
133        assertEquals(child1.getFedoraId().getFullId(),
134                containmentIndex.getContains(transaction1, parent1.getFedoraId()).findFirst().get());
135        assertEquals(parent1.getFedoraId().getFullId(),
136                containmentIndex.getContainedBy(transaction1, child1.getFedoraId()));
137        // outside of the transaction, the containment shouldn't show up
138        assertEquals(0, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
139        containmentIndex.removeContainedBy(transaction1, parent1.getFedoraId(), child1.getFedoraId());
140        assertEquals(0, containmentIndex.getContains(transaction1, parent1.getFedoraId()).count());
141        assertNull(containmentIndex.getContainedBy(transaction1, child1.getFedoraId()));
142    }
143
144    @Test
145    public void testAddRemoveChildInSameTransaction() {
146        stubObject("parent1");
147        stubObject("child1");
148        stubObject("transaction1");
149        assertEquals(0, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
150        assertEquals(0, containmentIndex.getContains(transaction1, parent1.getFedoraId()).count());
151        assertEquals(0, containmentIndex.getContainsDeleted(shortLivedTx, parent1.getFedoraId()).count());
152        assertEquals(0, containmentIndex.getContainsDeleted(transaction1, parent1.getFedoraId()).count());
153        assertNull(containmentIndex.getContainedBy(transaction1, child1.getFedoraId()));
154        containmentIndex.addContainedBy(transaction1, parent1.getFedoraId(), child1.getFedoraId());
155        assertEquals(1, containmentIndex.getContains(transaction1, parent1.getFedoraId()).count());
156        assertEquals(parent1.getFedoraId().getFullId(),
157                containmentIndex.getContainedBy(transaction1, child1.getFedoraId()));
158        containmentIndex.removeContainedBy(transaction1, parent1.getFedoraId(), child1.getFedoraId());
159        assertEquals(0, containmentIndex.getContains(transaction1, parent1.getFedoraId()).count());
160        assertNull(containmentIndex.getContainedBy(transaction1, child1.getFedoraId()));
161        assertEquals(0, containmentIndex.getContainsDeleted(shortLivedTx, parent1.getFedoraId()).count());
162        assertEquals(0, containmentIndex.getContainsDeleted(transaction1, parent1.getFedoraId()).count());
163    }
164
165    @Test
166    public void testAddRemoveChildInTwoTransactions() {
167        stubObject("parent1");
168        stubObject("child1");
169        stubObject("transaction1");
170        assertEquals(0, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
171        assertEquals(0, containmentIndex.getContains(transaction1, parent1.getFedoraId()).count());
172        assertEquals(0, containmentIndex.getContainsDeleted(shortLivedTx, parent1.getFedoraId()).count());
173        assertEquals(0, containmentIndex.getContainsDeleted(transaction1, parent1.getFedoraId()).count());
174        assertNull(containmentIndex.getContainedBy(transaction1, child1.getFedoraId()));
175        containmentIndex.addContainedBy(transaction1, parent1.getFedoraId(), child1.getFedoraId());
176        assertEquals(1, containmentIndex.getContains(transaction1, parent1.getFedoraId()).count());
177        assertEquals(parent1.getFedoraId().getFullId(),
178                containmentIndex.getContainedBy(transaction1, child1.getFedoraId()));
179        containmentIndex.commitTransaction(transaction1);
180        assertEquals(1, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
181        containmentIndex.removeContainedBy(transaction1, parent1.getFedoraId(), child1.getFedoraId());
182        assertEquals(0, containmentIndex.getContainsDeleted(shortLivedTx, parent1.getFedoraId()).count());
183        assertEquals(1, containmentIndex.getContainsDeleted(transaction1, parent1.getFedoraId()).count());
184        assertEquals(0, containmentIndex.getContains(transaction1, parent1.getFedoraId()).count());
185        assertNull(containmentIndex.getContainedBy(transaction1, child1.getFedoraId()));
186        containmentIndex.commitTransaction(transaction1);
187        assertEquals(1, containmentIndex.getContainsDeleted(shortLivedTx, parent1.getFedoraId()).count());
188        assertEquals(1, containmentIndex.getContainsDeleted(transaction1, parent1.getFedoraId()).count());
189    }
190
191    @Test
192    public void testAddRemovePurgeChildInTransaction() {
193        stubObject("parent1");
194        stubObject("child1");
195        stubObject("transaction1");
196        assertEquals(0, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
197        assertEquals(0, containmentIndex.getContains(transaction1, parent1.getFedoraId()).count());
198        assertEquals(0, containmentIndex.getContainsDeleted(shortLivedTx, parent1.getFedoraId()).count());
199        assertNull(containmentIndex.getContainedBy(transaction1, child1.getFedoraId()));
200        containmentIndex.addContainedBy(transaction1, parent1.getFedoraId(), child1.getFedoraId());
201        assertEquals(0, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
202        assertEquals(1, containmentIndex.getContains(transaction1, parent1.getFedoraId()).count());
203        containmentIndex.removeContainedBy(transaction1, parent1.getFedoraId(), child1.getFedoraId());
204        assertEquals(0, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
205        assertEquals(0, containmentIndex.getContains(transaction1, parent1.getFedoraId()).count());
206        assertEquals(0, containmentIndex.getContainsDeleted(shortLivedTx, parent1.getFedoraId()).count());
207        assertEquals(0, containmentIndex.getContainsDeleted(transaction1, parent1.getFedoraId()).count());
208        containmentIndex.purgeResource(transaction1, child1.getFedoraId());
209        assertEquals(0, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
210        assertEquals(0, containmentIndex.getContains(transaction1, parent1.getFedoraId()).count());
211        assertEquals(0, containmentIndex.getContainsDeleted(shortLivedTx, parent1.getFedoraId()).count());
212        assertEquals(0, containmentIndex.getContainsDeleted(transaction1, parent1.getFedoraId()).count());
213    }
214
215    @Test
216    public void testAddRemovePurgeChildThreeTransaction() {
217        stubObject("parent1");
218        stubObject("child1");
219        stubObject("transaction1");
220        assertEquals(0, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
221        assertEquals(0, containmentIndex.getContains(transaction1, parent1.getFedoraId()).count());
222        assertEquals(0, containmentIndex.getContainsDeleted(shortLivedTx, parent1.getFedoraId()).count());
223        assertNull(containmentIndex.getContainedBy(transaction1, child1.getFedoraId()));
224        containmentIndex.addContainedBy(transaction1, parent1.getFedoraId(), child1.getFedoraId());
225        containmentIndex.commitTransaction(transaction1);
226        assertEquals(1, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
227        assertEquals(1, containmentIndex.getContains(transaction1, parent1.getFedoraId()).count());
228        containmentIndex.removeContainedBy(transaction1, parent1.getFedoraId(), child1.getFedoraId());
229        containmentIndex.commitTransaction(transaction1);
230        assertEquals(0, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
231        assertEquals(0, containmentIndex.getContains(transaction1, parent1.getFedoraId()).count());
232        assertEquals(1, containmentIndex.getContainsDeleted(shortLivedTx, parent1.getFedoraId()).count());
233        assertEquals(1, containmentIndex.getContainsDeleted(transaction1, parent1.getFedoraId()).count());
234        containmentIndex.purgeResource(transaction1, child1.getFedoraId());
235        containmentIndex.commitTransaction(transaction1);
236        assertEquals(0, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
237        assertEquals(0, containmentIndex.getContains(transaction1, parent1.getFedoraId()).count());
238        assertEquals(0, containmentIndex.getContainsDeleted(shortLivedTx, parent1.getFedoraId()).count());
239        assertEquals(0, containmentIndex.getContainsDeleted(transaction1, parent1.getFedoraId()).count());
240    }
241
242    @Test
243    public void testRollbackTransaction() {
244        stubObject("parent1");
245        stubObject("child1");
246        stubObject("transaction1");
247        assertEquals(0, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
248        assertEquals(0, containmentIndex.getContains(transaction1, parent1.getFedoraId()).count());
249        assertNull(containmentIndex.getContainedBy(shortLivedTx, child1.getFedoraId()));
250        assertNull(containmentIndex.getContainedBy(transaction1, child1.getFedoraId()));
251        containmentIndex.addContainedBy(transaction1, parent1.getFedoraId(), child1.getFedoraId());
252        assertEquals(0, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
253        assertEquals(1, containmentIndex.getContains(transaction1, parent1.getFedoraId()).count());
254        assertEquals(child1.getFedoraId().getFullId(),
255                containmentIndex.getContains(transaction1, parent1.getFedoraId()).findFirst().get());
256        assertNull(containmentIndex.getContainedBy(shortLivedTx, child1.getFedoraId()));
257        assertEquals(parent1.getFedoraId().getFullId(),
258                containmentIndex.getContainedBy(transaction1, child1.getFedoraId()));
259        containmentIndex.rollbackTransaction(transaction1);
260        assertEquals(0, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
261        assertEquals(0, containmentIndex.getContains(transaction1, parent1.getFedoraId()).count());
262        assertNull(containmentIndex.getContainedBy(shortLivedTx, child1.getFedoraId()));
263        assertNull(containmentIndex.getContainedBy(transaction1, child1.getFedoraId()));
264    }
265
266    @Test
267    public void testCommitTransaction() {
268        stubObject("parent1");
269        stubObject("child2");
270        stubObject("transaction1");
271        assertEquals(0, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
272        assertEquals(0, containmentIndex.getContains(transaction1, parent1.getFedoraId()).count());
273        containmentIndex.addContainedBy(transaction1, parent1.getFedoraId(), child2.getFedoraId());
274        assertEquals(0, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
275        assertEquals(1, containmentIndex.getContains(transaction1, parent1.getFedoraId()).count());
276        assertEquals(child2.getFedoraId().getFullId(),
277                containmentIndex.getContains(transaction1, parent1.getFedoraId()).findFirst().get());
278        assertEquals(parent1.getFedoraId().getFullId(),
279                containmentIndex.getContainedBy(transaction1, child2.getFedoraId()));
280        assertNull(containmentIndex.getContainedBy(shortLivedTx, child2.getFedoraId()));
281        containmentIndex.commitTransaction(transaction1);
282        assertEquals(1, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
283        assertEquals(1, containmentIndex.getContains(transaction1, parent1.getFedoraId()).count());
284        assertEquals(child2.getFedoraId().getFullId(),
285                containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).findFirst().get());
286        assertEquals(parent1.getFedoraId().getFullId(),
287                containmentIndex.getContainedBy(transaction1, child2.getFedoraId()));
288        assertEquals(child2.getFedoraId().getFullId(),
289                containmentIndex.getContains(transaction1, parent1.getFedoraId()).findFirst().get());
290        assertEquals(parent1.getFedoraId().getFullId(),
291                containmentIndex.getContainedBy(shortLivedTx, child2.getFedoraId()));
292    }
293
294    @Test
295    public void testSwapContained() {
296        stubObject("parent1");
297        stubObject("child1");
298        stubObject("child2");
299        stubObject("transaction1");
300        stubObject("transaction2");
301        assertEquals(0, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
302        containmentIndex.addContainedBy(transaction1, parent1.getFedoraId(), child1.getFedoraId());
303        containmentIndex.commitTransaction(transaction1);
304        assertEquals(1, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
305        assertEquals(child1.getFedoraId().getFullId(),
306                containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).findFirst().get());
307        containmentIndex.addContainedBy(transaction1, parent1.getFedoraId(), child2.getFedoraId());
308        containmentIndex.removeContainedBy(transaction1, parent1.getFedoraId(), child1.getFedoraId());
309        // Still the same outside
310        assertEquals(1, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
311        assertEquals(child1.getFedoraId().getFullId(),
312                containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).findFirst().get());
313        // Still the same in a different transaction
314        assertEquals(1, containmentIndex.getContains(transaction2, parent1.getFedoraId()).count());
315        assertEquals(child1.getFedoraId().getFullId(),
316                containmentIndex.getContains(transaction2, parent1.getFedoraId()).findFirst().get());
317        // Inside it has changed
318        assertEquals(1, containmentIndex.getContains(transaction1, parent1.getFedoraId()).count());
319        assertEquals(child2.getFedoraId().getFullId(),
320                containmentIndex.getContains(transaction1, parent1.getFedoraId()).findFirst().get());
321        containmentIndex.commitTransaction(transaction1);
322        // After commit() it is the same outside transactions.
323        assertEquals(1, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
324        assertEquals(child2.getFedoraId().getFullId(),
325                containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).findFirst().get());
326        // And now the same in a different transaction
327        assertEquals(1, containmentIndex.getContains(transaction2, parent1.getFedoraId()).count());
328        assertEquals(child2.getFedoraId().getFullId(),
329                containmentIndex.getContains(transaction2, parent1.getFedoraId()).findFirst().get());
330    }
331
332    @Test
333    public void testOnlyCommitOne() throws Exception {
334        stubObject("parent1");
335        stubObject("child1");
336        stubObject("child2");
337        stubObject("transaction1");
338        stubObject("transaction2");
339        assertEquals(0, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
340        containmentIndex.addContainedBy(transaction1, parent1.getFedoraId(), child1.getFedoraId());
341        containmentIndex.addContainedBy(transaction1, parent1.getFedoraId(), child2.getFedoraId());
342        containmentIndex.commitTransaction(transaction1);
343        assertEquals(2, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
344        // Delete one object in separate transactions.
345        containmentIndex.removeContainedBy(transaction1, parent1.getFedoraId(), child1.getFedoraId());
346        containmentIndex.removeContainedBy(transaction2, parent1.getFedoraId(), child2.getFedoraId());
347        assertEquals(1, containmentIndex.getContains(transaction1, parent1.getFedoraId()).count());
348        assertEquals(1, containmentIndex.getContains(transaction2, parent1.getFedoraId()).count());
349        containmentIndex.commitTransaction(transaction1);
350        // Wait a second because containment end time is second accuracy.
351        TimeUnit.SECONDS.sleep(1);
352        // Now only one record was removed
353        assertEquals(1, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
354        assertEquals(1, containmentIndex.getContains(transaction1, parent1.getFedoraId()).count());
355        // Except in the second transaction as it should now have 0
356        assertEquals(0, containmentIndex.getContains(transaction2, parent1.getFedoraId()).count());
357        containmentIndex.commitTransaction(transaction2);
358        // Wait a second because containment end time is second accuracy.
359        TimeUnit.SECONDS.sleep(1);
360        // Now all are gone
361        assertEquals(0, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
362        assertEquals(0, containmentIndex.getContains(transaction1, parent1.getFedoraId()).count());
363        assertEquals(0, containmentIndex.getContains(transaction2, parent1.getFedoraId()).count());
364    }
365
366    @Test
367    public void testTwoTransactionDeleteSameChild() {
368        stubObject("parent1");
369        stubObject("child1");
370        stubObject("child2");
371        stubObject("transaction1");
372        stubObject("transaction2");
373        assertEquals(0, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
374        containmentIndex.addContainedBy(transaction1, parent1.getFedoraId(), child1.getFedoraId());
375        containmentIndex.addContainedBy(transaction1, parent1.getFedoraId(), child2.getFedoraId());
376        containmentIndex.commitTransaction(transaction1);
377        assertEquals(2, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
378        // Delete one object in separate transactions.
379        containmentIndex.removeContainedBy(transaction1, parent1.getFedoraId(), child1.getFedoraId());
380        containmentIndex.removeContainedBy(transaction2, parent1.getFedoraId(), child1.getFedoraId());
381        assertEquals(1, containmentIndex.getContains(transaction1, parent1.getFedoraId()).count());
382        assertEquals(1, containmentIndex.getContains(transaction2, parent1.getFedoraId()).count());
383        containmentIndex.commitTransaction(transaction1);
384        // Now only one record was removed
385        assertEquals(1, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
386        assertEquals(1, containmentIndex.getContains(transaction1, parent1.getFedoraId()).count());
387        assertEquals(1, containmentIndex.getContains(transaction2, parent1.getFedoraId()).count());
388        containmentIndex.commitTransaction(transaction2);
389        // No change as the first transaction already committed.
390        assertEquals(1, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
391        assertEquals(1, containmentIndex.getContains(transaction1, parent1.getFedoraId()).count());
392        assertEquals(1, containmentIndex.getContains(transaction2, parent1.getFedoraId()).count());
393    }
394
395    @Test
396    public void testContainedByTwoSameTransactionException() {
397        stubObject("parent1");
398        stubObject("parent2");
399        stubObject("child1");
400        stubObject("transaction1");
401        assertEquals(0, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
402        assertEquals(0, containmentIndex.getContains(shortLivedTx, parent2.getFedoraId()).count());
403        // Add it to the first parent.
404        containmentIndex.addContainedBy(transaction1, parent1.getFedoraId(), child1.getFedoraId());
405        assertEquals(0, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
406        assertEquals(0, containmentIndex.getContains(shortLivedTx, parent2.getFedoraId()).count());
407        assertEquals(1, containmentIndex.getContains(transaction1, parent1.getFedoraId()).count());
408        assertEquals(0, containmentIndex.getContains(transaction1, parent2.getFedoraId()).count());
409        // When you add it to the second parent, it is altered and the first relationship is overwritten.
410        containmentIndex.addContainedBy(transaction1, parent2.getFedoraId(), child1.getFedoraId());
411        assertEquals(0, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
412        assertEquals(0, containmentIndex.getContains(shortLivedTx, parent2.getFedoraId()).count());
413        assertEquals(0, containmentIndex.getContains(transaction1, parent1.getFedoraId()).count());
414        assertEquals(1, containmentIndex.getContains(transaction1, parent2.getFedoraId()).count());
415        containmentIndex.commitTransaction(transaction1);
416        // This should be rolled back so the additions should still be in the transaction operation table.
417        assertEquals(0, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
418        assertEquals(1, containmentIndex.getContains(shortLivedTx, parent2.getFedoraId()).count());
419        assertEquals(0, containmentIndex.getContains(transaction1, parent1.getFedoraId()).count());
420        assertEquals(1, containmentIndex.getContains(transaction1, parent2.getFedoraId()).count());
421    }
422
423    @Test
424    public void testExistsOutsideTransaction() {
425        stubObject("parent1");
426        stubObject("child1");
427        stubObject("transaction1");
428        stubObject("transaction2");
429        assertFalse(containmentIndex.resourceExists(shortLivedTx, child1.getFedoraId(), false));
430        assertFalse(containmentIndex.resourceExists(transaction1, child1.getFedoraId(), false));
431        containmentIndex.addContainedBy(transaction2, parent1.getFedoraId(), child1.getFedoraId());
432        containmentIndex.commitTransaction(transaction2);
433        assertTrue(containmentIndex.resourceExists(shortLivedTx, child1.getFedoraId(), false));
434        assertTrue(containmentIndex.resourceExists(transaction1, child1.getFedoraId(), false));
435    }
436
437    @Test
438    public void testExistsInsideTransaction() {
439        stubObject("parent1");
440        stubObject("child1");
441        stubObject("transaction1");
442        stubObject("transaction2");
443        assertFalse(containmentIndex.resourceExists(shortLivedTx, child1.getFedoraId(), false));
444        assertFalse(containmentIndex.resourceExists(transaction1, child1.getFedoraId(), false));
445        assertFalse(containmentIndex.resourceExists(transaction2, child1.getFedoraId(), false));
446        // Only visible in the transaction.
447        containmentIndex.addContainedBy(transaction1, parent1.getFedoraId(), child1.getFedoraId());
448        assertFalse(containmentIndex.resourceExists(shortLivedTx, child1.getFedoraId(), false));
449        assertTrue(containmentIndex.resourceExists(transaction1, child1.getFedoraId(), false));
450        assertFalse(containmentIndex.resourceExists(transaction2, child1.getFedoraId(), false));
451        // Rollback transaction.
452        containmentIndex.rollbackTransaction(transaction1);
453        assertFalse(containmentIndex.resourceExists(shortLivedTx, child1.getFedoraId(), false));
454        assertFalse(containmentIndex.resourceExists(transaction1, child1.getFedoraId(), false));
455        assertFalse(containmentIndex.resourceExists(transaction2, child1.getFedoraId(), false));
456        // Add again in transaction.
457        containmentIndex.addContainedBy(transaction1, parent1.getFedoraId(), child1.getFedoraId());
458        assertFalse(containmentIndex.resourceExists(shortLivedTx, child1.getFedoraId(), false));
459        assertTrue(containmentIndex.resourceExists(transaction1, child1.getFedoraId(), false));
460        assertFalse(containmentIndex.resourceExists(transaction2, child1.getFedoraId(), false));
461        // Commit and visible everywhere.
462        containmentIndex.commitTransaction(transaction1);
463        assertTrue(containmentIndex.resourceExists(shortLivedTx, child1.getFedoraId(), false));
464        assertTrue(containmentIndex.resourceExists(transaction1, child1.getFedoraId(), false));
465        assertTrue(containmentIndex.resourceExists(transaction2, child1.getFedoraId(), false));
466    }
467
468    @Test
469    public void testRemoveResource() {
470        stubObject("parent1");
471        stubObject("child1");
472        stubObject("transaction1");
473        containmentIndex.addContainedBy(transaction1, parent1.getFedoraId(), child1.getFedoraId());
474        containmentIndex.commitTransaction(transaction1);
475        assertEquals(1, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
476        assertEquals(parent1.getFedoraId().getFullId(),
477                containmentIndex.getContainedBy(shortLivedTx, child1.getFedoraId()));
478        containmentIndex.removeResource(transaction1, child1.getFedoraId());
479        containmentIndex.commitTransaction(transaction1);
480        assertEquals(0, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
481        assertNull(containmentIndex.getContainedBy(shortLivedTx, child1.getFedoraId()));
482    }
483
484    @Test
485    public void testRemoveNotFromTransaction() {
486        stubObject("parent1");
487        stubObject("child1");
488        stubObject("parent2");
489        stubObject("transaction1");
490        stubObject("transaction2");
491        assertNull(containmentIndex.getContainedBy(shortLivedTx, child1.getFedoraId()));
492        containmentIndex.addContainedBy(transaction2, parent1.getFedoraId(), child1.getFedoraId());
493        containmentIndex.commitTransaction(transaction2);
494        containmentIndex.addContainedBy(transaction1, parent2.getFedoraId(), child1.getFedoraId());
495        assertEquals(1, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
496        assertEquals(parent1.getFedoraId().getFullId(),
497                containmentIndex.getContainedBy(shortLivedTx, child1.getFedoraId()));
498        assertEquals(1, containmentIndex.getContains(transaction1, parent2.getFedoraId()).count());
499        assertEquals(child1.getFedoraId().getFullId(),
500                containmentIndex.getContains(transaction1, parent2.getFedoraId()).findFirst().get());
501        containmentIndex.removeResource(transaction2, child1.getFedoraId());
502        containmentIndex.commitTransaction(transaction2);
503        assertEquals(0, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
504        assertNull(containmentIndex.getContainedBy(shortLivedTx, child1.getFedoraId()));
505        assertEquals(1, containmentIndex.getContains(transaction1, parent2.getFedoraId()).count());
506        assertEquals(child1.getFedoraId().getFullId(),
507                containmentIndex.getContains(transaction1, parent2.getFedoraId()).findFirst().get());
508    }
509
510    @Test
511    public void testCommitRemoveFromTransaction() {
512        stubObject("parent1");
513        stubObject("child1");
514        stubObject("transaction1");
515        stubObject("transaction2");
516        containmentIndex.addContainedBy(transaction2, parent1.getFedoraId(), child1.getFedoraId());
517        containmentIndex.commitTransaction(transaction2);
518        assertEquals(1, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
519        assertEquals(parent1.getFedoraId().getFullId(),
520                containmentIndex.getContainedBy(shortLivedTx, child1.getFedoraId()));
521        containmentIndex.removeResource(transaction1, child1.getFedoraId());
522        assertEquals(1, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
523        assertEquals(parent1.getFedoraId().getFullId(),
524                containmentIndex.getContainedBy(shortLivedTx, child1.getFedoraId()));
525        assertEquals(0, containmentIndex.getContains(transaction1, parent1.getFedoraId()).count());
526        containmentIndex.commitTransaction(transaction1);
527        assertEquals(0, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
528        assertNull(containmentIndex.getContainedBy(shortLivedTx, child1.getFedoraId()));
529        assertEquals(0, containmentIndex.getContains(transaction1, parent1.getFedoraId()).count());
530    }
531
532    /**
533     * Ensure match the id without a trailing slash.
534     */
535    @Test
536    public void testResourceExistsFedoraIDNoTrailingSlash() {
537        stubObject("parent1");
538        stubObject("child1");
539        stubObject("transaction1");
540        final FedoraId fedoraID = FedoraId.create(child1.getFedoraId().getFullId());
541        containmentIndex.addContainedBy(transaction1, parent1.getFedoraId(), child1.getFedoraId());
542        containmentIndex.commitTransaction(transaction1);
543        assertEquals(1, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
544        assertEquals(parent1.getFedoraId().getFullId(),
545                containmentIndex.getContainedBy(shortLivedTx, child1.getFedoraId()));
546        assertTrue(containmentIndex.resourceExists(shortLivedTx, child1.getFedoraId(), false));
547        assertTrue(containmentIndex.resourceExists(shortLivedTx, fedoraID, false));
548    }
549
550    /**
551     * Ensure match the id with a trailing slash.
552     */
553    @Test
554    public void testResourceExistsFedoraIDTrailingSlash() {
555        stubObject("parent1");
556        stubObject("child1");
557        stubObject("transaction1");
558        final FedoraId fedoraID = FedoraId.create(child1.getFedoraId().getFullId() + "/");
559        containmentIndex.addContainedBy(transaction1, parent1.getFedoraId(), child1.getFedoraId());
560        containmentIndex.commitTransaction(transaction1);
561        assertEquals(1, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
562        assertEquals(parent1.getFedoraId().getFullId(),
563                containmentIndex.getContainedBy(shortLivedTx, child1.getFedoraId()));
564        assertTrue(containmentIndex.resourceExists(shortLivedTx, child1.getFedoraId(), false));
565        assertTrue(containmentIndex.resourceExists(shortLivedTx, fedoraID, false));
566    }
567
568    @Test
569    public void clearIndexWhenReset() {
570        stubObject("parent1");
571        stubObject("child1");
572        stubObject("transaction1");
573
574        containmentIndex.addContainedBy(transaction1, parent1.getFedoraId(), child1.getFedoraId());
575
576        assertTrue(containmentIndex.resourceExists(transaction1, child1.getFedoraId(), false));
577
578        containmentIndex.reset();
579
580        assertFalse(containmentIndex.resourceExists(transaction1, child1.getFedoraId(), false));
581    }
582
583    @Test
584    public void testHasResourcesStartingFailure() {
585        stubObject("parent1");
586        stubObject("child1");
587        stubObject("transaction1");
588        // Nothing exists.
589        assertFalse(containmentIndex.hasResourcesStartingWith(shortLivedTx, parent1.getFedoraId()));
590        // Add the single resource.
591        containmentIndex.addContainedBy(transaction1, FedoraId.getRepositoryRootId(), parent1.getFedoraId());
592        containmentIndex.commitTransaction(transaction1);
593        // Still no similar paths.
594        assertFalse(containmentIndex.hasResourcesStartingWith(shortLivedTx, parent1.getFedoraId()));
595        // Add a contained resource that does NOT share the URI path.
596        assertFalse(child1.getFedoraId().getFullId().startsWith(parent1.getFedoraId().getFullId()));
597        containmentIndex.addContainedBy(transaction1, parent1.getFedoraId(), child1.getFedoraId());
598        containmentIndex.commitTransaction(transaction1);
599        // Still no similar paths.
600        assertFalse(containmentIndex.hasResourcesStartingWith(shortLivedTx, parent1.getFedoraId()));
601    }
602
603    @Test
604    public void testHasResourcesStartingSuccess() {
605        stubObject("parent1");
606        final var subPathId = parent1.getFedoraId().resolve("a/layer/down");
607        stubObject("transaction1");
608        // Add a resource.
609        containmentIndex.addContainedBy(transaction1, FedoraId.getRepositoryRootId(), subPathId);
610        // That resource's ID starts with the ID we are checking.
611        assertTrue(subPathId.getFullId().startsWith(parent1.getFedoraId().getFullId()));
612        assertTrue(containmentIndex.hasResourcesStartingWith(transaction1, parent1.getFedoraId()));
613    }
614
615    @Test
616    public void testDeletedResourceExists() {
617        stubObject("parent1");
618        stubObject("transaction1");
619        containmentIndex.addContainedBy(transaction1, FedoraId.getRepositoryRootId(), parent1.getFedoraId());
620        containmentIndex.commitTransaction(transaction1);
621        assertTrue(containmentIndex.resourceExists(shortLivedTx, parent1.getFedoraId(), false));
622        assertTrue(containmentIndex.resourceExists(shortLivedTx, parent1.getFedoraId(), true));
623        containmentIndex.removeContainedBy(transaction1, FedoraId.getRepositoryRootId(), parent1.getFedoraId());
624        containmentIndex.commitTransaction(transaction1);
625        assertFalse(containmentIndex.resourceExists(shortLivedTx, parent1.getFedoraId(), false));
626        assertTrue(containmentIndex.resourceExists(shortLivedTx, parent1.getFedoraId(), true));
627        containmentIndex.purgeResource(transaction1, parent1.getFedoraId());
628        containmentIndex.commitTransaction(transaction1);
629        assertFalse(containmentIndex.resourceExists(shortLivedTx, parent1.getFedoraId(), false));
630        assertFalse(containmentIndex.resourceExists(shortLivedTx, parent1.getFedoraId(), true));
631    }
632
633    @Test
634    public void testMementosContainment() throws Exception {
635        stubObject("parent1");
636        stubObject("child1");
637        stubObject("child2");
638        stubObject("transaction1");
639
640        // Parent contains child1 and child2
641        containmentIndex.addContainedBy(transaction1, parent1.getFedoraId(), child1.getFedoraId());
642        containmentIndex.addContainedBy(transaction1, parent1.getFedoraId(), child2.getFedoraId());
643        containmentIndex.commitTransaction(transaction1);
644        TimeUnit.SECONDS.sleep(1);
645        assertEquals(2, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
646        // get the current instant and make a FedoraId for a memento at this instant.
647        final var bothTime = Instant.now();
648        final var mementoId = parent1.getFedoraId().resolve("fcr:versions/" +
649                bothTime.atZone(UTC).format(MEMENTO_LABEL_FORMATTER));
650        // Wait.
651        TimeUnit.SECONDS.sleep(2);
652        // Delete child1
653        containmentIndex.removeContainedBy(transaction1, parent1.getFedoraId(), child1.getFedoraId());
654        assertEquals(2, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
655        assertEquals(1, containmentIndex.getContains(transaction1, parent1.getFedoraId()).count());
656        containmentIndex.commitTransaction(transaction1);
657        TimeUnit.SECONDS.sleep(1);
658        // Child1 is gone in the current view.
659        assertEquals(1, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
660        // Child1 remains in the memento view.
661        assertEquals(2, containmentIndex.getContains(shortLivedTx, mementoId).count());
662        // purging child 1
663        containmentIndex.purgeResource(transaction1, child1.getFedoraId());
664        // stays the same as we haven't committed yet.
665        assertEquals(1, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
666        assertEquals(2, containmentIndex.getContains(shortLivedTx, mementoId).count());
667        containmentIndex.commitTransaction(transaction1);
668        // Now the memento loses track of child1.
669        assertEquals(1, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
670        assertEquals(1, containmentIndex.getContains(shortLivedTx, mementoId).count());
671    }
672
673    @Test
674    public void testChecksum() throws Exception {
675        stubObject("parent1");
676        stubObject("transaction1");
677        // Need to add a containment record for the parent to hold the updated value.
678        containmentIndex.addContainedBy(transaction1, FedoraId.getRepositoryRootId(), parent1.getFedoraId());
679        final var empty = containmentIndex.containmentLastUpdated(shortLivedTx, parent1.getFedoraId());
680        assertNull(empty);
681        // Wait a half second as the ETag is based on the highest value of any child's start_time or end_time.
682        TimeUnit.MILLISECONDS.sleep(500);
683        final var firstBorn = parent1.getFedoraId().resolve("child1");
684        containmentIndex.addContainedBy(transaction1, parent1.getFedoraId(), firstBorn);
685        assertEquals(1, containmentIndex.getContains(transaction1, parent1.getFedoraId()).count());
686        assertEquals(0, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
687        final var first = containmentIndex.containmentLastUpdated(transaction1, parent1.getFedoraId());
688        assertNotNull(first);
689        assertNotEquals(empty, first);
690
691        containmentIndex.commitTransaction(transaction1);
692        assertEquals(1, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
693        assertEquals(first, containmentIndex.containmentLastUpdated(shortLivedTx, parent1.getFedoraId()));
694
695        // Wait half a second, all these children will share a start_time.
696        TimeUnit.SECONDS.sleep(1);
697        for (var i = 0; i < 30; i += 1) {
698            final var kidId = parent1.getFedoraId().resolve("child-" + i);
699            containmentIndex.addContainedBy(transaction1, parent1.getFedoraId(), kidId);
700        }
701        assertEquals(31, containmentIndex.getContains(transaction1, parent1.getFedoraId()).count());
702        final var allTime = containmentIndex.containmentLastUpdated(transaction1, parent1.getFedoraId());
703        assertNotEquals(empty, allTime);
704        assertNotEquals(first, allTime);
705
706        containmentIndex.rollbackTransaction(transaction1);
707        assertEquals(1, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
708        assertEquals(first, containmentIndex.containmentLastUpdated(shortLivedTx, parent1.getFedoraId()));
709
710        TimeUnit.SECONDS.sleep(1);
711
712        containmentIndex.removeContainedBy(transaction1, parent1.getFedoraId(), firstBorn);
713        assertEquals(0, containmentIndex.getContains(transaction1, parent1.getFedoraId()).count());
714        assertNotEquals(first, containmentIndex.containmentLastUpdated(transaction1, parent1.getFedoraId()));
715        assertNotEquals(allTime, containmentIndex.containmentLastUpdated(transaction1, parent1.getFedoraId()));
716
717        containmentIndex.commitTransaction(transaction1);
718        assertEquals(0, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
719        final var last = containmentIndex.containmentLastUpdated(shortLivedTx, parent1.getFedoraId());
720        assertNotNull(last);
721        assertNotEquals(first, last);
722        assertNotEquals(allTime, last);
723    }
724
725    @Test
726    public void testLargeContainment() {
727        stubObject("transaction1");
728        stubObject("parent1");
729        containmentIndex.setContainsLimit(5);
730        final List<String> expectedChildren = new ArrayList<>(10);
731        for (var i = 0; i < 10; i += 1) {
732            final FedoraId childId = parent1.getFedoraId().resolve("child_" + i);
733            expectedChildren.add(childId.getFullId());
734            containmentIndex.addContainedBy(transaction1, parent1.getFedoraId(), childId);
735        }
736        assertEquals(10, containmentIndex.getContains(transaction1, parent1.getFedoraId()).count());
737        assertEquals(0, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
738        containmentIndex.commitTransaction(transaction1);
739        final var foundChildren = containmentIndex.getContains(shortLivedTx, parent1.getFedoraId())
740                .collect(toList());
741        assertEquals(10, foundChildren.size());
742        assertEquals(expectedChildren, foundChildren);
743    }
744}
745
746