Package org.apache.marmotta.kiwi.versioning.sail

Source Code of org.apache.marmotta.kiwi.versioning.sail.KiWiVersioningSail

/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.marmotta.kiwi.versioning.sail;

import org.apache.marmotta.commons.sesame.filter.AlwaysTrueFilter;
import org.apache.marmotta.commons.sesame.filter.SesameFilter;
import org.apache.marmotta.kiwi.model.rdf.KiWiResource;
import org.apache.marmotta.kiwi.sail.KiWiStore;
import org.apache.marmotta.kiwi.transactions.api.TransactionListener;
import org.apache.marmotta.kiwi.transactions.api.TransactionalSail;
import org.apache.marmotta.kiwi.transactions.model.TransactionData;
import org.apache.marmotta.kiwi.transactions.wrapper.TransactionalSailWrapper;
import org.apache.marmotta.kiwi.versioning.api.VersioningSail;
import org.apache.marmotta.kiwi.versioning.model.Version;
import org.apache.marmotta.kiwi.versioning.persistence.KiWiVersioningConnection;
import org.apache.marmotta.kiwi.versioning.persistence.KiWiVersioningPersistence;
import org.openrdf.model.Resource;
import org.openrdf.model.Statement;
import org.openrdf.model.URI;
import org.openrdf.repository.RepositoryException;
import org.openrdf.repository.RepositoryResult;
import org.openrdf.repository.sail.SailRepositoryConnection;
import org.openrdf.sail.SailConnection;
import org.openrdf.sail.SailException;
import org.openrdf.sail.StackableSail;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.SQLException;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;

/**
* A KiWi Stackable Sail offering versioning support for transactional sails. The versioning sail create a new
* version entry in the database whenever a transaction commits successfully (implemented through transaction listener).
* <p/>
* This SAIL offers three additional methods (need to be accessed directly):
* <ul>
*     <li>getSnapshot(Date) returns a read-only SailConnection operating on a historic snapshot of the repository,
*         i.e. it is possible to query the triple store with its state at any given point in history</li>
*     <li>getVersions() returns a list of all versions that have been tracked in the triple store</li>
*     <li>getVersions(Date from, Date to) returns a list of all versions that have been tracked between the given dates</li>
* </ul>
* Further functionalities like removing old versions or undoing certain versions are planned but currently not yet
* implemented.
* <p/>
* Author: Sebastian Schaffert
*/
public class KiWiVersioningSail extends TransactionalSailWrapper implements TransactionListener, VersioningSail {

    private static Logger log = LoggerFactory.getLogger(KiWiVersioningSail.class);

    protected final static long DEFAULT_CONNECTION_TIMEOUT = 20000L;

    private KiWiVersioningPersistence persistence;

    private Set<KiWiSnapshotConnection> activeSnapshots;

    private SesameFilter<Statement> filter;

    /**
     * Build a new {@link KiWiVersioningSail} based on the provided parent
     * {@link TransactionalSail}.
     *
     * @param parent
     *            the {@link TransactionalSail} to base the
     *            {@link KiWiVersioningSail} on.
     */
    public KiWiVersioningSail(TransactionalSail parent) {
        this(parent,new AlwaysTrueFilter<Statement>());
    }

    /**
     * Build a new selective {@link KiWiVersioningSail} based on the provided
     * parent {@link TransactionalSail}. Only {@link Statement}s that are
     * accepted by the filter are included in versioning.
     *
     * @param parent
     *            the {@link TransactionalSail} to base the
     *            {@link KiWiVersioningSail} on.
     * @param filter
     *            a {@link SesameFilter} to filter out {@link Statement}s that
     *            should not be versioned. Only a {@link Statement} that is
     *            accepted by this filter will be versioned.
     *
     * @see SesameFilter#accept(Object)
     */
    public KiWiVersioningSail(TransactionalSail parent, SesameFilter<Statement> filter) {
        super(parent);
        this.persistence = new KiWiVersioningPersistence(getBaseStore().getPersistence());
        this.activeSnapshots = new HashSet<KiWiSnapshotConnection>();
        this.filter = filter;

        parent.addTransactionListener(this);
    }


