org.trellisldp.file.FileMementoService.java Source code

Java tutorial

Introduction

Here is the source code for org.trellisldp.file.FileMementoService.java

Source

/*
 * 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.Objects.requireNonNull;
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 javax.inject.Inject;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.rdf.api.IRI;
import org.apache.tamaya.ConfigurationProvider;
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_BASE_PATH = "trellis.file.memento.basepath";

    private static final Logger LOGGER = getLogger(FileMementoService.class);

    private final File directory;

    /**
     * Create a file-based memento service.
     */
    @Inject
    public FileMementoService() {
        this(ConfigurationProvider.getConfiguration().get(CONFIG_FILE_MEMENTO_BASE_PATH));
    }

    /**
     * Create a file-based memento service.
     * @param path the file path
     */
    public FileMementoService(final String path) {
        requireNonNull(path, "Memento base path is undefined!");
        LOGGER.info("Storing Mementos as files at {}", path);
        this.directory = new File(path);
        init();
    }

    @Override
    public CompletionStage<Void> 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<Void> put(final Resource resource, final Instant time) {
        return runAsync(() -> {
            final File resourceDir = FileUtils.getResourceDirectory(directory, resource.getIdentifier());
            if (!resourceDir.exists()) {
                resourceDir.mkdirs();
            }
            FileUtils.writeMemento(resourceDir, resource, time.truncatedTo(SECONDS));
        });
    }

    @Override
    public CompletionStage<Resource> get(final IRI identifier, final Instant time) {
        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<Instant> possible = listMementos(identifier).headSet(mementoTime);
            if (possible.isEmpty()) {
                return MISSING_RESOURCE;
            }
            return new FileResource(identifier, FileUtils.getNquadsFile(resourceDir, possible.last()));
        });
    }

    @Override
    public CompletionStage<SortedSet<Instant>> mementos(final IRI identifier) {
        return supplyAsync(() -> listMementos(identifier));
    }

    private void init() {
        if (!directory.exists()) {
            directory.mkdirs();
        }
    }

    private SortedSet<Instant> listMementos(final IRI identifier) {
        final File resourceDir = FileUtils.getResourceDirectory(directory, identifier);
        if (!resourceDir.exists()) {
            return emptySortedSet();
        }

        final SortedSet<Instant> instants = new TreeSet<>();
        try (final Stream<Path> 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);
    }
}