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