001/*
002 * The contents of this file are subject to the license and copyright
003 * detailed in the LICENSE and NOTICE files at the root of the source
004 * tree.
005 */
006package org.fcrepo.kernel.impl.services;
007
008import static org.apache.jena.rdf.model.ModelFactory.createDefaultModel;
009import static org.fcrepo.kernel.api.FedoraTypes.FCR_METADATA;
010import static org.fcrepo.kernel.api.RdfCollectors.toModel;
011import static org.fcrepo.kernel.api.rdf.DefaultRdfStream.fromModel;
012import static org.junit.Assert.assertEquals;
013import static org.junit.Assert.assertTrue;
014import static org.junit.Assert.fail;
015import static org.mockito.Mockito.when;
016
017import java.util.List;
018import java.util.UUID;
019import java.util.stream.Collectors;
020
021import javax.inject.Inject;
022
023import org.fcrepo.kernel.api.RdfStream;
024import org.fcrepo.kernel.api.Transaction;
025import org.fcrepo.kernel.api.identifiers.FedoraId;
026import org.fcrepo.kernel.api.models.Binary;
027import org.fcrepo.kernel.api.models.FedoraResource;
028import org.fcrepo.kernel.api.models.NonRdfSourceDescription;
029import org.fcrepo.kernel.api.services.ReferenceService;
030import org.fcrepo.kernel.impl.TestTransactionHelper;
031
032import org.apache.jena.graph.Triple;
033import org.apache.jena.rdf.model.Model;
034import org.apache.jena.rdf.model.Property;
035import org.apache.jena.rdf.model.Resource;
036import org.apache.jena.rdf.model.ResourceFactory;
037import org.flywaydb.test.FlywayTestExecutionListener;
038import org.flywaydb.test.annotation.FlywayTest;
039import org.junit.Before;
040import org.junit.Test;
041import org.junit.runner.RunWith;
042import org.mockito.Mock;
043import org.mockito.MockitoAnnotations;
044import org.springframework.test.context.ContextConfiguration;
045import org.springframework.test.context.TestExecutionListeners;
046import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
047import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
048
049/**
050 * Reference Service Tests
051 * @author whikloj
052 */
053@RunWith(SpringJUnit4ClassRunner.class)
054@ContextConfiguration("/containmentIndexTest.xml")
055@TestExecutionListeners({DependencyInjectionTestExecutionListener.class, FlywayTestExecutionListener.class })
056public class ReferenceServiceImplTest {
057
058    @Inject
059    private ReferenceService referenceService;
060
061    @Mock
062    private FedoraResource targetResource;
063
064    @Mock
065    private Binary binaryResource;
066
067    @Mock
068    private NonRdfSourceDescription binaryDescriptionResource;
069
070    @Mock
071    private Transaction transaction;
072
073    @Mock
074    private Transaction shortLivedTx;
075
076    private FedoraId subject1Id;
077
078    private FedoraId subject2Id;
079
080    private Resource subject1;
081
082    private Resource subject2;
083
084    private Resource target;
085
086    private static final Property referenceProp = ResourceFactory.createProperty("http://example.org/pointer");
087
088    private static final String TEST_USER = "someUser";
089
090    @Before
091    @FlywayTest
092    public void setUp() {
093        MockitoAnnotations.openMocks(this);
094        final String transactionId = UUID.randomUUID().toString();
095        transaction = TestTransactionHelper.mockTransaction(transactionId, false);
096        shortLivedTx = TestTransactionHelper.mockTransaction(UUID.randomUUID().toString(), true);
097        subject1Id = FedoraId.create(UUID.randomUUID().toString());
098        subject2Id = FedoraId.create(UUID.randomUUID().toString());
099        final FedoraId targetId = FedoraId.create(UUID.randomUUID().toString());
100        when(targetResource.getFedoraId()).thenReturn(targetId);
101        subject1 =  ResourceFactory.createResource(subject1Id.getFullId());
102        subject2 = ResourceFactory.createResource(subject2Id.getFullId());
103        target = ResourceFactory.createResource(targetId.getFullId());
104    }
105
106    @Test
107    public void testAddAReference() {
108        assertEquals(0, referenceService.getInboundReferences(shortLivedTx, targetResource).count());
109
110        final Model model = createDefaultModel();
111        model.add(subject1, referenceProp, target);
112        final RdfStream stream = fromModel(subject1.asNode(), model);
113        referenceService.updateReferences(transaction, subject1Id, TEST_USER, stream);
114        referenceService.commitTransaction(transaction);
115
116        final List<Triple> refs = referenceService.getInboundReferences(shortLivedTx, targetResource)
117                .collect(Collectors.toList());
118
119        assertEquals(1, refs.size());
120        assertEquals(subject1Id.getFullId(), refs.get(0).getSubject().getURI());
121        assertEquals(referenceProp.getURI(), refs.get(0).getPredicate().getURI());
122    }
123
124    @Test
125    public void testAddNoReference() {
126        assertEquals(0, referenceService.getInboundReferences(shortLivedTx, targetResource).count());
127        final Model model = createDefaultModel();
128        model.add(subject1, referenceProp, "http://some/text/uri");
129        final RdfStream stream = fromModel(subject1.asNode(), model);
130        referenceService.updateReferences(transaction, subject1Id, TEST_USER, stream);
131        referenceService.commitTransaction(transaction);
132        assertEquals(0, referenceService.getInboundReferences(shortLivedTx, targetResource).count());
133    }
134
135    @Test
136    public void testAdd2References() {
137        assertEquals(0, referenceService.getInboundReferences(shortLivedTx, targetResource).count());
138        final Model model = createDefaultModel();
139        model.add(subject1, referenceProp, target);
140        final RdfStream stream = fromModel(subject1.asNode(), model);
141        referenceService.updateReferences(transaction, subject1Id, TEST_USER, stream);
142        referenceService.commitTransaction(transaction);
143        final List<Triple> refs = referenceService.getInboundReferences(shortLivedTx, targetResource).collect(
144                Collectors.toList());
145        assertEquals(1, refs.size());
146        assertEquals(subject1Id.getFullId(), refs.get(0).getSubject().getURI());
147        // Now make another object reference the target.
148        model.remove(subject1, referenceProp, target);
149        model.add(subject2, referenceProp, target);
150        final RdfStream stream2 = fromModel(subject2.asNode(),
151                model);
152        referenceService.updateReferences(transaction, subject2Id, TEST_USER, stream2);
153        referenceService.commitTransaction(transaction);
154        final List<Triple> refs2 = referenceService.getInboundReferences(shortLivedTx, targetResource).collect(
155                Collectors.toList());
156        assertEquals(2, refs2.size());
157        for (final var r : refs2) {
158            if (!(subject2Id.getFullId().equals(r.getSubject().getURI()) ||
159                    subject1Id.getFullId().equals(r.getSubject().getURI()))) {
160                fail("Missing expected subject in reference");
161            }
162        }
163    }
164
165    @Test
166    public void testAddExternalReference() {
167        assertEquals(0, referenceService.getInboundReferences(shortLivedTx, targetResource).count());
168
169        final Model model = createDefaultModel();
170        final Resource external = ResourceFactory.createResource("http://someother.org/pointer");
171        model.add(external, referenceProp, target);
172        final RdfStream stream = fromModel(external.asNode(), model);
173
174        referenceService.updateReferences(transaction, subject1Id, TEST_USER, stream);
175        referenceService.commitTransaction(transaction);
176        assertEquals(0, referenceService.getInboundReferences(shortLivedTx, targetResource).count());
177
178        model.remove(external, referenceProp, target);
179        model.add(subject1, referenceProp, target);
180        final RdfStream stream2 = fromModel(subject1.asNode(), model);
181        referenceService.updateReferences(transaction, subject1Id, TEST_USER, stream2);
182        referenceService.commitTransaction(transaction);
183        assertEquals(1, referenceService.getInboundReferences(shortLivedTx, targetResource).count());
184
185    }
186
187    @Test
188    public void testRollback() {
189        assertEquals(0, referenceService.getInboundReferences(shortLivedTx, targetResource).count());
190
191        final Model model = createDefaultModel();
192        model.add(subject1, referenceProp, target);
193        final RdfStream stream = fromModel(subject1.asNode(), model);
194        referenceService.updateReferences(transaction, subject1Id, TEST_USER, stream);
195        // Still nothing outside the transaction.
196        assertEquals(0, referenceService.getInboundReferences(shortLivedTx, targetResource).count());
197        // One inside the transaction
198        assertEquals(1, referenceService.getInboundReferences(transaction, targetResource).count());
199        referenceService.rollbackTransaction(transaction);
200        // Still nothing outside or inside the transaction.
201        assertEquals(0, referenceService.getInboundReferences(shortLivedTx, targetResource).count());
202        assertEquals(0, referenceService.getInboundReferences(transaction, targetResource).count());
203    }
204
205    @Test
206    public void testCommit() {
207        assertEquals(0, referenceService.getInboundReferences(shortLivedTx, targetResource).count());
208
209        final Model model = createDefaultModel();
210        model.add(subject1, referenceProp, target);
211        final RdfStream stream = fromModel(subject1.asNode(), model);
212        referenceService.updateReferences(transaction, subject1Id, TEST_USER, stream);
213        // Still nothing outside the transaction.
214        assertEquals(0, referenceService.getInboundReferences(shortLivedTx, targetResource).count());
215        // One inside the transaction
216        assertEquals(1, referenceService.getInboundReferences(transaction, targetResource).count());
217        referenceService.commitTransaction(transaction);
218        // Now 1 outside or inside the transaction.
219        assertEquals(1, referenceService.getInboundReferences(shortLivedTx, targetResource).count());
220        assertEquals(1, referenceService.getInboundReferences(transaction, targetResource).count());
221    }
222
223    @Test
224    public void commitTransactionNotExist() {
225        final String txID = UUID.randomUUID().toString();
226        when(transaction.getId()).thenReturn(txID);
227        referenceService.commitTransaction(transaction);
228    }
229
230    @Test
231    public void ensureNoCrossTransactionLeakage() {
232        assertEquals(0, referenceService.getInboundReferences(shortLivedTx, targetResource).count());
233
234        final Model model = createDefaultModel();
235        model.add(subject1, referenceProp, target);
236        final RdfStream stream = fromModel(subject1.asNode(), model);
237        referenceService.updateReferences(transaction, subject1Id, TEST_USER, stream);
238
239        final String transaction2Id = UUID.randomUUID().toString();
240        final Transaction transaction2 = TestTransactionHelper.mockTransaction(transaction2Id, false);
241        // Make both of these long-running.
242        when(transaction.isShortLived()).thenReturn(false);
243
244        // Still not public.
245        assertEquals(0, referenceService.getInboundReferences(shortLivedTx, targetResource).count());
246        // Available to current transaction
247        assertEquals(1, referenceService.getInboundReferences(transaction, targetResource).count());
248        // But not to other transactions.
249        assertEquals(0, referenceService.getInboundReferences(transaction2, targetResource).count());
250        referenceService.commitTransaction(transaction);
251        // Now all return the one.
252        assertEquals(1, referenceService.getInboundReferences(shortLivedTx, targetResource).count());
253        assertEquals(1, referenceService.getInboundReferences(transaction, targetResource).count());
254        assertEquals(1, referenceService.getInboundReferences(transaction2, targetResource).count());
255    }
256
257    @Test
258    public void testAddAndRemove() {
259        assertEquals(0, referenceService.getInboundReferences(shortLivedTx, targetResource).count());
260
261        // Add a reference.
262        final Model model = createDefaultModel();
263        model.add(subject1, referenceProp, target);
264        final RdfStream stream = fromModel(subject1.asNode(), model);
265        referenceService.updateReferences(transaction, subject1Id, TEST_USER, stream);
266        referenceService.commitTransaction(transaction);
267        assertEquals(1, referenceService.getInboundReferences(shortLivedTx, targetResource).count());
268
269        // Change the RDF to remove the reference.
270        final String transaction2Id = UUID.randomUUID().toString();
271        final Transaction transaction2 = TestTransactionHelper.mockTransaction(transaction2Id, false);
272        model.add(subject1, ResourceFactory.createProperty("http://someother/description"), "Some text");
273        model.remove(subject1, referenceProp, target);
274        final RdfStream stream2 = fromModel(subject1.asNode(), model);
275        referenceService.updateReferences(transaction2, subject1Id, TEST_USER, stream2);
276        // Reference still available outside transaction.
277        assertEquals(1, referenceService.getInboundReferences(shortLivedTx, targetResource).count());
278        // But gone inside the transaction
279        assertEquals(0, referenceService.getInboundReferences(transaction2, targetResource).count());
280        referenceService.commitTransaction(transaction2);
281        assertEquals(0, referenceService.getInboundReferences(shortLivedTx, targetResource).count());
282    }
283
284    @Test
285    public void testBinaryDescriptionListAllReferences() {
286        final FedoraId binaryId = FedoraId.create(UUID.randomUUID().toString());
287        final FedoraId descriptionId = binaryId.resolve(FCR_METADATA);
288        final Resource binaryUri = ResourceFactory.createResource(binaryId.getFullId());
289        final Resource binaryDescUri = ResourceFactory.createResource(descriptionId.getFullId());
290        when(binaryResource.getFedoraId()).thenReturn(binaryId);
291        when(binaryDescriptionResource.getFedoraId()).thenReturn(descriptionId);
292        when(binaryDescriptionResource.getDescribedResource()).thenReturn(binaryResource);
293        assertEquals(0, referenceService.getInboundReferences(shortLivedTx, binaryDescriptionResource).count());
294
295        // Add a reference to binary
296        final Model model = createDefaultModel();
297        model.add(subject1, referenceProp, binaryUri);
298        final RdfStream stream = fromModel(subject1.asNode(), model);
299        referenceService.updateReferences(transaction, subject1Id, TEST_USER, stream);
300        // Check before committing
301        assertEquals(0, referenceService.getInboundReferences(shortLivedTx, binaryDescriptionResource).count());
302        assertEquals(1, referenceService.getInboundReferences(transaction, binaryDescriptionResource)
303                .count());
304        // Commit
305        referenceService.commitTransaction(transaction);
306        assertEquals(1, referenceService.getInboundReferences(shortLivedTx, binaryDescriptionResource).count());
307
308        // Add a second to the description.
309        final Model model2 = createDefaultModel();
310        model2.add(subject2, referenceProp, binaryDescUri);
311        final RdfStream stream2 = fromModel(subject2.asNode(), model2);
312        referenceService.updateReferences(transaction, subject2Id, TEST_USER, stream2);
313        // One reference outside the transaction
314        assertEquals(1, referenceService.getInboundReferences(shortLivedTx, binaryDescriptionResource).count());
315        // Two inside
316        assertEquals(2, referenceService.getInboundReferences(transaction, binaryDescriptionResource)
317                .count());
318        // Commit the transaction
319        referenceService.commitTransaction(transaction);
320
321        // Verify both the reference to the binary and the description are returned.
322        final Model rdfModel = referenceService.getInboundReferences(shortLivedTx, binaryDescriptionResource).collect(
323                toModel());
324        assertTrue(rdfModel.contains(subject1, referenceProp, binaryUri));
325        assertTrue(rdfModel.contains(subject2, referenceProp, binaryDescUri));
326    }
327
328    @Test
329    public void testReferencesFromTwoSources() throws Exception {
330        assertEquals(0, referenceService.getInboundReferences(shortLivedTx, targetResource).count());
331
332        // Add a reference.
333        final Model model = createDefaultModel();
334        model.add(subject1, referenceProp, target);
335        final RdfStream stream = fromModel(subject1.asNode(), model);
336        referenceService.updateReferences(transaction, subject1Id, TEST_USER, stream);
337        referenceService.commitTransaction(transaction);
338        assertEquals(1, referenceService.getInboundReferences(shortLivedTx, targetResource).count());
339
340        // Add the same reference from another resource.
341        final RdfStream stream2 = fromModel(subject1.asNode(), model);
342        referenceService.updateReferences(transaction, subject2Id, TEST_USER, stream2);
343        referenceService.commitTransaction(transaction);
344        assertEquals(2, referenceService.getInboundReferences(shortLivedTx, targetResource).count());
345        final Triple expectedTriple = Triple.create(subject1.asNode(), referenceProp.asNode(), target.asNode());
346        assertTrue(referenceService.getInboundReferences(shortLivedTx, targetResource)
347                .allMatch(t -> t.equals(expectedTriple)));
348        // Delete the first resource and see the second reference remains.
349        referenceService.deleteAllReferences(transaction, subject1Id);
350        referenceService.commitTransaction(transaction);
351        assertEquals(1, referenceService.getInboundReferences(shortLivedTx, targetResource).count());
352        assertTrue(referenceService.getInboundReferences(shortLivedTx, targetResource)
353                .allMatch(t -> t.equals(expectedTriple)));
354
355        // Delete the second resource and see all references gone.
356        referenceService.deleteAllReferences(transaction, subject2Id);
357        referenceService.commitTransaction(transaction);
358        assertEquals(0, referenceService.getInboundReferences(shortLivedTx, targetResource).count());
359    }
360}