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