
org.trellisldp.file.FileMementoService Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of trellis-file Show documentation
Show all versions of trellis-file Show documentation
The core components for a Trellis linked data server
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.trellisldp.file;
import static java.time.temporal.ChronoUnit.SECONDS;
import static java.util.Collections.emptySortedSet;
import static java.util.Collections.unmodifiableSortedSet;
import static java.util.concurrent.CompletableFuture.completedFuture;
import static java.util.concurrent.CompletableFuture.runAsync;
import static java.util.concurrent.CompletableFuture.supplyAsync;
import static org.slf4j.LoggerFactory.getLogger;
import static org.trellisldp.api.Resource.SpecialResources.MISSING_RESOURCE;
import java.io.File;
import java.nio.file.Path;
import java.time.Instant;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.CompletionStage;
import java.util.stream.Stream;
import javax.enterprise.inject.Alternative;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.rdf.api.IRI;
import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.ConfigProvider;
import org.slf4j.Logger;
import org.trellisldp.api.MementoService;
import org.trellisldp.api.Resource;
/**
* A file-based versioning system.
*/
@Alternative
public class FileMementoService implements MementoService {
/** The configuration key controlling the base filesystem path for memento storage. */
public static final String CONFIG_FILE_MEMENTO_PATH = "trellis.file.memento-path";
/** The configuration key controlling whether Memento versioning is enabled. */
public static final String CONFIG_FILE_MEMENTO = "trellis.file.memento";
private static final Logger LOGGER = getLogger(FileMementoService.class);
private final File directory;
private final boolean enabled;
/**
* Create a file-based memento service.
*/
public FileMementoService() {
this(ConfigProvider.getConfig());
}
private FileMementoService(final Config config) {
this(config.getValue(CONFIG_FILE_MEMENTO_PATH, String.class),
config.getOptionalValue(CONFIG_FILE_MEMENTO, Boolean.class).orElse(Boolean.TRUE));
}
/**
* Create a file-based memento service.
* @param path the file path
* @param enabled whether memento handling is enabled
*/
public FileMementoService(final String path, final boolean enabled) {
this.directory = new File(path);
this.enabled = enabled;
if (enabled) {
LOGGER.info("Storing Mementos as files at {}", path);
init();
}
}
@Override
public CompletionStage put(final Resource resource) {
return put(resource, resource.getModified());
}
/**
* Create a Memento from a resource at a particular time.
* @param resource the resource
* @param time the time to which the Memento corresponds
* @return the completion stage representing that the operation has completed
*/
public CompletionStage put(final Resource resource, final Instant time) {
if (enabled) {
return runAsync(() -> {
final File resourceDir = FileUtils.getResourceDirectory(directory, resource.getIdentifier());
if (!resourceDir.exists()) {
resourceDir.mkdirs();
}
FileUtils.writeMemento(resourceDir, resource, time.truncatedTo(SECONDS));
});
}
return completedFuture(null);
}
@Override
public CompletionStage get(final IRI identifier, final Instant time) {
if (enabled) {
return supplyAsync(() -> {
final Instant mementoTime = time.truncatedTo(SECONDS);
final File resourceDir = FileUtils.getResourceDirectory(directory, identifier);
final File file = FileUtils.getNquadsFile(resourceDir, mementoTime);
if (file.exists()) {
return new FileResource(identifier, file);
}
final SortedSet allMementos = listMementos(identifier);
if (allMementos.isEmpty()) {
return MISSING_RESOURCE;
}
final SortedSet possible = allMementos.headSet(mementoTime);
if (possible.isEmpty()) {
// In this case, the requested Memento is earlier than the set of all existing Mementos.
// Based on RFC 7089, Section 4.5.3 https://tools.ietf.org/html/rfc7089#section-4.5.3
// the first extant memento should therefore be returned.
return new FileResource(identifier, FileUtils.getNquadsFile(resourceDir, allMementos.first()));
}
return new FileResource(identifier, FileUtils.getNquadsFile(resourceDir, possible.last()));
});
}
return completedFuture(MISSING_RESOURCE);
}
@Override
public CompletionStage> mementos(final IRI identifier) {
if (enabled) {
return supplyAsync(() -> listMementos(identifier));
}
return completedFuture(emptySortedSet());
}
/**
* Delete a memento at the given time.
*
* @param identifier the resource identifier
* @param time the memento time
* @return the next stage of completion
*/
public CompletionStage delete(final IRI identifier, final Instant time) {
if (enabled) {
return runAsync(() -> {
final File resourceDir = FileUtils.getResourceDirectory(directory, identifier);
final File file = FileUtils.getNquadsFile(resourceDir, time.truncatedTo(SECONDS));
if (FileUtils.uncheckedDeleteIfExists(file.toPath())) {
LOGGER.debug("Deleted Memento {} at {}", identifier, file);
}
});
}
return completedFuture(null);
}
private void init() {
if (!directory.exists()) {
directory.mkdirs();
}
}
private SortedSet listMementos(final IRI identifier) {
final File resourceDir = FileUtils.getResourceDirectory(directory, identifier);
if (!resourceDir.exists()) {
return emptySortedSet();
}
final SortedSet instants = new TreeSet<>();
try (final Stream files = FileUtils.uncheckedList(resourceDir.toPath())) {
files.map(Path::toString).filter(path -> path.endsWith(".nq")).map(FilenameUtils::getBaseName)
.map(Long::parseLong).map(Instant::ofEpochSecond).map(t -> t.truncatedTo(SECONDS))
.forEach(instants::add);
}
return unmodifiableSortedSet(instants);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy