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.connector.file;
019
020import static java.nio.file.Files.createTempDirectory;
021import static java.nio.file.Files.createTempFile;
022import static org.fcrepo.kernel.api.FedoraTypes.CONTENT_DIGEST;
023import static org.fcrepo.kernel.api.RdfLexicon.REPOSITORY_NAMESPACE;
024import static org.junit.Assert.assertEquals;
025import static org.junit.Assert.assertNotNull;
026import static org.junit.Assert.assertNull;
027import static org.junit.Assert.assertTrue;
028import static org.mockito.Matchers.any;
029import static org.mockito.Matchers.anyString;
030import static org.mockito.Matchers.eq;
031import static org.mockito.Mockito.doReturn;
032import static org.mockito.Mockito.mock;
033import static org.mockito.Mockito.spy;
034import static org.mockito.Mockito.verify;
035import static org.mockito.Mockito.when;
036import static org.modeshape.jcr.api.JcrConstants.JCR_DATA;
037import static org.modeshape.jcr.api.JcrConstants.NT_FILE;
038import static org.modeshape.jcr.api.JcrConstants.NT_FOLDER;
039import static org.modeshape.jcr.api.JcrConstants.NT_RESOURCE;
040import static org.slf4j.LoggerFactory.getLogger;
041import static org.springframework.test.util.ReflectionTestUtils.setField;
042
043import java.io.File;
044import java.io.FileOutputStream;
045import java.io.IOException;
046import java.nio.file.Path;
047
048import javax.jcr.NamespaceRegistry;
049
050import org.infinispan.schematic.document.Document;
051import org.junit.Before;
052import org.junit.BeforeClass;
053import org.junit.Test;
054import org.junit.runner.RunWith;
055import org.mockito.Mock;
056import org.mockito.runners.MockitoJUnitRunner;
057import org.modeshape.jcr.ExecutionContext;
058import org.modeshape.jcr.api.nodetype.NodeTypeManager;
059import org.modeshape.jcr.cache.document.DocumentTranslator;
060import org.modeshape.jcr.spi.federation.DocumentReader;
061import org.modeshape.jcr.spi.federation.ExtraPropertiesStore;
062import org.modeshape.jcr.value.BinaryValue;
063import org.modeshape.jcr.value.NameFactory;
064import org.modeshape.jcr.value.Property;
065import org.modeshape.jcr.value.ValueFactories;
066import org.modeshape.jcr.value.basic.BasicName;
067import org.modeshape.jcr.value.basic.BasicSingleValueProperty;
068import org.slf4j.Logger;
069
070/**
071 * @author Andrew Woods
072 *         Date: 2/3/14
073 */
074@RunWith(MockitoJUnitRunner.class)
075public class FedoraFileSystemConnectorTest {
076
077    private FedoraFileSystemConnector connector;
078
079    private static Path directoryPath;
080
081    private static File tmpFile;
082    private static File tmpFile2;
083    private static final String ROOT_PATH = "/";
084    private static final String PARENT_FIELD = "parent";
085    private static final String CHILD_FIELD = "children";
086
087    @Mock
088    private NamespaceRegistry mockRegistry;
089
090    @Mock
091    private NodeTypeManager mockNodeTypeManager;
092
093    @Mock
094    private DocumentTranslator mockTranslator;
095
096    @Mock
097    private NameFactory mockNameFactory;
098
099    @Mock
100    private ValueFactories mockValueFactories;
101
102    @Mock
103    private ExtraPropertiesStore mockExtraPropertiesStore;
104
105    @Mock
106    private Property binaryProperty;
107
108    @Mock
109    private BinaryValue binaryValue;
110
111    private final ExecutionContext mockContext = new ExecutionContext();
112
113    private static final Logger logger =
114        getLogger(FedoraFileSystemConnectorTest.class);
115
116    @BeforeClass
117    public static void beforeClass() throws IOException {
118        directoryPath = createTempDirectory("fedora-filesystemtest");
119        tmpFile =
120            createTempFile(directoryPath, "fedora-filesystemtestfile",
121                    "txt").toFile();
122        tmpFile.deleteOnExit();
123        try (FileOutputStream outputStream = new FileOutputStream(tmpFile)) {
124            outputStream.write("hello".getBytes());
125        } catch (final IOException e) {
126            logger.error("Error creating: {} - {}", tmpFile.getAbsolutePath(),
127                    e.getMessage());
128        }
129
130        tmpFile2 =
131            createTempFile(directoryPath, "fedora-filesystemtestfile",
132                    "txt").toFile();
133        tmpFile2.deleteOnExit();
134        try (FileOutputStream outputStream = new FileOutputStream(tmpFile2)) {
135            outputStream.write("goodbye".getBytes());
136        } catch (final IOException e) {
137            logger.error("Error creating: {} - {}", tmpFile2.getAbsolutePath(),
138                    e.getMessage());
139        }
140    }
141
142    @Before
143    public void setUp() throws IOException {
144
145        connector = new FedoraFileSystemConnector();
146        setField(connector, "directoryPath", directoryPath.toString());
147        setField(connector, "translator", mockTranslator);
148        setField(connector, "context", mockContext);
149        setField(connector, "extraPropertiesStore", mockExtraPropertiesStore);
150        setField(mockTranslator, "names", mockNameFactory);
151        setField(connector, "readonly", true);
152        connector.initialize(mockRegistry, mockNodeTypeManager);
153        mockContext.getNamespaceRegistry().register("fedora", REPOSITORY_NAMESPACE);
154        mockContext.getNamespaceRegistry().register("premis", "http://www.loc.gov/premis/rdf/v1#");
155    }
156
157    @Test
158    public void testGetDocumentByIdNull() {
159        final Document doc = connector.getDocumentById(null);
160        assertNull(doc);
161    }
162
163    @Test
164    public void testGetDocumentByIdDatastream() {
165        when(mockTranslator.getPrimaryTypeName(any(Document.class)))
166                .thenReturn(NT_FILE);
167        when(mockNameFactory.create(anyString())).thenReturn(
168                new BasicName("", tmpFile.getName()));
169
170        final Document doc = connector.getDocumentById(ROOT_PATH + tmpFile.getName());
171        assertNotNull(doc);
172        assertTrue(doc.containsField(PARENT_FIELD));
173        assertTrue(doc.containsField(CHILD_FIELD));
174        assertEquals(ROOT_PATH, doc.getString(PARENT_FIELD));
175        assertNull(doc.getString(CHILD_FIELD));
176    }
177
178    @Test
179    public void testGetDocumentByIdObject() {
180        when(mockTranslator.getPrimaryTypeName(any(Document.class)))
181                .thenReturn(NT_FOLDER);
182        when(mockNameFactory.create(anyString())).thenReturn(
183                new BasicName("", tmpFile.getName()));
184
185        final Document doc = connector.getDocumentById(ROOT_PATH + tmpFile.getName());
186        assertNotNull(doc);
187        assertTrue(doc.containsField(PARENT_FIELD));
188        assertTrue(doc.containsField(CHILD_FIELD));
189        assertEquals(ROOT_PATH, doc.getString(PARENT_FIELD));
190        assertNull(doc.getString(CHILD_FIELD));
191    }
192
193    @Test
194    public void testGetDocumentByIdNone() {
195        when(mockTranslator.getPrimaryTypeName(any(Document.class)))
196                .thenReturn("");
197        when(mockNameFactory.create(anyString())).thenReturn(
198                new BasicName("", tmpFile.getName()));
199
200        final Document doc = connector.getDocumentById(ROOT_PATH + tmpFile.getName());
201        assertNotNull(doc);
202        assertTrue(doc.containsField(PARENT_FIELD));
203        assertTrue(doc.containsField(CHILD_FIELD));
204        assertEquals(ROOT_PATH, doc.getString(PARENT_FIELD));
205        assertNull(doc.getString(CHILD_FIELD));
206    }
207
208    @Test
209    public void testGetDocumentByIdContent() {
210        when(mockTranslator.getPrimaryTypeName(any(Document.class)))
211                .thenReturn(NT_RESOURCE);
212        when(mockNameFactory.create(anyString())).thenReturn(
213                new BasicName("", tmpFile.getName()));
214
215        when(binaryProperty.getFirstValue()).thenReturn(binaryValue);
216        when(mockTranslator.getProperty(any(Document.class), eq(JCR_DATA)))
217                .thenReturn(binaryProperty);
218
219        final Document doc = connector.getDocumentById(ROOT_PATH + tmpFile.getName());
220
221        assertNotNull(doc);
222        assertTrue(doc.containsField(PARENT_FIELD));
223        assertTrue(doc.containsField(CHILD_FIELD));
224        assertEquals(ROOT_PATH, doc.getString(PARENT_FIELD));
225        assertNull(doc.getString(CHILD_FIELD));
226    }
227
228    @Test
229    public void testSha1WhenContentDigestIsCached() {
230        when(mockTranslator.getPrimaryTypeName(any(Document.class)))
231                .thenReturn(NT_RESOURCE);
232        when(mockNameFactory.create(anyString())).thenReturn(new BasicName("", tmpFile.getName()));
233        when(binaryProperty.getFirstValue()).thenReturn(binaryValue);
234        when(mockTranslator.getProperty(any(Document.class), eq(JCR_DATA)))
235                .thenReturn(binaryProperty);
236
237        final String chksum = "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d";
238
239        when(mockTranslator.getProperty(any(Document.class), eq(CONTENT_DIGEST)))
240                .thenReturn(new BasicSingleValueProperty(new BasicName("", CONTENT_DIGEST.toString()),
241                                     chksum));
242
243        final String sha1 = connector.sha1(tmpFile);
244
245        assertNotNull(sha1);
246        assert(sha1.contains(chksum));
247    }
248
249    @Test
250    public void testSha1ContentDigestIsNotCached() {
251        when(mockTranslator.getPrimaryTypeName(any(Document.class)))
252                .thenReturn(NT_RESOURCE);
253        when(mockNameFactory.create(anyString())).thenReturn(new BasicName("", tmpFile.getName()));
254        when(binaryProperty.getFirstValue()).thenReturn(binaryValue);
255        when(mockTranslator.getProperty(any(Document.class), eq(JCR_DATA)))
256                .thenReturn(binaryProperty);
257
258        final String chksum = "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d";
259
260        when(mockTranslator.getProperty(any(Document.class), eq(CONTENT_DIGEST)))
261                .thenReturn(null);
262
263        final String sha1 = connector.sha1(tmpFile);
264
265        assertNotNull(sha1);
266        assert(sha1.contains(chksum));
267    }
268
269    @Test
270    public void testRemoveDocument() {
271        final String id = "/" + tmpFile2.getName();
272        final FedoraFileSystemConnector spy = spy(connector);
273        assertTrue("Removing document should return true!", spy.removeDocument(id));
274        verify(spy).touchParent(id);
275    }
276
277    @Test
278    public void testStoreDocument() {
279        final String id = "/" + tmpFile.getName();
280        final DocumentReader reader = mock(DocumentReader.class);
281        final FedoraFileSystemConnector spy = spy(connector);
282        doReturn(tmpFile).when(spy).fileFor(anyString());
283        doReturn(reader).when(spy).readDocument(any(Document.class));
284        doReturn(id).when(reader).getDocumentId();
285        doReturn(NT_FILE).when(reader).getPrimaryTypeName();
286        spy.storeDocument(spy.getDocumentById(id));
287        verify(spy).touchParent(id);
288    }
289
290    @Test
291    public void testFileSystemConnectorReadOnly() {
292        assertTrue("FedoraFileSystemConnector is not read-only!", connector.isReadonly());
293    }
294}