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}