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}