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