001/*
002 * Copyright 2015 DuraSpace, Inc.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.fcrepo.connector.file;
017
018import org.fcrepo.kernel.api.exception.RepositoryRuntimeException;
019import org.infinispan.schematic.document.Document;
020import org.infinispan.schematic.document.EditableDocument;
021import org.infinispan.schematic.document.Json;
022import org.junit.Before;
023import org.junit.Test;
024import org.junit.runner.RunWith;
025import org.mockito.Mock;
026import org.mockito.runners.MockitoJUnitRunner;
027import org.modeshape.jcr.cache.document.DocumentTranslator;
028import org.modeshape.jcr.value.Name;
029import org.modeshape.jcr.value.Property;
030import org.modeshape.jcr.value.basic.BasicName;
031import org.modeshape.jcr.value.basic.BasicSingleValueProperty;
032
033import java.io.File;
034import java.io.FileInputStream;
035import java.io.FileOutputStream;
036import java.io.IOException;
037import java.util.HashMap;
038import java.util.Map;
039
040import static java.nio.file.Files.createTempDirectory;
041import static org.junit.Assert.assertEquals;
042import static org.junit.Assert.assertFalse;
043import static org.junit.Assert.assertTrue;
044import static org.mockito.Matchers.any;
045import static org.mockito.Matchers.anyObject;
046import static org.mockito.Matchers.eq;
047import static org.mockito.Mockito.times;
048import static org.mockito.Mockito.verify;
049import static org.mockito.Mockito.when;
050
051/**
052 * @author Mike Durbin
053 */
054@RunWith(MockitoJUnitRunner.class)
055public class ExternalJsonSidecarExtraPropertyStoreTest {
056
057    @Mock
058    private FedoraFileSystemConnector mockConnector;
059
060    @Mock
061    private DocumentTranslator mockTranslator;
062
063    private static final String FEDERATION_ROOT = "/federation-root";
064    private static final String FILE_PATH = "/federation-root/file";
065    private static final Name KEY1 = new BasicName("info", "one");
066    private static final Name KEY2 = new BasicName("info", "two");
067    private static final Name KEY3 = new BasicName("info", "three");
068    private static final Name KEY4 = new BasicName("info", "four");
069    private static final Name LANG_PROP = new BasicName("lang", "de");
070    private static final Property PROP1 = new BasicSingleValueProperty(LANG_PROP, "eins");
071    private static final Property PROP2 = new BasicSingleValueProperty(LANG_PROP, "zwei");
072    private static final Property PROP3 = new BasicSingleValueProperty(LANG_PROP, "drei");
073
074    @Before
075    public void setUp() {
076        when(mockConnector.fileFor("/")).thenReturn(new File(FEDERATION_ROOT));
077        when(mockConnector.isContentNode("/")).thenReturn(false);
078        when(mockConnector.isRoot("/")).thenReturn(true);
079        when(mockConnector.fileFor("/file")).thenReturn(new File(FILE_PATH));
080        when(mockConnector.isContentNode("/file")).thenReturn(false);
081        when(mockConnector.fileFor("/file/fcr:content")).thenReturn(new File(FILE_PATH));
082        when(mockConnector.isContentNode("/file/fcr:content")).thenReturn(true);
083        when(mockConnector.fileFor("/test/test")).thenReturn(new File(FEDERATION_ROOT + "/test/test"));
084        when(mockConnector.isContentNode("/test/test")).thenReturn(false);
085    }
086
087    @Test
088    public void testSidecarFile() throws IOException {
089        final File tmp = createTempDirectory("filesystem-federation").toFile();
090        tmp.deleteOnExit();
091
092        final ExternalJsonSidecarExtraPropertyStore store =
093                new ExternalJsonSidecarExtraPropertyStore(mockConnector, mockTranslator, tmp);
094        assertEquals(new File(tmp, "federation-root.modeshape.json"), store.sidecarFile("/"));
095        assertEquals(new File(tmp, "file.modeshape.json"), store.sidecarFile("/file"));
096        assertEquals(new File(tmp, "file.content.modeshape.json"), store.sidecarFile("/file/fcr:content"));
097        assertEquals(new File(tmp + "/test", "test.modeshape.json"), store.sidecarFile("/test/test"));
098    }
099
100    @Test
101    public void testEmptyPropertyFile() throws IOException {
102        final File tmp = createTempDirectory("filesystem-federation").toFile();
103        tmp.deleteOnExit();
104
105        final ExternalJsonSidecarExtraPropertyStore store =
106                new ExternalJsonSidecarExtraPropertyStore(mockConnector, mockTranslator, tmp);
107
108        assertFalse(store.contains("/file"));
109        assertTrue(store.getProperties("/file").isEmpty());
110    }
111
112    @Test
113    public void testStoreProperties() throws IOException {
114        final File tmp = createTempDirectory("filesystem-federation").toFile();
115        tmp.deleteOnExit();
116
117        final ExternalJsonSidecarExtraPropertyStore store =
118                new ExternalJsonSidecarExtraPropertyStore(mockConnector, mockTranslator, tmp);
119
120        final Map<Name, Property> properties = new HashMap<>();
121
122        store.storeProperties("/file", properties);
123
124        assertFalse(store.contains("/file"));
125        assertTrue(store.getProperties("/file").isEmpty());
126
127        properties.put(KEY1, PROP1);
128        properties.put(KEY2, PROP2);
129        properties.put(KEY3, PROP3);
130        properties.put(KEY4, null);
131
132        store.storeProperties("/file", properties);
133        assertTrue(store.contains("/file"));
134
135        properties.forEach((key, property) -> {
136            if (property != null) {
137                verify(mockTranslator).setProperty(any(EditableDocument.class), eq(property), anyObject(), anyObject());
138            }
139        });
140        verify(mockTranslator, times(3)).setProperty(any(EditableDocument.class),
141                    any(Property.class), anyObject(), anyObject());
142    }
143
144    @Test
145    public void testStoreExistingProperties() throws IOException {
146        final File tmp = createTempDirectory("filesystem-federation").toFile();
147        final File sidecarFile = new File(tmp, "file.modeshape.json");
148        tmp.deleteOnExit();
149        final String jsonString = "{}";
150        try (final FileOutputStream fos = new FileOutputStream(sidecarFile)) {
151            fos.write(jsonString.getBytes("UTF-8"));
152            fos.close();
153
154            final ExternalJsonSidecarExtraPropertyStore store =
155                    new ExternalJsonSidecarExtraPropertyStore(mockConnector, mockTranslator, tmp);
156
157            final Map<Name, Property> properties = new HashMap<>();
158
159            assertTrue(store.contains("/file"));
160
161            properties.put(KEY1, PROP1);
162            properties.put(KEY2, PROP2);
163            properties.put(KEY3, PROP3);
164            properties.put(KEY4, null);
165
166            store.storeProperties("/file", properties);
167
168            properties.forEach((key, property) -> {
169                if (property != null) {
170                    verify(mockTranslator).setProperty(any(EditableDocument.class), eq(property), anyObject(),
171                            anyObject());
172                }
173            });
174            verify(mockTranslator, times(3)).setProperty(any(EditableDocument.class),
175                    any(Property.class), anyObject(), anyObject());
176        }
177    }
178
179
180    @Test
181    public void testUpdateProperties() throws IOException {
182        final File tmp = createTempDirectory("filesystem-federation").toFile();
183        tmp.deleteOnExit();
184
185        final ExternalJsonSidecarExtraPropertyStore store =
186                new ExternalJsonSidecarExtraPropertyStore(mockConnector, mockTranslator, tmp);
187
188        final Map<Name, Property> properties = new HashMap<>();
189
190        store.updateProperties("/file", properties);
191
192        assertFalse(store.contains("/file"));
193        assertTrue(store.getProperties("/file").isEmpty());
194
195        properties.put(KEY1, PROP1);
196        properties.put(KEY2, PROP2);
197        properties.put(KEY3, null);
198
199        store.updateProperties("/file", properties);
200
201        properties.forEach((key, property) -> {
202            if (property == null) {
203                verify(mockTranslator).removeProperty(any(EditableDocument.class), eq(key), anyObject(), anyObject());
204            } else {
205                verify(mockTranslator).setProperty(any(EditableDocument.class), eq(property), anyObject(), anyObject());
206            }
207        });
208    }
209
210    @Test
211    public void testUpdateExistingProperties() throws IOException {
212        final File tmp = createTempDirectory("filesystem-federation").toFile();
213        tmp.deleteOnExit();
214        final File sidecarFile = new File(tmp, "file.modeshape.json");
215        final String jsonString = "{}";
216        try (final FileOutputStream fos = new FileOutputStream(sidecarFile)) {
217            fos.write(jsonString.getBytes("UTF-8"));
218            fos.close();
219
220            final ExternalJsonSidecarExtraPropertyStore store =
221                    new ExternalJsonSidecarExtraPropertyStore(mockConnector, mockTranslator, tmp);
222
223            assertTrue(store.contains("/file"));
224
225            final Map<Name, Property> properties = new HashMap<>();
226
227            properties.put(KEY1, PROP1);
228            properties.put(KEY2, PROP2);
229            properties.put(KEY3, null);
230
231            store.updateProperties("/file", properties);
232
233            properties.forEach((key, property) -> {
234                if (property == null) {
235                    verify(mockTranslator).removeProperty(any(EditableDocument.class), eq(key), anyObject(),
236                            anyObject());
237                } else {
238                    verify(mockTranslator).setProperty(any(EditableDocument.class), eq(property), anyObject(),
239                            anyObject());
240                }
241            });
242        }
243    }
244
245    @Test
246    public void testRemoveProperties() throws IOException {
247        final File tmp = createTempDirectory("filesystem-federation").toFile();
248        tmp.deleteOnExit();
249
250        final ExternalJsonSidecarExtraPropertyStore store =
251                new ExternalJsonSidecarExtraPropertyStore(mockConnector, mockTranslator, tmp);
252
253        final Map<Name, Property> properties = new HashMap<>();
254
255        properties.put(KEY1, PROP1);
256        properties.put(KEY2, PROP2);
257        properties.put(KEY3, PROP3);
258
259        store.updateProperties("/file", properties);
260        assertTrue(store.contains("/file"));
261
262        store.removeProperties("/file");
263        assertFalse(store.contains("/file"));
264    }
265
266    @Test
267    public void testGetProperties() throws IOException {
268        final File tmp = createTempDirectory("filesystem-federation").toFile();
269        tmp.deleteOnExit();
270        final File sidecarFile = new File(tmp, "file.content.modeshape.json");
271        final String jsonString = "{" +
272            "\"properties\" : { " +
273                "\"http://www.jcp.org/jcr/1.0\" : {" +
274                    "\"created\" : { " +
275                        "\"$date\" : \"2008-09-23T15:19:20.000-04:00\" } } ," +
276                "\"http://fedora.info/definitions/v4/repository#\" : {" +
277                    "\"digest\" : { " +
278                        "\"$uri\" : \"urn:sha1:6e1a2e24a4cc3dde495877019f53830b8f1d20e3\" } } } }";
279        try (final FileOutputStream fos = new FileOutputStream(sidecarFile)) {
280            fos.write(jsonString.getBytes("UTF-8"));
281        }
282
283        final ExternalJsonSidecarExtraPropertyStore store =
284                new ExternalJsonSidecarExtraPropertyStore(mockConnector, mockTranslator, tmp);
285
286        assertTrue(store.contains("/file/fcr:content"));
287
288        try (final FileInputStream sidecarStream = new FileInputStream(sidecarFile)) {
289            final Document document = Json.read(sidecarStream, false);
290            final Map<Name, Property> results = new HashMap<>();
291
292            store.getProperties("/file/fcr:content");
293
294            verify(mockTranslator).getProperties(eq(document), eq(results));
295        }
296    }
297
298    @Test(expected = RepositoryRuntimeException.class)
299    public void testGetPropertiesWithException() throws IOException {
300        final File tmp = createTempDirectory("filesystem-federation").toFile();
301        tmp.deleteOnExit();
302        final File sidecarFile = new File(tmp, "file.modeshape.json");
303        final String jsonString = "{ THIS ISN'T JSON !";
304        try (final FileOutputStream fos = new FileOutputStream(sidecarFile)) {
305            fos.write(jsonString.getBytes("UTF-8"));
306        }
307
308        final ExternalJsonSidecarExtraPropertyStore store =
309                new ExternalJsonSidecarExtraPropertyStore(mockConnector, mockTranslator, tmp);
310
311        store.getProperties("/file");
312    }
313
314}