it.tidalwave.bluemarine2.persistence.impl.DefaultPersistence.java Source code

Java tutorial

Introduction

Here is the source code for it.tidalwave.bluemarine2.persistence.impl.DefaultPersistence.java

Source

/*
 * #%L
 * *********************************************************************************************************************
 *
 * blueMarine2 - Semantic Media Center
 * http://bluemarine2.tidalwave.it - git clone https://bitbucket.org/tidalwave/bluemarine2-src.git
 * %%
 * Copyright (C) 2015 - 2017 Tidalwave s.a.s. (http://tidalwave.it)
 * %%
 *
 * *********************************************************************************************************************
 *
 * 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.
 *
 * *********************************************************************************************************************
 *
 * $Id$
 *
 * *********************************************************************************************************************
 * #L%
 */
package it.tidalwave.bluemarine2.persistence.impl;

import it.tidalwave.bluemarine2.util.SortingRDFHandler;
import javax.annotation.Nonnull;
import javax.inject.Inject;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Reader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.apache.commons.io.FileUtils;
import com.google.common.annotations.VisibleForTesting;
import org.eclipse.rdf4j.repository.Repository;
import org.eclipse.rdf4j.repository.RepositoryConnection;
import org.eclipse.rdf4j.repository.RepositoryException;
import org.eclipse.rdf4j.repository.sail.SailRepository;
import org.eclipse.rdf4j.sail.Sail;
import org.eclipse.rdf4j.sail.memory.MemoryStore;
import org.eclipse.rdf4j.sail.nativerdf.NativeStore;
import org.eclipse.rdf4j.rio.RDFFormat;
import org.eclipse.rdf4j.rio.RDFHandler;
import org.eclipse.rdf4j.rio.RDFHandlerException;
import org.eclipse.rdf4j.rio.RDFParseException;
import org.eclipse.rdf4j.rio.n3.N3Writer;
import it.tidalwave.util.TypeSafeMap8;
import it.tidalwave.messagebus.MessageBus;
import it.tidalwave.messagebus.annotation.ListensTo;
import it.tidalwave.messagebus.annotation.SimpleMessageSubscriber;
import it.tidalwave.bluemarine2.message.PersistenceInitializedNotification;
import it.tidalwave.bluemarine2.message.PowerOffNotification;
import it.tidalwave.bluemarine2.message.PowerOnNotification;
import it.tidalwave.bluemarine2.persistence.Persistence;
import lombok.extern.slf4j.Slf4j;
import static java.util.concurrent.TimeUnit.SECONDS;
import static java.nio.charset.StandardCharsets.UTF_8;
import static it.tidalwave.bluemarine2.persistence.PersistencePropertyNames.*;

/***********************************************************************************************************************
 *
 * @author  Fabrizio Giudici
 * @version $Id$
 *
 **********************************************************************************************************************/
@SimpleMessageSubscriber
@Slf4j
public class DefaultPersistence implements Persistence {
    @Inject
    private MessageBus messageBus;

    private final CountDownLatch initialized = new CountDownLatch(1);

    private Repository repository;

    @VisibleForTesting
    Sail sail;

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override
    @Nonnull
    public Repository getRepository() {
        waitForPowerOn();
        return repository;
    }

    /*******************************************************************************************************************
     *
     *
     ******************************************************************************************************************/
    @VisibleForTesting
    void onPowerOnNotification(final @ListensTo @Nonnull PowerOnNotification notification)
            throws RepositoryException, IOException, RDFParseException {
        log.info("onPowerOnNotification({})", notification);
        final TypeSafeMap8 properties = notification.getProperties();

        final Optional<Path> importFile = properties.getOptional(IMPORT_FILE);
        final Optional<Path> storageFolder = properties.getOptional(STORAGE_FOLDER);

        if (!storageFolder.isPresent()) {
            log.warn("No storage path: working in memory");
            sail = new MemoryStore();
        } else {
            log.info("Disk storage at {}", storageFolder);

            if (importFile.isPresent() && Files.exists(importFile.get())) {
                log.warn("Scratching store ...");
                FileUtils.deleteDirectory(storageFolder.get().toFile()); // FIXME: rename to backup folder with timestamp
            }

            sail = new NativeStore(storageFolder.get().toFile());
        }

        repository = new SailRepository(sail);
        repository.initialize();

        if (importFile.isPresent() && Files.exists(importFile.get())) {
            importFromFile(importFile.get());

            if (properties.getOptional(RENAME_IMPORT_FILE).orElse(false)) {
                Files.move(importFile.get(), Paths.get(importFile.get().toString() + "~"));
            }
        }

        initialized.countDown();
        messageBus.publish(new PersistenceInitializedNotification());
    }

