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 */ 018 019package org.fcrepo.kernel.impl.models; 020 021import static java.net.URI.create; 022import static org.apache.jena.rdf.model.ModelFactory.createDefaultModel; 023import static org.apache.jena.rdf.model.ResourceFactory.createResource; 024import static org.apache.jena.vocabulary.RDF.type; 025import static org.fcrepo.kernel.api.FedoraTypes.FCR_VERSIONS; 026import static org.fcrepo.kernel.api.FedoraTypes.FEDORA_ID_PREFIX; 027import static org.fcrepo.kernel.api.RdfLexicon.BASIC_CONTAINER; 028import static org.fcrepo.kernel.api.RdfLexicon.FEDORA_BINARY; 029import static org.fcrepo.kernel.api.RdfLexicon.FEDORA_RESOURCE; 030import static org.fcrepo.kernel.api.RdfLexicon.NON_RDF_SOURCE; 031import static org.fcrepo.kernel.api.RdfLexicon.RESOURCE; 032import static org.fcrepo.kernel.api.RdfLexicon.VERSIONED_RESOURCE; 033import static org.fcrepo.kernel.api.RdfLexicon.VERSIONING_TIMEGATE_TYPE; 034import static org.fcrepo.kernel.api.rdf.DefaultRdfStream.fromModel; 035import static org.junit.Assert.assertEquals; 036import static org.junit.Assert.assertNull; 037import static org.mockito.ArgumentMatchers.any; 038import static org.mockito.ArgumentMatchers.eq; 039import static org.mockito.Mockito.doReturn; 040import static org.mockito.Mockito.spy; 041import static org.mockito.Mockito.when; 042 043import java.net.URI; 044import java.time.Instant; 045import java.util.ArrayList; 046import java.util.List; 047 048import org.fcrepo.kernel.api.Transaction; 049import org.fcrepo.kernel.api.identifiers.FedoraId; 050import org.fcrepo.kernel.api.models.FedoraResource; 051import org.fcrepo.kernel.api.models.ResourceFactory; 052import org.fcrepo.kernel.api.models.ResourceHeaders; 053import org.fcrepo.kernel.api.models.TimeMap; 054import org.fcrepo.kernel.api.services.VersionService; 055import org.fcrepo.persistence.api.PersistentStorageSession; 056import org.fcrepo.persistence.api.PersistentStorageSessionManager; 057 058import org.apache.jena.rdf.model.Model; 059import org.junit.Test; 060import org.junit.runner.RunWith; 061import org.mockito.Mock; 062import org.mockito.junit.MockitoJUnitRunner; 063 064/** 065 * @author pwinckles 066 */ 067@RunWith(MockitoJUnitRunner.Silent.class) 068public class FedoraResourceImplTest { 069 070 @Mock 071 private TimeMap timeMap; 072 073 @Mock 074 private PersistentStorageSessionManager sessionManager; 075 076 @Mock 077 private ResourceFactory resourceFactory; 078 079 @Mock 080 private PersistentStorageSession psSession; 081 082 @Mock 083 private ResourceHeaders headers; 084 085 @Mock 086 private Transaction transaction; 087 088 private static final String ID = "info:fedora/test"; 089 090 private static final FedoraId FEDORA_ID = FedoraId.create(ID); 091 092 @Test 093 public void findMementoWhenOnlyOneAndBeforeSearch() { 094 final var resource = resourceWithMockedTimeMap(); 095 expectMementos("20200309172117"); 096 final var match = resource.findMementoByDatetime(instant("20200309172118")); 097 assertEquals(FEDORA_ID_PREFIX + "/0", match.getId()); 098 } 099 100 @Test 101 public void findClosestMementoWhenMultiple() { 102 final var resource = resourceWithMockedTimeMap(); 103 expectMementos("20200309172117", "20200309172118", "20200309172119"); 104 final var match = resource.findMementoByDatetime(instant("20200309172118")); 105 assertEquals(FEDORA_ID_PREFIX + "/1", match.getId()); 106 } 107 108 @Test 109 public void findClosestMementoWhenMultipleNoneExact() { 110 final var resource = resourceWithMockedTimeMap(); 111 expectMementos("20200309172116", "20200309172117", "20200309172119"); 112 final var match = resource.findMementoByDatetime(instant("20200309172118")); 113 assertEquals(FEDORA_ID_PREFIX + "/1", match.getId()); 114 } 115 116 @Test 117 public void findClosestMementoMultipleSameSecond() { 118 final var resource = resourceWithMockedTimeMap(); 119 expectMementos("20200309172117", "20200309172117", "20200309172117"); 120 final var match = resource.findMementoByDatetime(instant("20200309172118")); 121 assertEquals(FEDORA_ID_PREFIX + "/2", match.getId()); 122 } 123 124 @Test 125 public void findMementoWhenNonBeforeSearch() { 126 final var resource = resourceWithMockedTimeMap(); 127 expectMementos("20200309172119", "20200309172120", "20200309172121"); 128 final var match = resource.findMementoByDatetime(instant("20200309172118")); 129 assertEquals(FEDORA_ID_PREFIX + "/0", match.getId()); 130 } 131 132 @Test 133 public void findNoMementoWhenThereAreNone() { 134 final var resource = resourceWithMockedTimeMap(); 135 expectMementos(); 136 final var match = resource.findMementoByDatetime(instant("20200309172118")); 137 assertNull("Should not find a memento", match); 138 } 139 140 @Test 141 public void testTypesRdfSource() throws Exception { 142 final var subject = createResource(ID); 143 final String exampleType = "http://example.org/customType"; 144 final Model userModel = createDefaultModel(); 145 userModel.add(subject, type, createResource(exampleType)); 146 final var userStream = fromModel(subject.asNode(), userModel); 147 when(sessionManager.getReadOnlySession()).thenReturn(psSession); 148 when(psSession.getTriples(eq(FEDORA_ID), any())).thenReturn(userStream); 149 150 final List<URI> expectedTypes = List.of( 151 create(exampleType), 152 create(BASIC_CONTAINER.toString()), 153 create(RESOURCE.toString()), 154 create(FEDORA_RESOURCE.toString()), 155 create(VERSIONED_RESOURCE.getURI()), 156 create(VERSIONING_TIMEGATE_TYPE) 157 ); 158 159 when(transaction.isShortLived()).thenReturn(true); 160 161 final var resource = new FedoraResourceImpl(FEDORA_ID, transaction, sessionManager, resourceFactory); 162 resource.setInteractionModel(BASIC_CONTAINER.toString()); 163 resource.setIsArchivalGroup(false); 164 final var resourceTypes = resource.getTypes(); 165 166 // Initial lengths are the same 167 assertEquals(expectedTypes.size(), resourceTypes.size()); 168 // Only keep the types in the expected list. 169 resourceTypes.retainAll(expectedTypes); 170 // Lengths are still the same. 171 assertEquals(expectedTypes.size(), resourceTypes.size()); 172 } 173 174 @Test 175 public void testTypesNonRdfSource() throws Exception { 176 final var descriptionFedoraId = FEDORA_ID.asDescription(); 177 final var subject = createResource(ID); 178 final String exampleType = "http://example.org/customType"; 179 final Model userModel = createDefaultModel(); 180 userModel.add(subject, type, createResource(exampleType)); 181 final var userStream = fromModel(subject.asNode(), userModel); 182 183 final var description = new NonRdfSourceDescriptionImpl(descriptionFedoraId, null, sessionManager, 184 resourceFactory); 185 186 when(resourceFactory.getResource(any(Transaction.class), eq(descriptionFedoraId))).thenReturn(description); 187 when(sessionManager.getReadOnlySession()).thenReturn(psSession); 188 when(psSession.getTriples(eq(descriptionFedoraId), any())).thenReturn(userStream); 189 190 final List<URI> expectedTypes = List.of( 191 create(exampleType), 192 create(NON_RDF_SOURCE.toString()), 193 create(RESOURCE.toString()), 194 create(FEDORA_RESOURCE.toString()), 195 create(FEDORA_BINARY.toString()), 196 create(VERSIONED_RESOURCE.getURI()), 197 create(VERSIONING_TIMEGATE_TYPE) 198 ); 199 200 when(transaction.isShortLived()).thenReturn(true); 201 202 final var resource = new BinaryImpl(FEDORA_ID, transaction, sessionManager, resourceFactory); 203 resource.setInteractionModel(NON_RDF_SOURCE.toString()); 204 resource.setIsArchivalGroup(false); 205 final var resourceTypes = resource.getTypes(); 206 207 // Initial lengths are the same 208 assertEquals(expectedTypes.size(), resourceTypes.size()); 209 // Only keep the types in the expected list. 210 resourceTypes.retainAll(expectedTypes); 211 // Lengths are still the same. 212 assertEquals(expectedTypes.size(), resourceTypes.size()); 213 } 214 215 @Test 216 public void testGetChildren() { 217 final var resource = new FedoraResourceImpl(FEDORA_ID, null, sessionManager, resourceFactory); 218 assertEquals(0, resource.getChildren().count()); 219 } 220 221 private void expectMementos(final String... instants) { 222 final var mementos = new ArrayList<FedoraResource>(instants.length); 223 for (int i = 0; i < instants.length; i++) { 224 mementos.add(memento(String.valueOf(i), instant(instants[i]))); 225 } 226 when(timeMap.getChildren()).thenReturn(mementos.stream()); 227 } 228 229 private FedoraResource resourceWithMockedTimeMap() { 230 final var resource = spy(new FedoraResourceImpl(FEDORA_ID, null, sessionManager, resourceFactory)); 231 doReturn(timeMap).when(resource).getTimeMap(); 232 return resource; 233 } 234 235 private FedoraResource memento(final String id, final Instant instant) { 236 final String mementoTime = VersionService.MEMENTO_LABEL_FORMATTER.format(instant); 237 final FedoraId fedoraID = FedoraId.create(id, FCR_VERSIONS, mementoTime); 238 final var memento = new FedoraResourceImpl(fedoraID, null, sessionManager, resourceFactory); 239 memento.setIsMemento(true); 240 memento.setMementoDatetime(instant); 241 return memento; 242 } 243 244 private Instant instant(final String timestamp) { 245 return Instant.from(VersionService.MEMENTO_LABEL_FORMATTER.parse(timestamp)); 246 } 247 248}