    /**
     * Ensure that the versioning tables are properly initialised in the database.
     *
     * @throws SailException
     */
    @Override
    public void initialize() throws SailException {
        super.initialize();

        try {
            persistence.initDatabase();
        } catch (SQLException e) {
            throw new SailException("error while initialising versioning tables in database",e);
        }
    }

    /**
     * Return the KiWi store that is at the base of the SAIL stack. Throws an IllegalArgumentException in case the base
     * store is not a KiWi store.
     *
     * @return
     */
    public KiWiStore getBaseStore() {
        StackableSail current = this;
        while(current != null && current.getBaseSail() instanceof StackableSail) {
            current = (StackableSail) current.getBaseSail();
        }
        if(current != null && current.getBaseSail() instanceof KiWiStore) {
            return (KiWiStore) current.getBaseSail();
        } else {
            throw new IllegalStateException("the base store is not a KiWiStore (type: "+current.getBaseSail().getClass().getCanonicalName()+")!");
        }
    }

    /**
     * Return the versioning persistence object underlying this SAIL.
     *
     * @return
     */
    public KiWiVersioningPersistence getPersistence() {
        return persistence;
    }

    /**
     * Called after a transaction has committed. This method will take the transaction data and convert it into a
     * Version object, which will then be persisted in the KiWiVersioningPersistence.
     * @param data
     */
    @Override
    public void afterCommit(TransactionData data) {
        if(data.getAddedTriples().size() > 0 || data.getRemovedTriples().size() > 0) {
            Version version = new Version();

            version.setCommitTime(data.getCommitTime());
            for(Statement stmt : data.getAddedTriples()) {
                if(filter.accept(stmt)) {
                    version.getAddedTriples().add(stmt);
                }
            }
            for(Statement stmt : data.getRemovedTriples()) {
                if(filter.accept(stmt)) {
                    version.getRemovedTriples().add(stmt);
                }
            }

            if(version.getAddedTriples().size() > 0 || version.getRemovedTriples().size() > 0) {

                try {
                    final KiWiVersioningConnection connection = persistence.getConnection();
                    try {
                        connection.storeVersion(version);
                        connection.commit();
                    } catch (SQLException ex) {
                        log.warn("could not store versioning information (error: {}); rolling back...", ex.getMessage());
                        connection.rollback();
                    } finally {
                        connection.close();
                    }
                } catch(SQLException ex) {
                    log.warn("could not store versioning information (error: {})", ex.getMessage());
                }
            }
        }
    }

    /**
     * Called before a transaction commits. The transaction data will contain all changes done in the transaction since
     * the last commit. This method should be used in case the transaction listener aims to perform additional activities
     * in the same transaction, like inserting or updating database tables.
     *
     * @param data
     */
    @Override
    public void beforeCommit(TransactionData data) {
        // do nothing, versioning is carried out after commit
    }

    /**
     * Called when a transaction rolls back.
     */
    @Override
    public void rollback(TransactionData data) {
        // do nothing, don't create a version
    }