    /*******************************************************************************************************************
     *
     *
     ******************************************************************************************************************/
    @VisibleForTesting
    void onPowerOffNotification(final @ListensTo @Nonnull PowerOffNotification notification)
            throws RepositoryException, IOException, RDFParseException {
        log.info("onPowerOffNotification({})", notification);

        if (repository != null) {
            repository.shutDown();
        }
    }

    /*******************************************************************************************************************
     *
     * Exports the repository to the given file.
     *
     * @param   path                    where to export the data to
     * @throws  RDFHandlerException
     * @throws  IOException
     * @throws  RepositoryException
     *
     ******************************************************************************************************************/
    @Override
    public void exportToFile(final @Nonnull Path path)
            throws RDFHandlerException, IOException, RepositoryException {
        log.info("exportToFile({})", path);
        Files.createDirectories(path.getParent());

        try (final PrintWriter pw = new PrintWriter(Files.newBufferedWriter(path, UTF_8));
                final RepositoryConnection connection = repository.getConnection()) {
            final RDFHandler writer = new SortingRDFHandler(new N3Writer(pw));

            //            FIXME: use Iterations - and sort
            //            for (final Namespace namespace : connection.getNamespaces().asList())
            //              {
            //                writer.handleNamespace(namespace.getPrefix(), namespace.getName());
            //              }

            writer.handleNamespace("bio", "http://purl.org/vocab/bio/0.1/");
            writer.handleNamespace("bmmo", "http://bluemarine.tidalwave.it/2015/04/mo/");
            writer.handleNamespace("dc", "http://purl.org/dc/elements/1.1/");
            writer.handleNamespace("foaf", "http://xmlns.com/foaf/0.1/");
            writer.handleNamespace("owl", "http://www.w3.org/2002/07/owl#");
            writer.handleNamespace("mo", "http://purl.org/ontology/mo/");
            writer.handleNamespace("rdfs", "http://www.w3.org/2000/01/rdf-schema#");
            writer.handleNamespace("rel", "http://purl.org/vocab/relationship/");
            writer.handleNamespace("vocab", "http://dbtune.org/musicbrainz/resource/vocab/");
            writer.handleNamespace("xs", "http://www.w3.org/2001/XMLSchema#");

            connection.export(writer);
        }
    }

    /*******************************************************************************************************************
     *
     *
     ******************************************************************************************************************/
    @Override
    public <E extends Exception> void runInTransaction(final @Nonnull TransactionalTask<E> task)
            throws E, RepositoryException {
        log.info("runInTransaction({})", task);
        waitForPowerOn();
        final long baseTime = System.nanoTime();

        try (final RepositoryConnection connection = repository.getConnection()) // TODO: pool?
        {
            task.run(connection);
            connection.commit();
        } catch (Exception e) {
            log.error("Transaction failed: {}", e.toString());
        }

        if (log.isDebugEnabled()) {
            log.debug(">>>> done in {} ms", (System.nanoTime() - baseTime) * 1E-6);
        }
    }

    /*******************************************************************************************************************
     *
     * Imports the repository from the given file.
     *
     * @param   path                    where to import the data from
     * @throws  RDFHandlerException
     * @throws  IOException
     * @throws  RepositoryException
     *
     ******************************************************************************************************************/
    private void importFromFile(final @Nonnull Path path)
            throws IOException, RepositoryException, RDFParseException {
        try (final RepositoryConnection connection = repository.getConnection();
                final Reader reader = Files.newBufferedReader(path, UTF_8)) {
            log.info("Importing repository from {} ...", path);
            connection.add(reader, path.toUri().toString(), RDFFormat.N3);
            connection.commit();
        }
    }

    /*******************************************************************************************************************
     *
     *
     ******************************************************************************************************************/
    private void waitForPowerOn() {
        try {
            if (!initialized.await(10, SECONDS)) {
                throw new IllegalStateException("Did not receive PowerOnNotification");
            }
        } catch (InterruptedException ex) {
            throw new IllegalStateException("Interrupted while waiting for PowerOnNotification");
        }
    }
}