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}