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 propertyStoreRoot the root of a filesystem into which properties will be 061 * serialized. 062 */ 063 public ExternalJsonSidecarExtraPropertyStore(final FedoraFileSystemConnector connector, 064 final DocumentTranslator translator, 065 final File propertyStoreRoot) { 066 this.connector = connector; 067 this.translator = translator; 068 this.propertyStoreRoot = propertyStoreRoot; 069 } 070 071 protected File sidecarFile(final String id) { 072 final File file; 073 if (connector.isRoot(id)) { 074 file = new File(propertyStoreRoot, "federation-root.modeshape.json"); 075 } else { 076 String ext = ".modeshape.json"; 077 if (connector.isContentNode(id)) { 078 ext = ".content.modeshape.json"; 079 } 080 final File f = new File(connector.fileFor(id).getAbsolutePath() + ext); 081 082 final Path propertyFileInFederation = f.getAbsoluteFile().toPath(); 083 final Path relativePath = connector.fileFor("/") 084 .getAbsoluteFile().toPath().relativize(propertyFileInFederation); 085 file = propertyStoreRoot.toPath().resolve(relativePath).toFile(); 086 } 087 if (!file.getParentFile().exists() && !file.getParentFile().mkdirs()) { 088 throw new RepositoryRuntimeException("Unable to create directories " + file.getParentFile() + "."); 089 } 090 return file; 091 } 092 093 /** 094 * This is a trivial reimplementation of the private Modeshape implementation in 095 * org.modeshape.connector.filesystem.JsonSidecarExtraPropertyStore 096 * 097 * See: https://github.com/ModeShape/modeshape/blob/modeshape-4.2.0.Final/modeshape-jcr/src/main/java/ 098 * org/modeshape/connector/filesystem/JsonSidecarExtraPropertyStore.java#L139 099 * 100 * @param id the identifier for the sidecar file 101 * @return whether the file was deleted 102 */ 103 @Override 104 public boolean removeProperties(final String id) { 105 try { 106 return deleteIfExists(sidecarFile(id).toPath()); 107 } catch (final IOException e) { 108 throw new RepositoryRuntimeException(id, e); 109 } 110 } 111 112 113 /** 114 * This is a trivial reimplementation of the private modeshape implementation in 115 * org.modeshape.connector.filesystem.JsonSidecarExtraPropertyStore 116 * 117 * See: https://github.com/ModeShape/modeshape/blob/modeshape-4.2.0.Final/modeshape-jcr/src/main/java/ 118 * org/modeshape/connector/filesystem/JsonSidecarExtraPropertyStore.java#L60 119 * 120 * @param id the identifier for the sidecar file 121 * @return a map of the properties associated with the given configuration 122 */ 123 @Override 124 public Map<Name, Property> getProperties(final String id) { 125 final File sidecarFile = sidecarFile(id); 126 if (!sidecarFile.exists()) { 127 return Collections.emptyMap(); 128 } 129 try (final FileInputStream sidecarStream = new FileInputStream(sidecarFile)) { 130 final Document document = Json.read(sidecarStream, false); 131 final Map<Name, Property> results = new HashMap<>(); 132 translator.getProperties(document, results); 133 return results; 134 } catch (final IOException e) { 135 throw new RepositoryRuntimeException(id, e); 136 } 137 } 138 139 /** 140 * This is a trivial reimplementation of the private modeshape implementation in 141 * org.modeshape.connector.filesystem.JsonSidecarExtraPropertyStore 142 * 143 * See: https://github.com/ModeShape/modeshape/blob/modeshape-4.2.0.Final/modeshape-jcr/src/main/java/ 144 * org/modeshape/connector/filesystem/JsonSidecarExtraPropertyStore.java#L74 145 * 146 * @param id the id for the sidecar file 147 * @param properties the keys/values to set in the specified sidecar configuration 148 */ 149 @Override 150 public void updateProperties(final String id, final Map<Name, Property> properties ) { 151 final File sidecarFile = sidecarFile(id); 152 try { 153 final EditableDocument document; 154 if (!sidecarFile.exists()) { 155 if (properties.isEmpty()) { 156 return; 157 } 158 sidecarFile.createNewFile(); 159 document = Schematic.newDocument(); 160 } else { 161 try (final FileInputStream sidecarStream = new FileInputStream(sidecarFile)) { 162 final Document existing = Json.read(sidecarStream, false); 163 document = Schematic.newDocument(existing); 164 } 165 } 166 properties.forEach((key, property) -> { 167 if (property == null) { 168 translator.removeProperty(document, key, null, null); 169 } else { 170 translator.setProperty(document, property, null, null); 171 } 172 }); 173 try (final FileOutputStream outputStream = new FileOutputStream(sidecarFile)) { 174 Json.write(document, outputStream); 175 } 176 } catch (final IOException e) { 177 throw new RepositoryRuntimeException(id, e); 178 } 179 } 180 181 /** 182 * This is a trivial reimplementation of the private modeshape implementation in 183 * org.modeshape.connector.filesystem.JsonSidecarExtraPropertyStore 184 * 185 * See: https://github.com/ModeShape/modeshape/blob/modeshape-4.2.0.Final/modeshape-jcr/src/main/java/ 186 * org/modeshape/connector/filesystem/JsonSidecarExtraPropertyStore.java#L102 187 * 188 * @param id the id for the sidecar file 189 * @param properties the keys/values to set in the specified sidecar configuration 190 */ 191 @Override 192 public void storeProperties(final String id, final Map<Name, Property> properties ) { 193 final File sidecarFile = sidecarFile(id); 194 try { 195 if (!sidecarFile.exists()) { 196 if (properties.isEmpty()) { 197 return; 198 } 199 sidecarFile.createNewFile(); 200 } 201 final EditableDocument document = Schematic.newDocument(); 202 for (final Property property : properties.values()) { 203 if (property == null) { 204 continue; 205 } 206 translator.setProperty(document, property, null, null); 207 } 208 try (final FileOutputStream outputStream = new FileOutputStream(sidecarFile)) { 209 Json.write(document, outputStream); 210 } 211 } catch (final IOException e) { 212 throw new RepositoryRuntimeException(id, e); 213 } 214 } 215 216 /** 217 * This is a trivial reimplementation of the private modeshape implementation in 218 * org.modeshape.connector.filesystem.JsonSidecarExtraPropertyStore 219 * 220 * See: https://github.com/ModeShape/modeshape/blob/modeshape-4.2.0.Final/modeshape-jcr/src/main/java/ 221 * org/modeshape/connector/filesystem/JsonSidecarExtraPropertyStore.java#L156 222 * 223 * @param id the id for the sidecar file 224 * @return whether the specified sidecar configuration exists 225 */ 226 @Override 227 public boolean contains(final String id) { 228 return sidecarFile(id).exists(); 229 } 230 231}