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