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