    /**
     * Get a read-only snapshot of the repository at the given date. Returns a sail connection that
     * can be used to access the triple data. Any attempts to modify the underlying data will throw
     * a SailReadOnlyException.
     *
     * @param snapshotDate the date of which to take the snapshot; the snapshot will consist of all
     *                     triples that have been created before or at the date and deleted after that date
     *                     (or not deleted at all).
     * @return a read-only sail connection to access the data of the triple store at the given date
     */
    @Override
    public KiWiSnapshotConnection getSnapshot(Date snapshotDatethrows SailException {
        KiWiSnapshotConnection con = new KiWiSnapshotConnection(this,snapshotDate);
        activeSnapshots.add(con);
        return con;
    }

    /**
     * Unregister the snapshot connection, it has been cleaned up properly.
     * @param con
     */
    protected void closeSnapshotConnection(KiWiSnapshotConnection con) {
        synchronized (activeSnapshots) {
            activeSnapshots.remove(con);

            if(activeSnapshots.isEmpty()) {
                activeSnapshots.notifyAll();
            }
        }
    }

    /**
     * List all versions of this repository.
     *
     * @return
     */
    @Override
    public RepositoryResult<Version> listVersions()  throws SailException {
        try {
            final KiWiVersioningConnection connection = persistence.getConnection();
            return new RepositoryResult<Version>(connection.listVersions()) {
                @Override
                protected void handleClose() throws RepositoryException {
                    super.handleClose();

                    try {
                        connection.commit();
                        connection.close();
                    } catch (SQLException ex) {
                        throw new RepositoryException("database error while committing/closing connection");
                    }
                }
            };
        } catch(SQLException ex) {
            throw new SailException("database error while listing versions",ex);
        }
    }

    /**
     * List all versions of this repository between a start and end date.
     *
     * @return
     */
    @Override
    public RepositoryResult<Version> listVersions(Date from, Date to) throws SailException {
        try {
            final KiWiVersioningConnection connection = persistence.getConnection();
            return new RepositoryResult<Version>(connection.listVersions(from,to)) {
                @Override
                protected void handleClose() throws RepositoryException {
                    super.handleClose();

                    try {
                        connection.commit();
                        connection.close();
                    } catch (SQLException ex) {
                        throw new RepositoryException("database error while committing/closing connection");
                    }
                }
            };
        } catch(SQLException ex) {
            throw new SailException("database error while listing versions",ex);
        }
    }



    public Version getVersion(Long id) throws SailException {
        try {
            final KiWiVersioningConnection connection = persistence.getConnection();
            try {
                return connection.getVersion(id);
            } finally {
                connection.commit();
                connection.close();
            }

        } catch(SQLException ex) {
            throw new SailException("database error while listing versions",ex);
        }
    }


    /**
     * Remove the version with the id passed as argument, including all references to added and removed triples. The
     * triples themselves are not deleted immediately, we let the garbage collector carry this out periodically.
     * @param id  the database ID of the version (see {@link Version#getId()})
     * @throws SailException
     */
    public void removeVersion(Long id) throws SailException {
        try {
            final KiWiVersioningConnection connection = persistence.getConnection();
            try {
                connection.removeVersion(id);
                connection.commit();
            } finally {
                connection.close();
            }

        } catch(SQLException ex) {
            throw new SailException("database error while listing versions",ex);
        }
    }

    /**
     * Remove all versions until the date given as argument. Iterates over all versions and deletes them individually.
     * Entries in join tables (added/removed triples) are also deleted, the triples themselves not. Deleted triples
     * without version will later be cleaned up by the garbage collector
     * @param until date until when to delete versions
     * @throws SailException
     */
    public void removeVersions(Date until) throws SailException {
        try {
            final KiWiVersioningConnection connection = persistence.getConnection();
            try {
                connection.removeVersions(until);
                connection.commit();
            } finally {
                connection.close();
            }

        } catch(SQLException ex) {
            throw new SailException("database error while listing versions",ex);
        }
    }


    /**
     * Remove all versions in the given time interval. Iterates over all versions and deletes them individually.
     * Entries in join tables (added/removed triples) are also deleted, the triples themselves not. Deleted triples
     * without version will later be cleaned up by the garbage collector
     * @param from date after which versions will be deleted
     * @param to   date before which versions will be deleted
     * @throws SailException
     */
    public void removeVersions(Date from, Date to) throws SailException {
        try {
            final KiWiVersioningConnection connection = persistence.getConnection();
            try {
                connection.removeVersions(from, to);
                connection.commit();
            } finally {
                connection.close();
            }

        } catch(SQLException ex) {
            throw new SailException("database error while listing versions",ex);
        }
    }

    /**
     * Return the version that is the most recent version for a resource given a reference date. The method will either
     * return the version that was current for the resource at the given date or return null in case such a version
     * does not exist (e.g. before the resource was created).
     *
     * @param r         the resource for which to find a version
     * @param date      the reference date
     * @return the latest version of the resource at the given date, or null if such a version does not exist
     * @throws SQLException
     */
    public Version getLatestVersion(Resource r, Date date) throws SailException {
        try {
            final KiWiVersioningConnection connection = persistence.getConnection();

            KiWiResource kr = (KiWiResource) ((r instanceof URI) ? getValueFactory().createURI(r.stringValue()) : getValueFactory().createBNode(r.stringValue()));

            try {
                return connection.getLatestVersion(kr,date);
            } finally {
                connection.commit();
                connection.close();
            }

        } catch(SQLException ex) {
            throw new SailException("database error while listing versions",ex);
        }
    }


    /**
     * List all versions of this repository affecting the given resource as subject.
     *
     * @return
     */
    @Override
    public RepositoryResult<Version> listVersions(Resource rthrows SailException {
        try {
            final KiWiVersioningConnection connection = persistence.getConnection();

            KiWiResource kr = (KiWiResource) ((r instanceof URI) ? getValueFactory().createURI(r.stringValue()) : getValueFactory().createBNode(r.stringValue()));

            return new RepositoryResult<Version>(connection.listVersions(kr)) {
                @Override
                protected void handleClose() throws RepositoryException {
                    super.handleClose();

                    try {
                        connection.commit();
                        connection.close();
                    } catch (SQLException ex) {
                        throw new RepositoryException("database error while committing/closing connection");
                    }
                }
            };
        } catch(SQLException ex) {
            throw new SailException("database error while listing versions",ex);
        }
    }

    /**
     * List all versions of this repository affecting the given resource as subject between a start and end date.
     *
     * @return
     */
    @Override
    public RepositoryResult<Version> listVersions(Resource r, Date from, Date to) throws SailException {
        try {
            final KiWiVersioningConnection connection = persistence.getConnection();

            KiWiResource kr = (KiWiResource) ((r instanceof URI) ? getValueFactory().createURI(r.stringValue()) : getValueFactory().createBNode(r.stringValue()));

            return new RepositoryResult<Version>(connection.listVersions(kr,from,to)) {
                @Override
                protected void handleClose() throws RepositoryException {
                    super.handleClose();

                    try {
                        connection.commit();
                        connection.close();
                    } catch (SQLException ex) {
                        throw new RepositoryException("database error while committing/closing connection");
                    }
                }
            };
        } catch(SQLException ex) {
            throw new SailException("database error while listing versions",ex);
        }
    }

    /**
     * Revert (undo) the version given as argument. This method creates a new transaction, adds all triples
     * that were deleted in the old version, removes all triples that were added in the old version, and commits
     * the transaction, effectively creating a new (reverted) version.
     *
     * @param version    the version to revert
     * @throws SailException in case reverting the version failed
     */
    public void revertVersion(Version version) throws SailException {
        SailConnection con = getConnection();
        try {
            con.begin();

            for(Statement stmt : version.getAddedTriples()) {
                con.removeStatements(stmt.getSubject(), stmt.getPredicate(), stmt.getObject(), stmt.getContext());
            }

            for(Statement stmt : version.getRemovedTriples()) {
                con.addStatement(stmt.getSubject(), stmt.getPredicate(), stmt.getObject(), stmt.getContext());
            }

            con.commit();
        } finally {
            con.close();
        }
    }


    @Override
    public void shutDown() throws SailException {
        // close all open connections after a grace period
        synchronized (activeSnapshots) {
            if(!activeSnapshots.isEmpty()) {
                // wait for a certain grace period for other threads still holding connections to finish
                log.warn("waiting for open connections ({}) to finish ...", activeSnapshots.size());
                try {
                    activeSnapshots.wait(DEFAULT_CONNECTION_TIMEOUT);
                } catch (InterruptedException e) {
                }

                // create a copy of the set and rollback-close all active connections
                HashSet<KiWiSnapshotConnection> connectionCopy = new HashSet<KiWiSnapshotConnection>(activeSnapshots);
                for(KiWiSnapshotConnection con : connectionCopy) {
                    if(con.isActive()) {
                        con.rollback();
                    }
                    if(con.isOpen()) {
                        con.close();
                    }
                }
            }
        }

        // call parent
        super.shutDown();
    }
}
TOP

Related Classes of org.apache.marmotta.kiwi.versioning.sail.KiWiVersioningSail

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.