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 static java.nio.file.Files.deleteIfExists; 019 020import org.fcrepo.kernel.api.exception.RepositoryRuntimeException; 021 022import org.infinispan.schematic.Schematic; 023import org.infinispan.schematic.document.Document; 024import org.infinispan.schematic.document.EditableDocument; 025import org.infinispan.schematic.document.Json; 026import org.modeshape.jcr.cache.document.DocumentTranslator; 027import org.modeshape.jcr.spi.federation.ExtraPropertiesStore; 028import org.modeshape.jcr.value.Name; 029import org.modeshape.jcr.value.Property; 030 031import java.util.Collections; 032import java.io.File; 033import java.nio.file.Path; 034import java.util.HashMap; 035import java.util.Map; 036import java.io.IOException; 037import java.io.FileInputStream; 038import java.io.FileOutputStream; 039 040/** 041 * An implementation of ExtraPropertyStore, based on 042 * org.modeshape.connector.filesystem.JsonSidecarExtraPropertyStore that stores the 043 * properties in a separate configured directory than the filesystem federation itself. 044 * 045 * @author Mike Durbin 046 * @author acoburn 047 * @author ajs6f 048 */ 049public class ExternalJsonSidecarExtraPropertyStore implements ExtraPropertiesStore { 050 051 private final FedoraFileSystemConnector connector; 052 053 private final File propertyStoreRoot; 054 055 private final DocumentTranslator translator; 056 057 /** 058 * Default constructor. 059 * @param connector the FileSystemConnector for which this class will store properties. 060 * @param translator the utility to translate properties to/from the JSON configuration 061 * @param propertyStoreRoot the root of a filesystem into which properties will be 062 * serialized. 063 */ 064 public ExternalJsonSidecarExtraPropertyStore(final FedoraFileSystemConnector connector, 065 final DocumentTranslator translator, 066 final File propertyStoreRoot) { 067 this.connector = connector; 068 this.translator = translator; 069 this.propertyStoreRoot = propertyStoreRoot; 070 } 071 072 protected File sidecarFile(final String id) { 073 final File file; 074 if (connector.isRoot(id)) { 075 file = new File(propertyStoreRoot, "federation-root.modeshape.json"); 076 } else { 077 String ext = ".modeshape.json"; 078 if (connector.isContentNode(id)) { 079 ext = ".content.modeshape.json"; 080 } 081 final File f = new File(connector.fileFor(id).getAbsolutePath() + ext); 082 083 final Path propertyFileInFederation = f.getAbsoluteFile().toPath(); 084 final Path relativePath = connector.fileFor("/") 085 .getAbsoluteFile().toPath().relativize(propertyFileInFederation); 086 file = propertyStoreRoot.toPath().resolve(relativePath).toFile(); 087 } 088 if (!file.getParentFile().exists() && !file.getParentFile().mkdirs()) { 089 throw new RepositoryRuntimeException("Unable to create directories " + file.getParentFile() + "."); 090 } 091 return file; 092 } 093 094 /** 095 * This is a trivial reimplementation of the private Modeshape implementation in 096 * org.modeshape.connector.filesystem.JsonSidecarExtraPropertyStore 097 * 098 * See: https://github.com/ModeShape/modeshape/blob/modeshape-4.2.0.Final/modeshape-jcr/src/main/java/ 099 * org/modeshape/connector/filesystem/JsonSidecarExtraPropertyStore.java#L139 100 * 101 * @param id the identifier for the sidecar file 102 * @return whether the file was deleted 103 */ 104 @Override 105 public boolean removeProperties(final String id) { 106 try { 107 return deleteIfExists(sidecarFile(id).toPath()); 108 } catch (final IOException e) { 109 throw new RepositoryRuntimeException(id, e); 110 } 111 } 112 113 114 /** 115 * This is a trivial reimplementation of the private modeshape implementation in 116 * org.modeshape.connector.filesystem.JsonSidecarExtraPropertyStore 117 * 118 * See: https://github.com/ModeShape/modeshape/blob/modeshape-4.2.0.Final/modeshape-jcr/src/main/java/ 119 * org/modeshape/connector/filesystem/JsonSidecarExtraPropertyStore.java#L60 120 * 121 * @param id the identifier for the sidecar file 122 * @return a map of the properties associated with the given configuration 123 */ 124 @Override 125 public Map<Name, Property> getProperties(final String id) { 126 final File sidecarFile = sidecarFile(id); 127 if (!sidecarFile.exists()) { 128 return Collections.emptyMap(); 129 } 130 try (final FileInputStream sidecarStream = new FileInputStream(sidecarFile)) { 131 final Document document = Json.read(sidecarStream, false); 132 final Map<Name, Property> results = new HashMap<>(); 133 translator.getProperties(document, results); 134 return results; 135 } catch (final IOException e) { 136 throw new RepositoryRuntimeException(id, e); 137 } 138 } 139 140 /** 141 * This is a trivial reimplementation of the private modeshape implementation in 142 * org.modeshape.connector.filesystem.JsonSidecarExtraPropertyStore 143 * 144 * See: https://github.com/ModeShape/modeshape/blob/modeshape-4.2.0.Final/modeshape-jcr/src/main/java/ 145 * org/modeshape/connector/filesystem/JsonSidecarExtraPropertyStore.java#L74 146 * 147 * @param id the id for the sidecar file 148 * @param properties the keys/values to set in the specified sidecar configuration 149 */ 150 @Override 151 public void updateProperties(final String id, final Map<Name, Property> properties ) { 152 final File sidecarFile = sidecarFile(id); 153 try { 154 final EditableDocument document; 155 if (!sidecarFile.exists()) { 156 if (properties.isEmpty()) { 157 return; 158 } 159 sidecarFile.createNewFile(); 160 document = Schematic.newDocument(); 161 } else { 162 try (final FileInputStream sidecarStream = new FileInputStream(sidecarFile)) { 163 final Document existing = Json.read(sidecarStream, false); 164 document = Schematic.newDocument(existing); 165 } 166 } 167 properties.forEach((key, property) -> { 168 if (property == null) { 169 translator.removeProperty(document, key, null, null); 170 } else { 171 translator.setProperty(document, property, null, null); 172 } 173 }); 174 try (final FileOutputStream outputStream = new FileOutputStream(sidecarFile)) { 175 Json.write(document, outputStream); 176 } 177 } catch (final IOException e) { 178 throw new RepositoryRuntimeException(id, e); 179 } 180 } 181 182 /** 183 * This is a trivial reimplementation of the private modeshape implementation in 184 * org.modeshape.connector.filesystem.JsonSidecarExtraPropertyStore 185 * 186 * See: https://github.com/ModeShape/modeshape/blob/modeshape-4.2.0.Final/modeshape-jcr/src/main/java/ 187 * org/modeshape/connector/filesystem/JsonSidecarExtraPropertyStore.java#L102 188 * 189 * @param id the id for the sidecar file 190 * @param properties the keys/values to set in the specified sidecar configuration 191 */ 192 @Override 193 public void storeProperties(final String id, final Map<Name, Property> properties ) { 194 final File sidecarFile = sidecarFile(id); 195 try { 196 if (!sidecarFile.exists()) { 197 if (properties.isEmpty()) { 198 return; 199 } 200 sidecarFile.createNewFile(); 201 } 202 final EditableDocument document = Schematic.newDocument(); 203 for (final Property property : properties.values()) { 204 if (property == null) { 205 continue; 206 } 207 translator.setProperty(document, property, null, null); 208 } 209 try (final FileOutputStream outputStream = new FileOutputStream(sidecarFile)) { 210 Json.write(document, outputStream); 211 } 212 } catch (final IOException e) { 213 throw new RepositoryRuntimeException(id, e); 214 } 215 } 216 217 /** 218 * This is a trivial reimplementation of the private modeshape implementation in 219 * org.modeshape.connector.filesystem.JsonSidecarExtraPropertyStore 220 * 221 * See: https://github.com/ModeShape/modeshape/blob/modeshape-4.2.0.Final/modeshape-jcr/src/main/java/ 222 * org/modeshape/connector/filesystem/JsonSidecarExtraPropertyStore.java#L156 223 * 224 * @param id the id for the sidecar file 225 * @return whether the specified sidecar configuration exists 226 */ 227 @Override 228 public boolean contains(final String id) { 229 return sidecarFile(id).exists(); 230 } 231 232}