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