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}