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