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.models; 007 008import static org.fcrepo.kernel.api.FedoraTypes.FEDORA_ID_PREFIX; 009import static org.fcrepo.kernel.api.RdfLexicon.BASIC_CONTAINER; 010import static org.fcrepo.kernel.api.RdfLexicon.DIRECT_CONTAINER; 011import static org.fcrepo.kernel.api.RdfLexicon.FEDORA_NON_RDF_SOURCE_DESCRIPTION_URI; 012import static org.fcrepo.kernel.api.RdfLexicon.INDIRECT_CONTAINER; 013import static org.fcrepo.kernel.api.RdfLexicon.NON_RDF_SOURCE; 014import static org.fcrepo.kernel.api.models.ExternalContent.PROXY; 015import static org.junit.Assert.assertEquals; 016import static org.junit.Assert.assertTrue; 017import static org.mockito.ArgumentMatchers.eq; 018import static org.mockito.ArgumentMatchers.nullable; 019import static org.mockito.Mockito.verify; 020import static org.mockito.Mockito.when; 021import static org.springframework.test.util.ReflectionTestUtils.setField; 022 023import static java.util.Arrays.asList; 024 025import java.net.URI; 026import java.time.Instant; 027import java.util.Collection; 028import java.util.UUID; 029import java.util.stream.Collectors; 030 031import javax.inject.Inject; 032 033import org.fcrepo.kernel.api.ContainmentIndex; 034import org.fcrepo.kernel.api.ReadOnlyTransaction; 035import org.fcrepo.kernel.api.Transaction; 036import org.fcrepo.kernel.api.exception.PathNotFoundException; 037import org.fcrepo.kernel.api.exception.RepositoryRuntimeException; 038import org.fcrepo.kernel.api.exception.ResourceTypeException; 039import org.fcrepo.kernel.api.identifiers.FedoraId; 040import org.fcrepo.kernel.api.models.Binary; 041import org.fcrepo.kernel.api.models.Container; 042import org.fcrepo.kernel.api.models.FedoraResource; 043import org.fcrepo.kernel.impl.TestTransactionHelper; 044import org.fcrepo.persistence.api.PersistentStorageSession; 045import org.fcrepo.persistence.api.PersistentStorageSessionManager; 046import org.fcrepo.persistence.api.exceptions.PersistentItemNotFoundException; 047import org.fcrepo.persistence.api.exceptions.PersistentStorageException; 048import org.fcrepo.persistence.common.ResourceHeadersImpl; 049 050import org.apache.jena.rdf.model.Resource; 051import org.apache.jena.rdf.model.ResourceFactory; 052import org.junit.After; 053import org.junit.Before; 054import org.junit.Test; 055import org.junit.runner.RunWith; 056import org.mockito.InjectMocks; 057import org.mockito.Mock; 058import org.mockito.MockitoAnnotations; 059import org.springframework.test.context.ContextConfiguration; 060import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 061 062/** 063 * @author bbpennel 064 * 065 */ 066@RunWith(SpringJUnit4ClassRunner.class) 067@ContextConfiguration("/containmentIndexTest.xml") 068public class ResourceFactoryImplTest { 069 070 private final static Instant CREATED_DATE = Instant.parse("2019-11-12T10:00:30.0Z"); 071 072 private final static String CREATED_BY = "user1"; 073 074 private final static Instant LAST_MODIFIED_DATE = Instant.parse("2019-11-12T14:11:05.0Z"); 075 076 private final static String LAST_MODIFIED_BY = "user2"; 077 078 private final static String STATE_TOKEN = "stately_value"; 079 080 private final static long CONTENT_SIZE = 100L; 081 082 private final static String MIME_TYPE = "text/plain"; 083 084 private final static String FILENAME = "testfile.txt"; 085 086 private final static URI DIGEST = URI.create("sha:12345"); 087 088 private final static Collection<URI> DIGESTS = asList(DIGEST); 089 090 private final static String RELATIVE_RESOURCE_PATH = "some/ocfl/path/v1/content/.fcrepo/fcr-container.json"; 091 092 @Mock 093 private PersistentStorageSessionManager sessionManager; 094 095 @Mock 096 private PersistentStorageSession psSession; 097 098 private ResourceHeadersImpl resourceHeaders; 099 100 private String fedoraIdStr; 101 102 private String sessionId; 103 104 private final FedoraId rootId = FedoraId.getRepositoryRootId(); 105 106 private FedoraId fedoraId; 107 108 private String fedoraMementoIdStr; 109 110 @Mock 111 private Transaction mockTx; 112 113 @Mock 114 private FedoraResource mockResource; 115 116 @Inject 117 private ContainmentIndex containmentIndex; 118 119 @InjectMocks 120 private ResourceFactoryImpl factory; 121 122 @Before 123 public void setup() throws Exception { 124 MockitoAnnotations.openMocks(this); 125 fedoraIdStr = FEDORA_ID_PREFIX + "/" + UUID.randomUUID().toString(); 126 fedoraId = FedoraId.create(fedoraIdStr); 127 fedoraMementoIdStr = fedoraIdStr + "/fcr:versions/20000102120000"; 128 129 sessionId = UUID.randomUUID().toString(); 130 mockTx = TestTransactionHelper.mockTransaction(sessionId, false); 131 132 factory = new ResourceFactoryImpl(); 133 134 setField(factory, "persistentStorageSessionManager", sessionManager); 135 setField(factory, "containmentIndex", containmentIndex); 136 137 resourceHeaders = new ResourceHeadersImpl(); 138 resourceHeaders.setId(fedoraId); 139 140 when(sessionManager.getSession(mockTx)).thenReturn(psSession); 141 when(sessionManager.getReadOnlySession()).thenReturn(psSession); 142 143 when(psSession.getHeaders(eq(fedoraId), nullable(Instant.class))).thenReturn(resourceHeaders); 144 } 145 146 @After 147 public void cleanUp() { 148 when(mockResource.getFedoraId()).thenReturn(rootId); 149 containmentIndex.reset(); 150 } 151 152 @Test(expected = PathNotFoundException.class) 153 public void getResource_ObjectNotFound() throws Exception { 154 when(psSession.getHeaders(fedoraId, null)).thenThrow(PersistentItemNotFoundException.class); 155 156 factory.getResource(mockTx, fedoraId); 157 } 158 159 @Test(expected = ResourceTypeException.class) 160 public void getResource_NoInteractionModel() throws Exception { 161 resourceHeaders.setInteractionModel(null); 162 163 factory.getResource(mockTx, fedoraId); 164 } 165 166 @Test(expected = ResourceTypeException.class) 167 public void getResource_UnknownInteractionModel() throws Exception { 168 resourceHeaders.setInteractionModel("http://example.com/mystery_stroop"); 169 170 factory.getResource(mockTx, fedoraId); 171 } 172 173 @Test 174 public void getResource_BasicContainer() throws Exception { 175 populateHeaders(resourceHeaders, BASIC_CONTAINER); 176 177 // Need to make a short lived transaction or the verify will fail. 178 final var shortTx = TestTransactionHelper.mockTransaction(sessionId, true); 179 final var resc = factory.getResource(ReadOnlyTransaction.INSTANCE, fedoraId); 180 181 assertTrue("Factory must return a container", resc instanceof Container); 182 assertEquals(fedoraIdStr, resc.getId()); 183 assertStateFieldsMatches(resc); 184 185 verify(sessionManager).getReadOnlySession(); 186 } 187 188 @Test 189 public void getResource_BasicContainer_WithParent() throws Exception { 190 populateHeaders(resourceHeaders, BASIC_CONTAINER); 191 192 final var parentId = FedoraId.create(FEDORA_ID_PREFIX + UUID.randomUUID().toString()); 193 resourceHeaders.setParent(parentId); 194 195 final var parentHeaders = new ResourceHeadersImpl(); 196 parentHeaders.setId(parentId); 197 populateHeaders(parentHeaders, DIRECT_CONTAINER); 198 199 when(psSession.getHeaders(parentId, null)).thenReturn(parentHeaders); 200 201 final var resc = factory.getResource(mockTx, fedoraId); 202 203 assertTrue("Factory must return a container", resc instanceof Container); 204 assertEquals(fedoraIdStr, resc.getId()); 205 assertStateFieldsMatches(resc); 206 207 final var parentResc = resc.getParent(); 208 assertTrue("Parent must be a container", parentResc instanceof Container); 209 assertEquals(parentId, parentResc.getFedoraId()); 210 assertStateFieldsMatches(parentResc); 211 } 212 213 @Test 214 public void getResource_BasicContainer_InTransaction() throws Exception { 215 populateHeaders(resourceHeaders, BASIC_CONTAINER); 216 217 final var resc = factory.getResource(mockTx, fedoraId); 218 219 assertTrue("Factory must return a container", resc instanceof Container); 220 assertEquals(fedoraIdStr, resc.getId()); 221 assertStateFieldsMatches(resc); 222 223 verify(sessionManager).getSession(mockTx); 224 } 225 226 @Test 227 public void getResource_BasicContainer_Cast_InTransaction() throws Exception { 228 populateHeaders(resourceHeaders, BASIC_CONTAINER); 229 230 final var resc = factory.getResource(mockTx, fedoraId, Container.class); 231 232 assertTrue("Factory must return a container", resc instanceof Container); 233 assertEquals(fedoraIdStr, resc.getId()); 234 assertStateFieldsMatches(resc); 235 236 verify(sessionManager).getSession(mockTx); 237 } 238 239 @Test 240 public void getResource_DirectContainer() throws Exception { 241 populateHeaders(resourceHeaders, DIRECT_CONTAINER); 242 243 final var resc = factory.getResource(mockTx, fedoraId); 244 245 assertTrue("Factory must return a container", resc instanceof Container); 246 assertEquals(fedoraIdStr, resc.getId()); 247 assertStateFieldsMatches(resc); 248 } 249 250 @Test 251 public void getResource_IndirectContainer() throws Exception { 252 populateHeaders(resourceHeaders, INDIRECT_CONTAINER); 253 254 final var resc = factory.getResource(mockTx, fedoraId); 255 256 assertTrue("Factory must return a container", resc instanceof Container); 257 assertEquals(fedoraIdStr, resc.getId()); 258 assertStateFieldsMatches(resc); 259 } 260 261 @Test 262 public void getResource_Binary() throws Exception { 263 populateHeaders(resourceHeaders, NON_RDF_SOURCE); 264 populateInternalBinaryHeaders(resourceHeaders); 265 266 final var resc = factory.getResource(mockTx, fedoraId); 267 268 assertTrue("Factory must return a binary", resc instanceof Binary); 269 assertEquals(fedoraIdStr, resc.getId()); 270 assertStateFieldsMatches(resc); 271 assertBinaryFieldsMatch(resc); 272 } 273 274 @Test(expected = RepositoryRuntimeException.class) 275 public void getResource_Binary_StorageException() throws Exception { 276 when(psSession.getHeaders(fedoraId, null)).thenThrow(new PersistentStorageException("Boom")); 277 278 populateHeaders(resourceHeaders, NON_RDF_SOURCE); 279 populateInternalBinaryHeaders(resourceHeaders); 280 281 factory.getResource(mockTx, fedoraId); 282 } 283 284 @Test 285 public void getResource_ExternalBinary() throws Exception { 286 populateHeaders(resourceHeaders, NON_RDF_SOURCE); 287 populateInternalBinaryHeaders(resourceHeaders); 288 final String externalUrl = "http://example.com/stuff"; 289 resourceHeaders.setExternalUrl(externalUrl); 290 resourceHeaders.setExternalHandling(PROXY); 291 292 final var resc = factory.getResource(mockTx, fedoraId); 293 294 assertTrue("Factory must return a container", resc instanceof Binary); 295 assertEquals(fedoraIdStr, resc.getId()); 296 assertStateFieldsMatches(resc); 297 assertBinaryFieldsMatch(resc); 298 final var binary = (Binary) resc; 299 assertEquals(externalUrl, binary.getExternalURL()); 300 assertTrue(binary.isProxy()); 301 } 302 303 @Test 304 public void getNonRdfSourceDescription() throws Exception { 305 final FedoraId descFedoraId = FedoraId.create(FEDORA_ID_PREFIX + "/object1/fcr:metadata"); 306 307 final var headers = new ResourceHeadersImpl(); 308 headers.setId(descFedoraId); 309 populateHeaders(headers, ResourceFactory.createResource(FEDORA_NON_RDF_SOURCE_DESCRIPTION_URI)); 310 when(psSession.getHeaders(descFedoraId, null)).thenReturn(headers); 311 312 final var resc = factory.getResource(mockTx, descFedoraId); 313 assertTrue("Factory must return a NonRdfSourceDescripton", resc instanceof NonRdfSourceDescriptionImpl); 314 } 315 316 @Test 317 public void getChildren_NoChildren() throws Exception { 318 populateHeaders(resourceHeaders, BASIC_CONTAINER); 319 320 final var childrenStream = factory.getChildren(mockTx, fedoraId); 321 322 assertEquals(0, childrenStream.count()); 323 } 324 325 @Test 326 public void getChildren_DoesNotExist() throws Exception { 327 final var childrenStream = factory.getChildren(mockTx, fedoraId); 328 assertEquals(0, childrenStream.count()); 329 } 330 331 @Test 332 public void getChildren_WithChildren() throws Exception { 333 populateHeaders(resourceHeaders, BASIC_CONTAINER); 334 335 final var child1Id = FedoraId.create(UUID.randomUUID().toString()); 336 final var child1Headers = new ResourceHeadersImpl(); 337 child1Headers.setId(child1Id); 338 populateHeaders(child1Headers, BASIC_CONTAINER); 339 when(psSession.getHeaders(child1Id, null)).thenReturn(child1Headers); 340 341 final var childNestedId = FedoraId.create(UUID.randomUUID().toString()); 342 final var childNestedHeaders = new ResourceHeadersImpl(); 343 childNestedHeaders.setId(childNestedId); 344 populateHeaders(childNestedHeaders, BASIC_CONTAINER); 345 when(psSession.getHeaders(childNestedId, null)).thenReturn(childNestedHeaders); 346 347 final var child2Id = FedoraId.create(UUID.randomUUID().toString()); 348 final var child2Headers = new ResourceHeadersImpl(); 349 child2Headers.setId(child2Id); 350 populateHeaders(child2Headers, NON_RDF_SOURCE); 351 populateInternalBinaryHeaders(child2Headers); 352 when(psSession.getHeaders(child2Id, null)).thenReturn(child2Headers); 353 354 containmentIndex.addContainedBy(mockTx, rootId, fedoraId); 355 containmentIndex.addContainedBy(mockTx, fedoraId, child1Id); 356 containmentIndex.addContainedBy(mockTx, child1Id, childNestedId); 357 containmentIndex.addContainedBy(mockTx, fedoraId, child2Id); 358 containmentIndex.commitTransaction(mockTx); 359 360 final var childrenStream = factory.getChildren(mockTx, fedoraId); 361 final var childrenList = childrenStream.collect(Collectors.toList()); 362 363 assertEquals(2, childrenList.size()); 364 365 final var child1 = childrenList.stream().filter(c -> c.getFedoraId().equals(child1Id)).findFirst(); 366 assertTrue(child1.isPresent()); 367 assertTrue(child1.get() instanceof Container); 368 369 final var child2 = childrenList.stream().filter(c -> c.getFedoraId().equals(child2Id)).findFirst(); 370 assertTrue(child2.isPresent()); 371 assertTrue(child2.get() instanceof Binary); 372 } 373 374 private void assertStateFieldsMatches(final FedoraResource resc) { 375 assertEquals(CREATED_DATE, resc.getCreatedDate()); 376 assertEquals(CREATED_BY, resc.getCreatedBy()); 377 assertEquals(LAST_MODIFIED_DATE, resc.getLastModifiedDate()); 378 assertEquals(LAST_MODIFIED_BY, resc.getLastModifiedBy()); 379 assertEquals(STATE_TOKEN, resc.getStateToken()); 380 assertEquals(STATE_TOKEN, resc.getEtagValue()); 381 } 382 383 private static void populateHeaders(final ResourceHeadersImpl headers, final Resource ixModel) { 384 headers.setInteractionModel(ixModel.getURI()); 385 headers.setCreatedBy(CREATED_BY); 386 headers.setCreatedDate(CREATED_DATE); 387 headers.setLastModifiedBy(LAST_MODIFIED_BY); 388 headers.setLastModifiedDate(LAST_MODIFIED_DATE); 389 headers.setStateToken(STATE_TOKEN); 390 headers.setStorageRelativePath(RELATIVE_RESOURCE_PATH); 391 } 392 393 private void assertBinaryFieldsMatch(final FedoraResource resc) { 394 final Binary binary = (Binary) resc; 395 assertEquals(CONTENT_SIZE, binary.getContentSize()); 396 assertEquals(MIME_TYPE, binary.getMimeType()); 397 assertEquals(DIGEST, binary.getContentDigests().stream().findFirst().get()); 398 assertEquals(FILENAME, binary.getFilename()); 399 } 400 401 private static void populateInternalBinaryHeaders(final ResourceHeadersImpl headers) { 402 headers.setContentSize(CONTENT_SIZE); 403 headers.setMimeType(MIME_TYPE); 404 headers.setDigests(DIGESTS); 405 headers.setFilename(FILENAME); 406 } 407 408}