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 testHasResourcesStartingFailure() {
573        stubObject("parent1");
574        stubObject("child1");
575        stubObject("transaction1");
576        // Nothing exists.
577        assertFalse(containmentIndex.hasResourcesStartingWith(shortLivedTx, parent1.getFedoraId()));
578        // Add the single resource.
579        containmentIndex.addContainedBy(transaction1, FedoraId.getRepositoryRootId(), parent1.getFedoraId());
580        containmentIndex.commitTransaction(transaction1);
581        // Still no similar paths.
582        assertFalse(containmentIndex.hasResourcesStartingWith(shortLivedTx, parent1.getFedoraId()));
583        // Add a contained resource that does NOT share the URI path.
584        assertFalse(child1.getFedoraId().getFullId().startsWith(parent1.getFedoraId().getFullId()));
585        containmentIndex.addContainedBy(transaction1, parent1.getFedoraId(), child1.getFedoraId());
586        containmentIndex.commitTransaction(transaction1);
587        // Still no similar paths.
588        assertFalse(containmentIndex.hasResourcesStartingWith(shortLivedTx, parent1.getFedoraId()));
589    }
590
591    @Test
592    public void testHasResourcesStartingSuccess() {
593        stubObject("parent1");
594        final var subPathId = parent1.getFedoraId().resolve("a/layer/down");
595        stubObject("transaction1");
596        // Add a resource.
597        containmentIndex.addContainedBy(transaction1, FedoraId.getRepositoryRootId(), subPathId);
598        // That resource's ID starts with the ID we are checking.
599        assertTrue(subPathId.getFullId().startsWith(parent1.getFedoraId().getFullId()));
600        assertTrue(containmentIndex.hasResourcesStartingWith(transaction1, parent1.getFedoraId()));
601    }
602
603    @Test
604    public void testDeletedResourceExists() {
605        stubObject("parent1");
606        stubObject("transaction1");
607        containmentIndex.addContainedBy(transaction1, FedoraId.getRepositoryRootId(), parent1.getFedoraId());
608        containmentIndex.commitTransaction(transaction1);
609        assertTrue(containmentIndex.resourceExists(shortLivedTx, parent1.getFedoraId(), false));
610        assertTrue(containmentIndex.resourceExists(shortLivedTx, parent1.getFedoraId(), true));
611        containmentIndex.removeContainedBy(transaction1, FedoraId.getRepositoryRootId(), parent1.getFedoraId());
612        containmentIndex.commitTransaction(transaction1);
613        assertFalse(containmentIndex.resourceExists(shortLivedTx, parent1.getFedoraId(), false));
614        assertTrue(containmentIndex.resourceExists(shortLivedTx, parent1.getFedoraId(), true));
615        containmentIndex.purgeResource(transaction1, parent1.getFedoraId());
616        containmentIndex.commitTransaction(transaction1);
617        assertFalse(containmentIndex.resourceExists(shortLivedTx, parent1.getFedoraId(), false));
618        assertFalse(containmentIndex.resourceExists(shortLivedTx, parent1.getFedoraId(), true));
619    }
620
621    @Test
622    public void testMementosContainment() throws Exception {
623        stubObject("parent1");
624        stubObject("child1");
625        stubObject("child2");
626        stubObject("transaction1");
627
628        // Parent contains child1 and child2
629        containmentIndex.addContainedBy(transaction1, parent1.getFedoraId(), child1.getFedoraId());
630        containmentIndex.addContainedBy(transaction1, parent1.getFedoraId(), child2.getFedoraId());
631        containmentIndex.commitTransaction(transaction1);
632        TimeUnit.SECONDS.sleep(1);
633        assertEquals(2, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
634        // get the current instant and make a FedoraId for a memento at this instant.
635        final var bothTime = Instant.now();
636        final var mementoId = parent1.getFedoraId().resolve("fcr:versions/" +
637                bothTime.atZone(UTC).format(MEMENTO_LABEL_FORMATTER));
638        // Wait.
639        TimeUnit.SECONDS.sleep(2);
640        // Delete child1
641        containmentIndex.removeContainedBy(transaction1, parent1.getFedoraId(), child1.getFedoraId());
642        assertEquals(2, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
643        assertEquals(1, containmentIndex.getContains(transaction1, parent1.getFedoraId()).count());
644        containmentIndex.commitTransaction(transaction1);
645        TimeUnit.SECONDS.sleep(1);
646        // Child1 is gone in the current view.
647        assertEquals(1, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
648        // Child1 remains in the memento view.
649        assertEquals(2, containmentIndex.getContains(shortLivedTx, mementoId).count());
650        // purging child 1
651        containmentIndex.purgeResource(transaction1, child1.getFedoraId());
652        // stays the same as we haven't committed yet.
653        assertEquals(1, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
654        assertEquals(2, containmentIndex.getContains(shortLivedTx, mementoId).count());
655        containmentIndex.commitTransaction(transaction1);
656        // Now the memento loses track of child1.
657        assertEquals(1, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
658        assertEquals(1, containmentIndex.getContains(shortLivedTx, mementoId).count());
659    }
660
661    @Test
662    public void testChecksum() throws Exception {
663        stubObject("parent1");
664        stubObject("transaction1");
665        // Need to add a containment record for the parent to hold the updated value.
666        containmentIndex.addContainedBy(transaction1, FedoraId.getRepositoryRootId(), parent1.getFedoraId());
667        final var empty = containmentIndex.containmentLastUpdated(shortLivedTx, parent1.getFedoraId());
668        assertNull(empty);
669        // Wait a half second as the ETag is based on the highest value of any child's start_time or end_time.
670        TimeUnit.MILLISECONDS.sleep(500);
671        final var firstBorn = parent1.getFedoraId().resolve("child1");
672        containmentIndex.addContainedBy(transaction1, parent1.getFedoraId(), firstBorn);
673        assertEquals(1, containmentIndex.getContains(transaction1, parent1.getFedoraId()).count());
674        assertEquals(0, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
675        final var first = containmentIndex.containmentLastUpdated(transaction1, parent1.getFedoraId());
676        assertNotNull(first);
677        assertNotEquals(empty, first);
678
679        containmentIndex.commitTransaction(transaction1);
680        assertEquals(1, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
681        assertEquals(first, containmentIndex.containmentLastUpdated(shortLivedTx, parent1.getFedoraId()));
682
683        // Wait half a second, all these children will share a start_time.
684        TimeUnit.SECONDS.sleep(1);
685        for (var i = 0; i < 30; i += 1) {
686            final var kidId = parent1.getFedoraId().resolve("child-" + i);
687            containmentIndex.addContainedBy(transaction1, parent1.getFedoraId(), kidId);
688        }
689        assertEquals(31, containmentIndex.getContains(transaction1, parent1.getFedoraId()).count());
690        final var allTime = containmentIndex.containmentLastUpdated(transaction1, parent1.getFedoraId());
691        assertNotEquals(empty, allTime);
692        assertNotEquals(first, allTime);
693
694        containmentIndex.rollbackTransaction(transaction1);
695        assertEquals(1, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
696        assertEquals(first, containmentIndex.containmentLastUpdated(shortLivedTx, parent1.getFedoraId()));
697
698        TimeUnit.SECONDS.sleep(1);
699
700        containmentIndex.removeContainedBy(transaction1, parent1.getFedoraId(), firstBorn);
701        assertEquals(0, containmentIndex.getContains(transaction1, parent1.getFedoraId()).count());
702        assertNotEquals(first, containmentIndex.containmentLastUpdated(transaction1, parent1.getFedoraId()));
703        assertNotEquals(allTime, containmentIndex.containmentLastUpdated(transaction1, parent1.getFedoraId()));
704
705        containmentIndex.commitTransaction(transaction1);
706        assertEquals(0, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
707        final var last = containmentIndex.containmentLastUpdated(shortLivedTx, parent1.getFedoraId());
708        assertNotNull(last);
709        assertNotEquals(first, last);
710        assertNotEquals(allTime, last);
711    }
712
713    @Test
714    public void testLargeContainment() {
715        stubObject("transaction1");
716        stubObject("parent1");
717        containmentIndex.setContainsLimit(5);
718        final List<String> expectedChildren = new ArrayList<>(10);
719        for (var i = 0; i < 10; i += 1) {
720            final FedoraId childId = parent1.getFedoraId().resolve("child_" + i);
721            expectedChildren.add(childId.getFullId());
722            containmentIndex.addContainedBy(transaction1, parent1.getFedoraId(), childId);
723        }
724        assertEquals(10, containmentIndex.getContains(transaction1, parent1.getFedoraId()).count());
725        assertEquals(0, containmentIndex.getContains(shortLivedTx, parent1.getFedoraId()).count());
726        containmentIndex.commitTransaction(transaction1);
727        final var foundChildren = containmentIndex.getContains(shortLivedTx, parent1.getFedoraId())
728                .collect(toList());
729        assertEquals(10, foundChildren.size());
730        assertEquals(expectedChildren, foundChildren);
731    }
732}
733
734