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}