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        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(), anyObject());
171            }
172        });
173        verify(mockTranslator, times(3)).setProperty(any(EditableDocument.class),
174                    any(Property.class), anyObject(), anyObject());
175    }
176
177
178    @Test
179    public void testUpdateProperties() throws IOException {
180        final File tmp = createTempDirectory("filesystem-federation").toFile();
181        tmp.deleteOnExit();
182
183        final ExternalJsonSidecarExtraPropertyStore store =
184                new ExternalJsonSidecarExtraPropertyStore(mockConnector, mockTranslator, tmp);
185
186        final Map<Name, Property> properties = new HashMap<>();
187
188        store.updateProperties("/file", properties);
189
190        assertFalse(store.contains("/file"));
191        assertTrue(store.getProperties("/file").isEmpty());
192
193        properties.put(KEY1, PROP1);
194        properties.put(KEY2, PROP2);
195        properties.put(KEY3, null);
196
197        store.updateProperties("/file", properties);
198
199        properties.forEach((key, property) -> {
200            if (property == null) {
201                verify(mockTranslator).removeProperty(any(EditableDocument.class), eq(key), anyObject(), anyObject());
202            } else {
203                verify(mockTranslator).setProperty(any(EditableDocument.class), eq(property), anyObject(), anyObject());
204            }
205        });
206    }
207
208    @Test
209    public void testUpdateExistingProperties() throws IOException {
210        final File tmp = createTempDirectory("filesystem-federation").toFile();
211        tmp.deleteOnExit();
212        final File sidecarFile = new File(tmp, "file.modeshape.json");
213        final String jsonString = "{}";
214        final FileOutputStream fos = new FileOutputStream(sidecarFile);
215        fos.write(jsonString.getBytes("UTF-8"));
216        fos.close();
217
218        final ExternalJsonSidecarExtraPropertyStore store =
219                new ExternalJsonSidecarExtraPropertyStore(mockConnector, mockTranslator, tmp);
220
221        assertTrue(store.contains("/file"));
222
223        final Map<Name, Property> properties = new HashMap<>();
224
225        properties.put(KEY1, PROP1);
226        properties.put(KEY2, PROP2);
227        properties.put(KEY3, null);
228
229        store.updateProperties("/file", properties);
230
231        properties.forEach((key, property) -> {
232            if (property == null) {
233                verify(mockTranslator).removeProperty(any(EditableDocument.class), eq(key), anyObject(), anyObject());
234            } else {
235                verify(mockTranslator).setProperty(any(EditableDocument.class), eq(property), anyObject(), anyObject());
236            }
237        });
238    }
239
240    @Test
241    public void testRemoveProperties() throws IOException {
242        final File tmp = createTempDirectory("filesystem-federation").toFile();
243        tmp.deleteOnExit();
244
245        final ExternalJsonSidecarExtraPropertyStore store =
246                new ExternalJsonSidecarExtraPropertyStore(mockConnector, mockTranslator, tmp);
247
248        final Map<Name, Property> properties = new HashMap<>();
249
250        properties.put(KEY1, PROP1);
251        properties.put(KEY2, PROP2);
252        properties.put(KEY3, PROP3);
253
254        store.updateProperties("/file", properties);
255        assertTrue(store.contains("/file"));
256
257        store.removeProperties("/file");
258        assertFalse(store.contains("/file"));
259    }
260
261    @Test
262    public void testGetProperties() throws IOException {
263        final File tmp = createTempDirectory("filesystem-federation").toFile();
264        tmp.deleteOnExit();
265        final File sidecarFile = new File(tmp, "file.content.modeshape.json");
266        final String jsonString = "{" +
267            "\"properties\" : { " +
268                "\"http://www.jcp.org/jcr/1.0\" : {" +
269                    "\"created\" : { " +
270                        "\"$date\" : \"2008-09-23T15:19:20.000-04:00\" } } ," +
271                "\"http://fedora.info/definitions/v4/repository#\" : {" +
272                    "\"digest\" : { " +
273                        "\"$uri\" : \"urn:sha1:6e1a2e24a4cc3dde495877019f53830b8f1d20e3\" } } } }";
274        final FileOutputStream fos = new FileOutputStream(sidecarFile);
275        fos.write(jsonString.getBytes("UTF-8"));
276        fos.close();
277
278        final ExternalJsonSidecarExtraPropertyStore store =
279                new ExternalJsonSidecarExtraPropertyStore(mockConnector, mockTranslator, tmp);
280
281        assertTrue(store.contains("/file/fcr:content"));
282
283        final FileInputStream sidecarStream = new FileInputStream(sidecarFile);
284        final Document document = Json.read(sidecarStream, false);
285        final Map<Name, Property> results = new HashMap<>();
286
287        store.getProperties("/file/fcr:content");
288
289        verify(mockTranslator).getProperties(eq(document), eq(results));
290    }
291
292    @Test(expected = RepositoryRuntimeException.class)
293    public void testGetPropertiesWithException() throws IOException {
294        final File tmp = createTempDirectory("filesystem-federation").toFile();
295        tmp.deleteOnExit();
296        final File sidecarFile = new File(tmp, "file.modeshape.json");
297        final String jsonString = "{ THIS ISN'T JSON !";
298        final FileOutputStream fos = new FileOutputStream(sidecarFile);
299        fos.write(jsonString.getBytes("UTF-8"));
300        fos.close();
301
302        final ExternalJsonSidecarExtraPropertyStore store =
303                new ExternalJsonSidecarExtraPropertyStore(mockConnector, mockTranslator, tmp);
304
305        store.getProperties("/file");
306    }
307
308}