You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@marmotta.apache.org by wi...@apache.org on 2013/02/19 13:52:00 UTC

[38/52] [partial] code contribution, initial import of relevant modules of LMF-3.0.0-SNAPSHOT based on revision 4bf944319368 of the default branch at https://code.google.com/p/lmf/

http://git-wip-us.apache.org/repos/asf/incubator-marmotta/blob/c32963d5/kiwi/kiwi-tripletable/src/main/java/org/apache/marmotta/kiwi/model/caching/IntArray.java
----------------------------------------------------------------------
diff --git a/kiwi/kiwi-tripletable/src/main/java/org/apache/marmotta/kiwi/model/caching/IntArray.java b/kiwi/kiwi-tripletable/src/main/java/org/apache/marmotta/kiwi/model/caching/IntArray.java
new file mode 100644
index 0000000..8695fa4
--- /dev/null
+++ b/kiwi/kiwi-tripletable/src/main/java/org/apache/marmotta/kiwi/model/caching/IntArray.java
@@ -0,0 +1,168 @@
+/**
+ * Copyright (C) 2013 Salzburg Research.
+ *
+ * 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.apache.marmotta.kiwi.model.caching;
+
+import com.google.common.hash.HashCode;
+import com.google.common.hash.HashFunction;
+import com.google.common.hash.Hasher;
+import com.google.common.hash.Hashing;
+import org.openrdf.model.Resource;
+import org.openrdf.model.URI;
+import org.openrdf.model.Value;
+
+import java.nio.IntBuffer;
+import java.util.Arrays;
+
+/**
+ * Add file description here!
+ * <p/>
+ * Author: Sebastian Schaffert
+ */
+public final class IntArray implements Comparable<IntArray> {
+
+    private static HashFunction hashFunction = Hashing.goodFastHash(32);
+
+    private int[] data;
+
+    private HashCode goodHashCode;
+
+
+    public IntArray(int[] data) {
+        this.data = data;
+
+        Hasher hasher = hashFunction.newHasher();
+        for(int i : data) {
+            hasher.putInt(i);
+        }
+        goodHashCode = hasher.hash();
+    }
+
+    public static final IntArray createSPOCKey(Resource subject, URI property, Value object, Resource context){
+
+        // the cache key is generated by appending the bytes of the hashcodes of subject, property, object, context and inferred and
+        // storing them as a BigInteger; generating the cache key should thus be very efficient
+
+        int s = subject != null ? subject.hashCode() : Integer.MIN_VALUE;
+        int p = property != null ? property.hashCode() : Integer.MIN_VALUE;
+        int o = object != null ? object.hashCode() : Integer.MIN_VALUE;
+        int c = context != null ? context.hashCode() : Integer.MIN_VALUE;
+
+        IntBuffer bb = IntBuffer.allocate(4);
+        bb.put(s);
+        bb.put(p);
+        bb.put(o);
+        bb.put(c);
+
+        return new IntArray(bb.array());
+
+    }
+
+    public static final IntArray createSPOCMaxKey(Resource subject, URI property, Value object, URI context){
+
+        // the cache key is generated by appending the bytes of the hashcodes of subject, property, object, context and inferred and
+        // storing them as a BigInteger; generating the cache key should thus be very efficient
+
+        int s = subject != null ? subject.hashCode() : Integer.MAX_VALUE;
+        int p = property != null ? property.hashCode() : Integer.MAX_VALUE;
+        int o = object != null ? object.hashCode() : Integer.MAX_VALUE;
+        int c = context != null ? context.hashCode() : Integer.MAX_VALUE;
+
+        IntBuffer bb = IntBuffer.allocate(4);
+        bb.put(s);
+        bb.put(p);
+        bb.put(o);
+        bb.put(c);
+
+        return new IntArray(bb.array());
+
+    }
+
+    public static final IntArray createCSPOKey(Resource subject, URI property, Value object, Resource context){
+
+        // the cache key is generated by appending the bytes of the hashcodes of subject, property, object, context and inferred and
+        // storing them as a BigInteger; generating the cache key should thus be very efficient
+
+        int s = subject != null ? subject.hashCode() : Integer.MIN_VALUE;
+        int p = property != null ? property.hashCode() : Integer.MIN_VALUE;
+        int o = object != null ? object.hashCode() : Integer.MIN_VALUE;
+        int c = context != null ? context.hashCode() : Integer.MIN_VALUE;
+
+        IntBuffer bb = IntBuffer.allocate(4);
+        bb.put(c);
+        bb.put(s);
+        bb.put(p);
+        bb.put(o);
+
+        return new IntArray(bb.array());
+
+    }
+
+    public static final IntArray createCSPOMaxKey(Resource subject, URI property, Value object, URI context){
+
+        // the cache key is generated by appending the bytes of the hashcodes of subject, property, object, context and inferred and
+        // storing them as a BigInteger; generating the cache key should thus be very efficient
+
+        int s = subject != null ? subject.hashCode() : Integer.MAX_VALUE;
+        int p = property != null ? property.hashCode() : Integer.MAX_VALUE;
+        int o = object != null ? object.hashCode() : Integer.MAX_VALUE;
+        int c = context != null ? context.hashCode() : Integer.MAX_VALUE;
+
+        IntBuffer bb = IntBuffer.allocate(4);
+        bb.put(c);
+        bb.put(s);
+        bb.put(p);
+        bb.put(o);
+
+        return new IntArray(bb.array());
+
+    }
+
+    @Override
+    public String toString() {
+        return "IntArray{" +
+                "data=" + Arrays.toString(data) +
+                '}';
+    }
+
+    @Override
+    public int compareTo(IntArray o) {
+        for(int i=0; i < data.length && i < o.data.length; i++) {
+            if(data[i] < o.data[i]) {
+                return -1;
+            } else if(data[i]  > o.data[i]) {
+                return 1;
+            }
+        }
+        return 0;
+    }
+
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        IntArray intArray = (IntArray) o;
+
+        return Arrays.equals(data, intArray.data);
+
+    }
+
+    @Override
+    public int hashCode() {
+        return goodHashCode.hashCode();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-marmotta/blob/c32963d5/kiwi/kiwi-tripletable/src/main/java/org/apache/marmotta/kiwi/model/caching/TripleTable.java
----------------------------------------------------------------------
diff --git a/kiwi/kiwi-tripletable/src/main/java/org/apache/marmotta/kiwi/model/caching/TripleTable.java b/kiwi/kiwi-tripletable/src/main/java/org/apache/marmotta/kiwi/model/caching/TripleTable.java
new file mode 100644
index 0000000..41b450b
--- /dev/null
+++ b/kiwi/kiwi-tripletable/src/main/java/org/apache/marmotta/kiwi/model/caching/TripleTable.java
@@ -0,0 +1,469 @@
+/**
+ * Copyright (C) 2013 Salzburg Research.
+ *
+ * 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.apache.marmotta.kiwi.model.caching;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Sets;
+import org.openrdf.model.Resource;
+import org.openrdf.model.Statement;
+import org.openrdf.model.URI;
+import org.openrdf.model.Value;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.Set;
+import java.util.TreeMap;
+
+/**
+ * A triple table that allows efficient in-memory operations over large collections of triples. This can be used as
+ * a simplified version of a Sesame in-memory repository (MemStore) using typical collection methods.
+ * <p/>
+ * Author: Sebastian Schaffert
+ */
+public class TripleTable<Triple extends Statement> implements Set<Triple>, Serializable {
+
+	private static final long serialVersionUID = 1L;
+
+	private Set<Triple> data;
+
+    /**
+     * A set that orders triples orderd in a way that the subject is the most significant, while the context is the
+     * least significant property in the order. Can be used for efficient queries that involve either just a subject,
+     * a subject and a property, a subject, property and object or a subject, property, object and context
+     */
+    private NavigableMap<IntArray,Triple> indexSPOC;
+
+    /**
+     * A set that orders triples orderd in a way that the context is the most significant, while the object is the
+     * least significant property in the order. Can be used for efficient queries that involve either just a context,
+     * a context and a subject, a context, subject, and property
+     */
+    private NavigableMap<IntArray,Triple> indexCSPO;
+
+    public TripleTable() {
+        data = new HashSet<Triple>();
+        indexSPOC = new TreeMap<IntArray, Triple>();
+        indexCSPO = new TreeMap<IntArray, Triple>();
+    }
+
+
+    public TripleTable(Collection<Triple> triples) {
+        data = new HashSet<Triple>(triples.size());
+        indexSPOC = new TreeMap<IntArray, Triple>();
+        indexCSPO = new TreeMap<IntArray, Triple>();
+        addAll(triples);
+    }
+
+
+    /**
+     * Returns the number of elements in this set (its cardinality).  If this
+     * set contains more than <tt>Integer.MAX_VALUE</tt> elements, returns
+     * <tt>Integer.MAX_VALUE</tt>.
+     *
+     * @return the number of elements in this set (its cardinality)
+     */
+    @Override
+    public int size() {
+        return data.size();
+    }
+
+    /**
+     * Returns <tt>true</tt> if this set contains no elements.
+     *
+     * @return <tt>true</tt> if this set contains no elements
+     */
+    @Override
+    public boolean isEmpty() {
+        return data.isEmpty();
+    }
+
+    /**
+     * Returns <tt>true</tt> if this set contains the specified element.
+     * More formally, returns <tt>true</tt> if and only if this set
+     * contains an element <tt>e</tt> such that
+     * <tt>(o==null&nbsp;?&nbsp;e==null&nbsp;:&nbsp;o.equals(e))</tt>.
+     *
+     * @param o element whose presence in this set is to be tested
+     * @return <tt>true</tt> if this set contains the specified element
+     * @throws ClassCastException if the type of the specified element
+     *         is incompatible with this set (optional)
+     * @throws NullPointerException if the specified element is null and this
+     *         set does not permit null elements (optional)
+     */
+    @Override
+    public boolean contains(Object o) {
+        return data.contains(o);
+    }
+
+    /**
+     * Returns an iterator over the elements in this set.  The elements are
+     * returned in no particular order (unless this set is an instance of some
+     * class that provides a guarantee).
+     *
+     * @return an iterator over the elements in this set
+     */
+    @Override
+    public Iterator<Triple> iterator() {
+        return data.iterator();
+    }
+
+    /**
+     * Returns an array containing all of the elements in this set.
+     * If this set makes any guarantees as to what order its elements
+     * are returned by its iterator, this method must return the
+     * elements in the same order.
+     *
+     * <p>The returned array will be "safe" in that no references to it
+     * are maintained by this set.  (In other words, this method must
+     * allocate a new array even if this set is backed by an array).
+     * The caller is thus free to modify the returned array.
+     *
+     * <p>This method acts as bridge between array-based and collection-based
+     * APIs.
+     *
+     * @return an array containing all the elements in this set
+     */
+    @Override
+    public Object[] toArray() {
+        return data.toArray();
+    }
+
+    /**
+     * Returns an array containing all of the elements in this set; the
+     * runtime type of the returned array is that of the specified array.
+     * If the set fits in the specified array, it is returned therein.
+     * Otherwise, a new array is allocated with the runtime type of the
+     * specified array and the size of this set.
+     *
+     * <p>If this set fits in the specified array with room to spare
+     * (i.e., the array has more elements than this set), the element in
+     * the array immediately following the end of the set is set to
+     * <tt>null</tt>.  (This is useful in determining the length of this
+     * set <i>only</i> if the caller knows that this set does not contain
+     * any null elements.)
+     *
+     * <p>If this set makes any guarantees as to what order its elements
+     * are returned by its iterator, this method must return the elements
+     * in the same order.
+     *
+     * <p>Like the {@link #toArray()} method, this method acts as bridge between
+     * array-based and collection-based APIs.  Further, this method allows
+     * precise control over the runtime type of the output array, and may,
+     * under certain circumstances, be used to save allocation costs.
+     *
+     * <p>Suppose <tt>x</tt> is a set known to contain only strings.
+     * The following code can be used to dump the set into a newly allocated
+     * array of <tt>String</tt>:
+     *
+     * <pre>
+     *     String[] y = x.toArray(new String[0]);</pre>
+     *
+     * Note that <tt>toArray(new Object[0])</tt> is identical in function to
+     * <tt>toArray()</tt>.
+     *
+     * @param a the array into which the elements of this set are to be
+     *        stored, if it is big enough; otherwise, a new array of the same
+     *        runtime type is allocated for this purpose.
+     * @return an array containing all the elements in this set
+     * @throws ArrayStoreException if the runtime type of the specified array
+     *         is not a supertype of the runtime type of every element in this
+     *         set
+     * @throws NullPointerException if the specified array is null
+     */
+    @Override
+    public <T> T[] toArray(T[] a) {
+        return data.toArray(a);
+    }
+
+    /**
+     * Adds the specified element to this set if it is not already present
+     * (optional operation).  More formally, adds the specified element
+     * <tt>e</tt> to this set if the set contains no element <tt>e2</tt>
+     * such that
+     * <tt>(e==null&nbsp;?&nbsp;e2==null&nbsp;:&nbsp;e.equals(e2))</tt>.
+     * If this set already contains the element, the call leaves the set
+     * unchanged and returns <tt>false</tt>.  In combination with the
+     * restriction on constructors, this ensures that sets never contain
+     * duplicate elements.
+     *
+     * <p>The stipulation above does not imply that sets must accept all
+     * elements; sets may refuse to add any particular element, including
+     * <tt>null</tt>, and throw an exception, as described in the
+     * specification for {@link java.util.Collection#add Collection.add}.
+     * Individual set implementations should clearly document any
+     * restrictions on the elements that they may contain.
+     *
+     * @param triple element to be added to this set
+     * @return <tt>true</tt> if this set did not already contain the specified
+     *         element
+     * @throws UnsupportedOperationException if the <tt>add</tt> operation
+     *         is not supported by this set
+     * @throws ClassCastException if the class of the specified element
+     *         prevents it from being added to this set
+     * @throws NullPointerException if the specified element is null and this
+     *         set does not permit null elements
+     * @throws IllegalArgumentException if some property of the specified element
+     *         prevents it from being added to this set
+     */
+    @Override
+    public boolean add(Triple triple) {
+        indexSPOC.put(IntArray.createSPOCKey(triple.getSubject(), triple.getPredicate(), triple.getObject(), triple.getContext()),triple);
+        indexCSPO.put(IntArray.createCSPOKey(triple.getSubject(), triple.getPredicate(), triple.getObject(), triple.getContext()),triple);
+        return data.add(triple);
+    }
+
+    /**
+     * Removes the specified element from this set if it is present
+     * (optional operation).  More formally, removes an element <tt>e</tt>
+     * such that
+     * <tt>(o==null&nbsp;?&nbsp;e==null&nbsp;:&nbsp;o.equals(e))</tt>, if
+     * this set contains such an element.  Returns <tt>true</tt> if this set
+     * contained the element (or equivalently, if this set changed as a
+     * result of the call).  (This set will not contain the element once the
+     * call returns.)
+     *
+     * @param o object to be removed from this set, if present
+     * @return <tt>true</tt> if this set contained the specified element
+     * @throws ClassCastException if the type of the specified element
+     *         is incompatible with this set (optional)
+     * @throws NullPointerException if the specified element is null and this
+     *         set does not permit null elements (optional)
+     * @throws UnsupportedOperationException if the <tt>remove</tt> operation
+     *         is not supported by this set
+     */
+    @Override
+    public boolean remove(Object o) {
+        if(o instanceof Statement) {
+            Statement triple = (Statement)o;
+            indexSPOC.remove(IntArray.createSPOCKey(triple.getSubject(), triple.getPredicate(), triple.getObject(), triple.getContext()));
+            indexCSPO.remove(IntArray.createCSPOKey(triple.getSubject(), triple.getPredicate(), triple.getObject(), triple.getContext()));
+        }
+        return data.remove(o);
+    }
+
+    /**
+     * Returns <tt>true</tt> if this set contains all of the elements of the
+     * specified collection.  If the specified collection is also a set, this
+     * method returns <tt>true</tt> if it is a <i>subset</i> of this set.
+     *
+     * @param  c collection to be checked for containment in this set
+     * @return <tt>true</tt> if this set contains all of the elements of the
+     * 	       specified collection
+     * @throws ClassCastException if the types of one or more elements
+     *         in the specified collection are incompatible with this
+     *         set (optional)
+     * @throws NullPointerException if the specified collection contains one
+     *         or more null elements and this set does not permit null
+     *         elements (optional), or if the specified collection is null
+     * @see    #contains(Object)
+     */
+    @Override
+    public boolean containsAll(Collection<?> c) {
+        return data.containsAll(c);
+    }
+
+    /**
+     * Adds all of the elements in the specified collection to this set if
+     * they're not already present (optional operation).  If the specified
+     * collection is also a set, the <tt>addAll</tt> operation effectively
+     * modifies this set so that its value is the <i>union</i> of the two
+     * sets.  The behavior of this operation is undefined if the specified
+     * collection is modified while the operation is in progress.
+     *
+     * @param  c collection containing elements to be added to this set
+     * @return <tt>true</tt> if this set changed as a result of the call
+     *
+     * @throws UnsupportedOperationException if the <tt>addAll</tt> operation
+     *         is not supported by this set
+     * @throws ClassCastException if the class of an element of the
+     *         specified collection prevents it from being added to this set
+     * @throws NullPointerException if the specified collection contains one
+     *         or more null elements and this set does not permit null
+     *         elements, or if the specified collection is null
+     * @throws IllegalArgumentException if some property of an element of the
+     *         specified collection prevents it from being added to this set
+     * @see #add(Object)
+     */
+    @Override
+    public boolean addAll(Collection<? extends Triple> c) {
+        boolean modified = false;
+        for(Triple t : c) {
+            modified = add(t) || modified;
+        }
+        return modified;
+    }
+
+    /**
+     * Retains only the elements in this set that are contained in the
+     * specified collection (optional operation).  In other words, removes
+     * from this set all of its elements that are not contained in the
+     * specified collection.  If the specified collection is also a set, this
+     * operation effectively modifies this set so that its value is the
+     * <i>intersection</i> of the two sets.
+     *
+     * @param  c collection containing elements to be retained in this set
+     * @return <tt>true</tt> if this set changed as a result of the call
+     * @throws UnsupportedOperationException if the <tt>retainAll</tt> operation
+     *         is not supported by this set
+     * @throws ClassCastException if the class of an element of this set
+     *         is incompatible with the specified collection (optional)
+     * @throws NullPointerException if this set contains a null element and the
+     *         specified collection does not permit null elements (optional),
+     *         or if the specified collection is null
+     * @see #remove(Object)
+     */
+    @Override
+    public boolean retainAll(Collection<?> c) {
+        Iterator<Map.Entry<IntArray,Triple>> it = indexSPOC.entrySet().iterator();
+        while(it.hasNext()) {
+            if(!c.contains(it.next().getValue())) {
+                it.remove();
+            }
+        }
+        Iterator<Map.Entry<IntArray,Triple>> it2 = indexCSPO.entrySet().iterator();
+        while(it2.hasNext()) {
+            if(!c.contains(it2.next().getValue())) {
+                it2.remove();
+            }
+        }
+        return data.retainAll(c);
+    }
+
+    /**
+     * Removes from this set all of its elements that are contained in the
+     * specified collection (optional operation).  If the specified
+     * collection is also a set, this operation effectively modifies this
+     * set so that its value is the <i>asymmetric set difference</i> of
+     * the two sets.
+     *
+     * @param  c collection containing elements to be removed from this set
+     * @return <tt>true</tt> if this set changed as a result of the call
+     * @throws UnsupportedOperationException if the <tt>removeAll</tt> operation
+     *         is not supported by this set
+     * @throws ClassCastException if the class of an element of this set
+     *         is incompatible with the specified collection (optional)
+     * @throws NullPointerException if this set contains a null element and the
+     *         specified collection does not permit null elements (optional),
+     *         or if the specified collection is null
+     * @see #remove(Object)
+     * @see #contains(Object)
+     */
+    @Override
+    public boolean removeAll(Collection<?> c) {
+        boolean modified = false;
+        for(Object o : c) {
+            modified = remove(o) || modified;
+        }
+        return modified;
+    }
+
+    /**
+     * Removes all of the elements from this set (optional operation).
+     * The set will be empty after this call returns.
+     *
+     * @throws UnsupportedOperationException if the <tt>clear</tt> method
+     *         is not supported by this set
+     */
+    @Override
+    public void clear() {
+        data.clear();
+        indexSPOC.clear();
+        indexCSPO.clear();
+    }
+
+    /**
+     * Return a subset of the triples matching the filter criteria. Arguments with null value are treated as wildcards.
+     *
+     * @param subject
+     * @param property
+     * @param object
+     * @param context
+     * @return
+     */
+    public Collection<Triple> listTriples(final Resource subject, final URI property, final Value object, final URI context) {
+        // in special cases we can make use of the index
+        if(subject != null && property != null && object != null && context != null) {
+            IntArray key = IntArray.createSPOCKey(subject, property, object, context);
+            Triple result = indexSPOC.get(key);
+            if(result != null) {
+                return Collections.singleton(result);
+            } else {
+                return Collections.emptyList();
+            }
+        } else if(  (subject != null && property != null && object != null)
+                 || (subject != null && property != null)
+                 || subject != null) {
+            IntArray fromKey = IntArray.createSPOCKey(subject, property, object, context);
+            IntArray toKey   = IntArray.createSPOCMaxKey(subject, property, object, context);
+
+            return indexSPOC.subMap(fromKey,true,toKey,true).values();
+        } else if(  (context != null && subject != null && property != null)
+                 || (context != null && subject != null)
+                 || context != null) {
+            IntArray fromKey = IntArray.createCSPOKey(subject, property, object, context);
+            IntArray toKey   = IntArray.createCSPOMaxKey(subject, property, object, context);
+
+            return indexCSPO.subMap(fromKey,true,toKey,true).values();
+        } else {
+            // in all other cases we need to iterate and filter :-(
+            Predicate<Statement> p = new Predicate<Statement>() {
+                @Override
+                public boolean apply(Statement triple) {
+                    if(subject != null && !triple.getSubject().equals(subject)) {
+                        return false;
+                    }
+                    if(property != null && !triple.getPredicate().equals(property)) {
+                        return false;
+                    }
+                    if(object != null && !triple.getObject().equals(object)) {
+                        return false;
+                    }
+                    if(context != null && !triple.getContext().equals(context)) {
+                        return false;
+                    }
+
+                    return true;
+                }
+            };
+
+            return Sets.filter(data, p);
+        }
+    }
+
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+		@SuppressWarnings("rawtypes")
+		TripleTable that = (TripleTable) o;
+
+        if (!data.equals(that.data)) return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return data.hashCode();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-marmotta/blob/c32963d5/kiwi/kiwi-versioning/.classpath
----------------------------------------------------------------------
diff --git a/kiwi/kiwi-versioning/.classpath b/kiwi/kiwi-versioning/.classpath
new file mode 100644
index 0000000..bf96ac0
--- /dev/null
+++ b/kiwi/kiwi-versioning/.classpath
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" output="target/classes" path="src/main/java"/>
+	<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources"/>
+	<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
+	<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
+	<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
+		<attributes>
+			<attribute name="org.eclipse.jst.component.nondependency" value=""/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="output" path="target/classes"/>
+</classpath>

http://git-wip-us.apache.org/repos/asf/incubator-marmotta/blob/c32963d5/kiwi/kiwi-versioning/.project
----------------------------------------------------------------------
diff --git a/kiwi/kiwi-versioning/.project b/kiwi/kiwi-versioning/.project
new file mode 100644
index 0000000..89f77a7
--- /dev/null
+++ b/kiwi/kiwi-versioning/.project
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>kiwi-versioning</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.wst.common.project.facet.core.builder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.m2e.core.maven2Builder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.wst.validation.validationbuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jem.workbench.JavaEMFNature</nature>
+		<nature>org.eclipse.wst.common.modulecore.ModuleCoreNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+		<nature>org.eclipse.m2e.core.maven2Nature</nature>
+		<nature>org.eclipse.wst.common.project.facet.core.nature</nature>
+	</natures>
+</projectDescription>

http://git-wip-us.apache.org/repos/asf/incubator-marmotta/blob/c32963d5/kiwi/kiwi-versioning/.settings/org.eclipse.core.resources.prefs
----------------------------------------------------------------------
diff --git a/kiwi/kiwi-versioning/.settings/org.eclipse.core.resources.prefs b/kiwi/kiwi-versioning/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 0000000..dc1b414
--- /dev/null
+++ b/kiwi/kiwi-versioning/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,5 @@
+eclipse.preferences.version=1
+encoding//src/main/java=UTF-8
+encoding//src/main/resources=UTF-8
+encoding//src/test/java=UTF-8
+encoding//src/test/resources=UTF-8

http://git-wip-us.apache.org/repos/asf/incubator-marmotta/blob/c32963d5/kiwi/kiwi-versioning/.settings/org.eclipse.jdt.core.prefs
----------------------------------------------------------------------
diff --git a/kiwi/kiwi-versioning/.settings/org.eclipse.jdt.core.prefs b/kiwi/kiwi-versioning/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..69c31cd
--- /dev/null
+++ b/kiwi/kiwi-versioning/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,8 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
+org.eclipse.jdt.core.compiler.source=1.6

http://git-wip-us.apache.org/repos/asf/incubator-marmotta/blob/c32963d5/kiwi/kiwi-versioning/.settings/org.eclipse.m2e.core.prefs
----------------------------------------------------------------------
diff --git a/kiwi/kiwi-versioning/.settings/org.eclipse.m2e.core.prefs b/kiwi/kiwi-versioning/.settings/org.eclipse.m2e.core.prefs
new file mode 100644
index 0000000..f897a7f
--- /dev/null
+++ b/kiwi/kiwi-versioning/.settings/org.eclipse.m2e.core.prefs
@@ -0,0 +1,4 @@
+activeProfiles=
+eclipse.preferences.version=1
+resolveWorkspaceProjects=true
+version=1

http://git-wip-us.apache.org/repos/asf/incubator-marmotta/blob/c32963d5/kiwi/kiwi-versioning/.settings/org.eclipse.wst.common.component
----------------------------------------------------------------------
diff --git a/kiwi/kiwi-versioning/.settings/org.eclipse.wst.common.component b/kiwi/kiwi-versioning/.settings/org.eclipse.wst.common.component
new file mode 100644
index 0000000..21c1575
--- /dev/null
+++ b/kiwi/kiwi-versioning/.settings/org.eclipse.wst.common.component
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project-modules id="moduleCoreId" project-version="1.5.0">
+    <wb-module deploy-name="kiwi-versioning">
+        <wb-resource deploy-path="/" source-path="/src/main/java"/>
+        <wb-resource deploy-path="/" source-path="/src/main/resources"/>
+    </wb-module>
+</project-modules>

http://git-wip-us.apache.org/repos/asf/incubator-marmotta/blob/c32963d5/kiwi/kiwi-versioning/.settings/org.eclipse.wst.common.project.facet.core.xml
----------------------------------------------------------------------
diff --git a/kiwi/kiwi-versioning/.settings/org.eclipse.wst.common.project.facet.core.xml b/kiwi/kiwi-versioning/.settings/org.eclipse.wst.common.project.facet.core.xml
new file mode 100644
index 0000000..c78d932
--- /dev/null
+++ b/kiwi/kiwi-versioning/.settings/org.eclipse.wst.common.project.facet.core.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<faceted-project>
+  <installed facet="java" version="1.6"/>
+  <installed facet="jst.utility" version="1.0"/>
+</faceted-project>

http://git-wip-us.apache.org/repos/asf/incubator-marmotta/blob/c32963d5/kiwi/kiwi-versioning/pom.xml
----------------------------------------------------------------------
diff --git a/kiwi/kiwi-versioning/pom.xml b/kiwi/kiwi-versioning/pom.xml
new file mode 100644
index 0000000..18040dd
--- /dev/null
+++ b/kiwi/kiwi-versioning/pom.xml
@@ -0,0 +1,164 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright (c) 2013 The Apache Software Foundation
+  ~  
+  ~  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.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>at.newmedialab.lmf</groupId>
+        <artifactId>kiwi-parent</artifactId>
+        <version>3.0.0-SNAPSHOT</version>
+        <relativePath>../</relativePath>
+    </parent>
+
+    <artifactId>kiwi-versioning</artifactId>
+    <packaging>jar</packaging>
+
+    <name>KiWi Triplestore: Versioning</name>
+    <description>
+        Provides versioning functionality on top of the KiWi triplestore, i.e. allows keeping a log
+        of all changes carried out on the triple store as well as reverting to previous states.
+    </description>
+
+    <dependencies>
+        <dependency>
+            <groupId>at.newmedialab.lmf</groupId>
+            <artifactId>kiwi-triplestore</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>at.newmedialab.lmf</groupId>
+            <artifactId>kiwi-transactions</artifactId>
+        </dependency>
+
+        <!-- Logging -->
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>log4j-over-slf4j</artifactId>
+        </dependency>
+
+        <!-- Sesame dependencies -->
+        <dependency>
+            <groupId>org.openrdf.sesame</groupId>
+            <artifactId>sesame-model</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.openrdf.sesame</groupId>
+            <artifactId>sesame-sail-api</artifactId>
+        </dependency>
+
+        <!-- Utilities -->
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>commons-lang</groupId>
+            <artifactId>commons-lang</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>at.newmedialab.sesame</groupId>
+            <artifactId>sesame-commons</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>at.newmedialab.sesame</groupId>
+            <artifactId>sesame-filter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+        </dependency>
+
+
+        <!-- Testing -->
+        <dependency>
+            <artifactId>junit</artifactId>
+            <groupId>junit</groupId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+        	<groupId>at.newmedialab.lmf</groupId>
+        	<artifactId>kiwi-triplestore</artifactId>
+        	<type>test-jar</type>
+        	<scope>test</scope>
+        </dependency>
+        <dependency> <!-- see http://www.dbunit.org/howto.html -->
+            <artifactId>dbunit</artifactId>
+            <groupId>org.dbunit</groupId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <artifactId>hamcrest-core</artifactId>
+            <groupId>org.hamcrest</groupId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <artifactId>hamcrest-library</artifactId>
+            <groupId>org.hamcrest</groupId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-classic</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.h2database</groupId>
+            <artifactId>h2</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>postgresql</groupId>
+            <artifactId>postgresql</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.openrdf.sesame</groupId>
+            <artifactId>sesame-rio-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.openrdf.sesame</groupId>
+            <artifactId>sesame-rio-rdfxml</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.openrdf.sesame</groupId>
+            <artifactId>sesame-queryparser-api</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.openrdf.sesame</groupId>
+            <artifactId>sesame-queryparser-sparql</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+    </dependencies>
+
+</project>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-marmotta/blob/c32963d5/kiwi/kiwi-versioning/src/main/java/org/apache/marmotta/kiwi/versioning/api/VersioningSail.java
----------------------------------------------------------------------
diff --git a/kiwi/kiwi-versioning/src/main/java/org/apache/marmotta/kiwi/versioning/api/VersioningSail.java b/kiwi/kiwi-versioning/src/main/java/org/apache/marmotta/kiwi/versioning/api/VersioningSail.java
new file mode 100644
index 0000000..fdd74f1
--- /dev/null
+++ b/kiwi/kiwi-versioning/src/main/java/org/apache/marmotta/kiwi/versioning/api/VersioningSail.java
@@ -0,0 +1,76 @@
+/**
+ * Copyright (C) 2013 Salzburg Research.
+ *
+ * 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.apache.marmotta.kiwi.versioning.api;
+
+import org.apache.marmotta.kiwi.transactions.api.TransactionalSail;
+import org.apache.marmotta.kiwi.versioning.model.Version;
+import org.openrdf.model.Resource;
+import org.openrdf.repository.RepositoryResult;
+import org.openrdf.sail.SailConnection;
+import org.openrdf.sail.SailException;
+
+import java.util.Date;
+
+/**
+ * A Sesame SAIL with support for versioning of transaction data. Allows querying for the versions of a
+ * repository (see {@link Version}).
+ * <p/>
+ * Author: Sebastian Schaffert
+ */
+public interface VersioningSail extends TransactionalSail {
+
+
+    /**
+     * List all versions of this repository.
+     *
+     * @return
+     */
+    public RepositoryResult<Version> listVersions() throws SailException;
+
+    /**
+     * List all versions of this repository between a start and end date.
+     *
+     * @return
+     */
+    public RepositoryResult<Version> listVersions(Date from, Date to) throws SailException;
+
+
+    /**
+     * 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
+     */
+    public SailConnection getSnapshot(Date snapshotDate) throws SailException;
+
+    /**
+     * List all versions of this repository affecting the given resource as subject.
+     *
+     * @return
+     */
+    RepositoryResult<Version> listVersions(Resource r)  throws SailException;
+
+    /**
+     * List all versions of this repository affecting the given resource as subject between a start and end date.
+     *
+     * @return
+     */
+    RepositoryResult<Version> listVersions(Resource r, Date from, Date to) throws SailException;
+}

http://git-wip-us.apache.org/repos/asf/incubator-marmotta/blob/c32963d5/kiwi/kiwi-versioning/src/main/java/org/apache/marmotta/kiwi/versioning/model/Version.java
----------------------------------------------------------------------
diff --git a/kiwi/kiwi-versioning/src/main/java/org/apache/marmotta/kiwi/versioning/model/Version.java b/kiwi/kiwi-versioning/src/main/java/org/apache/marmotta/kiwi/versioning/model/Version.java
new file mode 100644
index 0000000..5ed00aa
--- /dev/null
+++ b/kiwi/kiwi-versioning/src/main/java/org/apache/marmotta/kiwi/versioning/model/Version.java
@@ -0,0 +1,63 @@
+/**
+ * Copyright (C) 2013 Salzburg Research.
+ *
+ * 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.apache.marmotta.kiwi.versioning.model;
+
+import org.apache.marmotta.kiwi.model.rdf.KiWiResource;
+import org.apache.marmotta.kiwi.transactions.model.TransactionData;
+
+/**
+ * In-memory representation of a KiWi version. Consists of a set of added triples, a set of removed triples,
+ * a commit date, and a database ID.
+ * <p/>
+ * Author: Sebastian Schaffert
+ */
+public class Version extends TransactionData {
+
+    private Long id;
+
+    private KiWiResource creator;
+
+    public Version() {
+        super();
+    }
+
+    public Version(Long id) {
+        super();
+        this.id = id;
+    }
+
+    public Version(TransactionData data) {
+        this.addedTriples   = data.getAddedTriples();
+        this.removedTriples = data.getRemovedTriples();
+        this.commitTime     = data.getCommitTime();
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public KiWiResource getCreator() {
+        return creator;
+    }
+
+    public void setCreator(KiWiResource creator) {
+        this.creator = creator;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-marmotta/blob/c32963d5/kiwi/kiwi-versioning/src/main/java/org/apache/marmotta/kiwi/versioning/persistence/KiWiVersioningConnection.java
----------------------------------------------------------------------
diff --git a/kiwi/kiwi-versioning/src/main/java/org/apache/marmotta/kiwi/versioning/persistence/KiWiVersioningConnection.java b/kiwi/kiwi-versioning/src/main/java/org/apache/marmotta/kiwi/versioning/persistence/KiWiVersioningConnection.java
new file mode 100644
index 0000000..169ddbf
--- /dev/null
+++ b/kiwi/kiwi-versioning/src/main/java/org/apache/marmotta/kiwi/versioning/persistence/KiWiVersioningConnection.java
@@ -0,0 +1,596 @@
+/**
+ * Copyright (C) 2013 Salzburg Research.
+ *
+ * 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.apache.marmotta.kiwi.versioning.persistence;
+
+import info.aduna.iteration.CloseableIteration;
+import info.aduna.iteration.EmptyIteration;
+import info.aduna.iteration.ExceptionConvertingIteration;
+import org.apache.marmotta.kiwi.caching.KiWiCacheManager;
+import org.apache.marmotta.kiwi.model.rdf.KiWiNode;
+import org.apache.marmotta.kiwi.model.rdf.KiWiResource;
+import org.apache.marmotta.kiwi.model.rdf.KiWiTriple;
+import org.apache.marmotta.kiwi.model.rdf.KiWiUriResource;
+import org.apache.marmotta.kiwi.persistence.KiWiConnection;
+import org.apache.marmotta.kiwi.persistence.KiWiDialect;
+import org.apache.marmotta.kiwi.persistence.util.ResultSetIteration;
+import org.apache.marmotta.kiwi.persistence.util.ResultTransformerFunction;
+import org.apache.marmotta.kiwi.versioning.model.Version;
+import org.openrdf.model.Statement;
+import org.openrdf.repository.RepositoryException;
+import org.openrdf.repository.RepositoryResult;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.util.Date;
+
+/**
+ * Add file description here!
+ * <p/>
+ * Author: Sebastian Schaffert
+ */
+public class KiWiVersioningConnection extends KiWiConnection {
+
+    private static Logger log = LoggerFactory.getLogger(KiWiVersioningConnection.class);
+
+    public KiWiVersioningConnection(Connection connection, KiWiDialect dialect, KiWiCacheManager cacheManager) throws SQLException {
+        super(connection, dialect, cacheManager);
+    }
+
+
+    /**
+     * Store a new version in the database. Will query for a new sequence ID for versions, insert a new row in the
+     * versions table, and then add join entries to the versions_added and versions_removed tables for each added
+     * and removed triple.
+     * <p/>
+     * Note that this method assumes the transaction data to be already consistent, i.e. a triple is not added and
+     * removed at the same time. No check for this situation is performed
+     *
+     * @param data
+     * @return
+     * @throws SQLException
+     */
+    public void storeVersion(Version data) throws SQLException {
+        if(data.getId() != null) {
+            log.warn("version {} already had a version ID, not persisting", data);
+            return;
+        }
+
+        // first create a new entry in the version table
+        data.setId(getNextSequence("seq.versions"));
+
+        PreparedStatement insertVersion = getPreparedStatement("store.version");
+        synchronized (insertVersion) {
+            insertVersion.setLong(1,data.getId());
+            if(data.getCreator() != null) {
+                insertVersion.setLong(2,data.getCreator().getId());
+            } else {
+                insertVersion.setObject(2, null);
+            }
+            insertVersion.setTimestamp(3, new Timestamp(data.getCommitTime().getTime()));
+            insertVersion.executeUpdate();
+        }
+
+        // then add join table entries for every added triple
+        PreparedStatement insertAdded = getPreparedStatement("store.version_added");
+        synchronized (insertAdded) {
+            insertAdded.clearParameters();
+            for(Statement added : data.getAddedTriples()) {
+                if(added instanceof KiWiTriple) {
+                    insertAdded.setLong(1,data.getId());
+                    insertAdded.setLong(2,((KiWiTriple) added).getId());
+                    insertAdded.addBatch();
+                } else {
+                    // maybe we should even throw an exception here
+                    log.warn("cannot create version with statements that are not instances of KiWiTriple!");
+                }
+            }
+            insertAdded.executeBatch(); // if this is a performance issue, we could also run it every 1000 inserts or so
+        }
+
+        // then add join table entries for every removed triple
+        PreparedStatement insertRemoved = getPreparedStatement("store.version_removed");
+        synchronized (insertRemoved) {
+            insertRemoved.clearParameters();
+            for(Statement added : data.getRemovedTriples()) {
+                if(added instanceof KiWiTriple) {
+                    insertRemoved.setLong(1,data.getId());
+                    insertRemoved.setLong(2,((KiWiTriple) added).getId());
+                    insertRemoved.addBatch();
+                } else {
+                    // maybe we should even throw an exception here
+                    log.warn("cannot create version with statements that are not instances of KiWiTriple!");
+                }
+            }
+            insertRemoved.executeBatch(); // if this is a performance issue, we could also run it every 1000 inserts or so
+        }
+
+    }
+
+    /**
+     * Retrieve a version by its id. If the version does not exist, returns null
+     *
+     * @param id
+     * @return
+     * @throws SQLException
+     */
+    public Version getVersion(Long id) throws SQLException {
+        PreparedStatement queryVersions = getPreparedStatement("load.version_by_id");
+        queryVersions.setLong(1,id);
+        queryVersions.setMaxRows(1);
+
+        ResultSet r = queryVersions.executeQuery();
+        try {
+            if(r.next()) {
+                return constructVersionFromDatabase(r);
+            } else {
+                return null;
+            }
+        } finally {
+            r.close();
+        }
+    }
+
+    /**
+     * 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 resource  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(KiWiResource resource, Date date) throws SQLException {
+        PreparedStatement queryVersions = getPreparedStatement("load.versions_by_resource_latest");
+        synchronized (queryVersions) {
+            queryVersions.setLong(1, resource.getId());
+            queryVersions.setTimestamp(2, new Timestamp(date.getTime()));
+            queryVersions.setMaxRows(1);
+
+            ResultSet r = queryVersions.executeQuery();
+            try {
+                if(r.next()) {
+                    return constructVersionFromDatabase(r);
+                } else {
+                    return null;
+                }
+            } finally {
+                r.close();
+            }
+        }
+    }
+
+
+    /**
+     * List all versions in the database; operates directly on the result set, i.e. the iteration is carried out
+     * lazily and needs to be closed when iteration is completed.
+     *
+     * @return
+     * @throws SQLException
+     */
+    public RepositoryResult<Version> listVersions() throws SQLException {
+
+        return new RepositoryResult<Version>(
+                new ExceptionConvertingIteration<Version, RepositoryException>(listVersionsInternal()) {
+                    @Override
+                    protected RepositoryException convert(Exception e) {
+                        return new RepositoryException("database error while iterating over result set",e);
+                    }
+                }
+
+        );
+    }
+
+
+    /**
+     * List all versions in the database; operates directly on the result set, i.e. the iteration is carried out
+     * lazily and needs to be closed when iteration is completed.
+     *
+     * @return
+     * @throws SQLException
+     */
+    private CloseableIteration<Version, SQLException> listVersionsInternal() throws SQLException {
+        PreparedStatement queryVersions = getPreparedStatement("load.versions");
+
+        final ResultSet result = queryVersions.executeQuery();
+        return new ResultSetIteration<Version>(result, new ResultTransformerFunction<Version>() {
+            @Override
+            public Version apply(ResultSet row) throws SQLException {
+                return constructVersionFromDatabase(result);
+            }
+        });
+    }
+
+
+    /**
+     * List all versions in the database affecting the given resource as subject; operates directly on the result set,
+     * i.e. the iteration is carried out lazily and needs to be closed when iteration is completed.
+     *
+     * @return
+     * @throws SQLException
+     */
+    public RepositoryResult<Version> listVersions(KiWiResource r) throws SQLException {
+
+        return new RepositoryResult<Version>(
+                new ExceptionConvertingIteration<Version, RepositoryException>(listVersionsInternal(r)) {
+                    @Override
+                    protected RepositoryException convert(Exception e) {
+                        return new RepositoryException("database error while iterating over result set",e);
+                    }
+                }
+
+        );
+    }
+
+    /**
+     * List all versions in the database affecting the given resource as subject; operates directly on the result set,
+     * i.e. the iteration is carried out lazily and needs to be closed when iteration is completed.
+     *
+     * @return
+     * @throws SQLException
+     */
+    private CloseableIteration<Version, SQLException> listVersionsInternal(KiWiResource r) throws SQLException {
+        if(r.getId() == null) {
+            return new EmptyIteration<Version, SQLException>();
+        } else {
+            PreparedStatement queryVersions = getPreparedStatement("load.versions_by_resource");
+            queryVersions.setLong(1,r.getId());
+
+            final ResultSet result = queryVersions.executeQuery();
+            return new ResultSetIteration<Version>(result, new ResultTransformerFunction<Version>() {
+                @Override
+                public Version apply(ResultSet row) throws SQLException {
+                    return constructVersionFromDatabase(result);
+                }
+            });
+        }
+
+    }
+
+
+
+    /**
+     * List all versions in the database; operates directly on the result set, i.e. the iteration is carried out
+     * lazily and needs to be closed when iteration is completed.
+     *
+     * @return
+     * @throws SQLException
+     */
+    public RepositoryResult<Version> listVersions(final Date from, final Date to) throws SQLException {
+
+        return new RepositoryResult<Version>(
+                new ExceptionConvertingIteration<Version, RepositoryException>(listVersionsInternal(from, to)) {
+                    @Override
+                    protected RepositoryException convert(Exception e) {
+                        return new RepositoryException("database error while iterating over result set",e);
+                    }
+                }
+
+        );
+    }
+
+    /**
+     * List all versions in the database; operates directly on the result set, i.e. the iteration is carried out
+     * lazily and needs to be closed when iteration is completed.
+     *
+     * @return
+     * @throws SQLException
+     */
+    private CloseableIteration<Version, SQLException> listVersionsInternal(Date from, Date to) throws SQLException {
+        PreparedStatement queryVersions = getPreparedStatement("load.version_between");
+        synchronized (queryVersions) {
+            queryVersions.clearParameters();
+            queryVersions.setTimestamp(1, new Timestamp(from.getTime()));
+            queryVersions.setTimestamp(2, new Timestamp(to.getTime()));
+
+            final ResultSet result = queryVersions.executeQuery();
+            return new ResultSetIteration<Version>(result, new ResultTransformerFunction<Version>() {
+                @Override
+                public Version apply(ResultSet row) throws SQLException {
+                    return constructVersionFromDatabase(result);
+                }
+            });
+        }
+    }
+
+
+
+    /**
+     * List all versions in the database; operates directly on the result set, i.e. the iteration is carried out
+     * lazily and needs to be closed when iteration is completed.
+     *
+     * @return
+     * @throws SQLException
+     */
+    public RepositoryResult<Version> listVersions(final KiWiResource r, final Date from, final Date to) throws SQLException {
+
+        return new RepositoryResult<Version>(
+                new ExceptionConvertingIteration<Version, RepositoryException>(listVersionsInternal(r, from, to)) {
+                    @Override
+                    protected RepositoryException convert(Exception e) {
+                        return new RepositoryException("database error while iterating over result set",e);
+                    }
+                }
+
+        );
+    }
+
+    /**
+     * List all versions in the database; operates directly on the result set, i.e. the iteration is carried out
+     * lazily and needs to be closed when iteration is completed.
+     *
+     * @return
+     * @throws SQLException
+     */
+    private CloseableIteration<Version, SQLException> listVersionsInternal(KiWiResource r, Date from, Date to) throws SQLException {
+        PreparedStatement queryVersions = getPreparedStatement("load.versions_by_resource_between");
+        synchronized (queryVersions) {
+            queryVersions.clearParameters();
+            if(r.getId() == null) {
+                return new EmptyIteration<Version, SQLException>();
+            } else {
+                queryVersions.setLong(1, r.getId());
+                queryVersions.setTimestamp(2, new Timestamp(from.getTime()));
+                queryVersions.setTimestamp(3, new Timestamp(to.getTime()));
+
+                final ResultSet result = queryVersions.executeQuery();
+                return new ResultSetIteration<Version>(result, new ResultTransformerFunction<Version>() {
+                    @Override
+                    public Version apply(ResultSet row) throws SQLException {
+                        return constructVersionFromDatabase(result);
+                    }
+                });
+            }
+        }
+    }
+
+
+    /**
+     * Construct a version from the database using the data contained in the result set row passed as argument. The method
+     * will load all added and removed triples in subsequent SQL queries.
+     * <p/>
+     * The method will not change the ResultSet iterator, only read its values, so it needs to be executed for each row separately.
+     *
+     * @param row
+     * @return
+     * @throws SQLException
+     */
+    protected Version constructVersionFromDatabase(ResultSet row) throws SQLException {
+        Version result = new Version(row.getLong("id"));
+        result.setCommitTime(new Date(row.getTimestamp("createdAt").getTime()));
+
+        if(row.getObject("creator") != null) {
+            result.setCreator((KiWiResource) loadNodeById(row.getLong("creator")));
+        }
+
+        // query the versions_added and versions_removed join tables to reconstruct the triple sets
+        PreparedStatement queryAdded = getPreparedStatement("load.versions_added");
+        synchronized (queryAdded) {
+            queryAdded.clearParameters();
+            queryAdded.setLong(1,result.getId());
+            ResultSet addedRow = queryAdded.executeQuery();
+            try {
+                while(addedRow.next()) {
+                    result.addTriple(loadTripleById(addedRow.getLong("triple_id")));
+                }
+            } finally {
+                addedRow.close();
+            }
+        }
+
+        PreparedStatement queryRemoved = getPreparedStatement("load.versions_removed");
+        synchronized (queryRemoved) {
+            queryRemoved.clearParameters();
+            queryRemoved.setLong(1,result.getId());
+            ResultSet removedRow = queryRemoved.executeQuery();
+            try {
+                while(removedRow.next()) {
+                    result.removeTriple(loadTripleById(removedRow.getLong("triple_id")));
+                }
+            } finally {
+                removedRow.close();
+            }
+        }
+
+        return result;
+    }
+
+
+
+    /**
+     * Return a Sesame RepositoryResult of statements according to the query pattern given in the arguments. Each of
+     * the parameters subject, predicate, object and context may be null, indicating a wildcard query. If the boolean
+     * parameter "inferred" is set to true, the result will also include inferred triples, if it is set to false only
+     * base triples.
+     * <p/>
+     * The RepositoryResult holds a direct connection to the database and needs to be closed properly, or otherwise
+     * the system might run out of resources. The returned RepositoryResult will try its best to clean up when the
+     * iteration has completed or the garbage collector calls the finalize() method, but this can take longer than
+     * necessary.
+     *
+     *
+     * @param subject    the subject to query for, or null for a wildcard query
+     * @param predicate  the predicate to query for, or null for a wildcard query
+     * @param object     the object to query for, or null for a wildcard query
+     * @param context    the context to query for, or null for a wildcard query
+     * @param inferred   if true, the result will also contain triples inferred by the reasoner, if false not
+     * @return a new RepositoryResult with a direct connection to the database; the result should be properly closed
+     *         by the caller
+     */
+    public RepositoryResult<Statement> listTriplesSnapshot(KiWiResource subject, KiWiUriResource predicate, KiWiNode object, KiWiResource context, boolean inferred, Date snapshotDate) throws SQLException {
+
+        return new RepositoryResult<Statement>(
+                new ExceptionConvertingIteration<Statement, RepositoryException>(listTriplesInternalSnapshot(subject, predicate, object, context, inferred, snapshotDate)) {
+                    @Override
+                    protected RepositoryException convert(Exception e) {
+                        return new RepositoryException("database error while iterating over result set",e);
+                    }
+                }
+
+        );
+    }
+
+    /**
+     * Internal implementation for actually carrying out the query. Returns a closable iteration that can be used
+     * in a repository result. The iteration is forward-only and does not allow removing result rows.
+     *
+     * @param subject    the subject to query for, or null for a wildcard query
+     * @param predicate  the predicate to query for, or null for a wildcard query
+     * @param object     the object to query for, or null for a wildcard query
+     * @param context    the context to query for, or null for a wildcard query
+     * @param inferred   if true, the result will also contain triples inferred by the reasoner, if false not
+     * @return a ClosableIteration that wraps the database ResultSet; needs to be closed explicitly by the caller
+     * @throws SQLException
+     */
+    private CloseableIteration<Statement, SQLException> listTriplesInternalSnapshot(KiWiResource subject, KiWiUriResource predicate, KiWiNode object, KiWiResource context, boolean inferred, Date snapshotDate) throws SQLException {
+        // if one of the database ids is null, there will not be any database results, so we can return an empty result
+        if(subject != null && subject.getId() == null) {
+            return new EmptyIteration<Statement, SQLException>();
+        }
+        if(predicate != null && predicate.getId() == null) {
+            return new EmptyIteration<Statement, SQLException>();
+        }
+        if(object != null && object.getId() == null) {
+            return new EmptyIteration<Statement, SQLException>();
+        }
+        if(context != null && context.getId() == null) {
+            return new EmptyIteration<Statement, SQLException>();
+        }
+
+        // otherwise we need to create an appropriate SQL query and execute it, the repository result will be read-only
+        // and only allow forward iteration, so we can limit the query using the respective flags
+        PreparedStatement query = connection.prepareStatement(
+                constructTripleQuerySnapshot(subject, predicate, object, context, inferred, snapshotDate),
+                ResultSet.TYPE_FORWARD_ONLY,
+                ResultSet.CONCUR_READ_ONLY
+        );
+        query.clearParameters();
+
+        // set query parameters
+        query.setTimestamp(1, new Timestamp(snapshotDate.getTime()));
+        query.setTimestamp(2, new Timestamp(snapshotDate.getTime()));
+
+        int position = 3;
+        if(subject != null) {
+            query.setLong(position++, subject.getId());
+        }
+        if(predicate != null) {
+            query.setLong(position++, predicate.getId());
+        }
+        if(object != null) {
+            query.setLong(position++, object.getId());
+        }
+        if(context != null) {
+            query.setLong(position++, context.getId());
+        }
+
+        final ResultSet result = query.executeQuery();
+
+
+        return new ResultSetIteration<Statement>(result, true, new ResultTransformerFunction<Statement>() {
+            @Override
+            public Statement apply(ResultSet row) throws SQLException {
+                return constructTripleFromDatabase(result);
+            }
+        });
+    }
+
+    /**
+     * Construct the SQL query string from the query pattern passed as arguments
+     *
+     * @param subject    the subject to query for, or null for a wildcard query
+     * @param predicate  the predicate to query for, or null for a wildcard query
+     * @param object     the object to query for, or null for a wildcard query
+     * @param context    the context to query for, or null for a wildcard query
+     * @param inferred   if true, the result will also contain triples inferred by the reasoner, if false not
+     * @return an SQL query string representing the triple pattern
+     */
+    protected String constructTripleQuerySnapshot(KiWiResource subject, KiWiUriResource predicate, KiWiNode object, KiWiResource context, boolean inferred, Date snapshotDate) {
+        StringBuilder builder = new StringBuilder();
+        builder.append("SELECT id,subject,predicate,object,context,deleted,inferred,creator,createdAt,deletedAt FROM triples");
+        builder.append(" WHERE  createdAt <= ? AND (deleted = false OR deletedAt > ?)");
+        if(subject != null) {
+            builder.append(" AND subject = ?");
+        }
+        if(predicate != null) {
+            builder.append(" AND predicate = ?");
+        }
+        if(object != null) {
+            builder.append(" AND object = ?");
+        }
+        if(context != null) {
+            builder.append(" AND context = ?");
+        }
+        if(!inferred) {
+            builder.append(" AND inferred = false");
+        }
+        return builder.toString();
+
+    }
+
+    /**
+     * Count all non-deleted triples in the triple store at the given snapshot date
+     * @return
+     * @throws SQLException
+     */
+    public long getSnapshotSize(Date snapshotDate) throws SQLException {
+        PreparedStatement querySize = getPreparedStatement("query.snapshot_size");
+        querySize.setTimestamp(1, new Timestamp(snapshotDate.getTime()));
+        querySize.setTimestamp(2, new Timestamp(snapshotDate.getTime()));
+        ResultSet result = querySize.executeQuery();
+        try {
+            if(result.next()) {
+                return result.getLong(1);
+            } else {
+                return 0;
+            }
+        } finally {
+            result.close();
+        }
+    }
+
+    /**
+     * Count all non-deleted triples in the triple store at the given snapshot date
+     * @return
+     * @throws SQLException
+     */
+    public long getSnapshotSize(KiWiResource context, Date snapshotDate) throws SQLException {
+        if(context.getId() == null) {
+            return 0;
+        };
+        PreparedStatement querySize = getPreparedStatement("query.snapshot_size_ctx");
+        querySize.setLong(1,context.getId());
+        querySize.setTimestamp(2, new Timestamp(snapshotDate.getTime()));
+        querySize.setTimestamp(3, new Timestamp(snapshotDate.getTime()));
+
+        ResultSet result = querySize.executeQuery();
+        try {
+            if(result.next()) {
+                return result.getLong(1);
+            } else {
+                return 0;
+            }
+        } finally {
+            result.close();
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-marmotta/blob/c32963d5/kiwi/kiwi-versioning/src/main/java/org/apache/marmotta/kiwi/versioning/persistence/KiWiVersioningPersistence.java
----------------------------------------------------------------------
diff --git a/kiwi/kiwi-versioning/src/main/java/org/apache/marmotta/kiwi/versioning/persistence/KiWiVersioningPersistence.java b/kiwi/kiwi-versioning/src/main/java/org/apache/marmotta/kiwi/versioning/persistence/KiWiVersioningPersistence.java
new file mode 100644
index 0000000..98eb10f
--- /dev/null
+++ b/kiwi/kiwi-versioning/src/main/java/org/apache/marmotta/kiwi/versioning/persistence/KiWiVersioningPersistence.java
@@ -0,0 +1,85 @@
+/**
+ * Copyright (C) 2013 Salzburg Research.
+ *
+ * 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.apache.marmotta.kiwi.versioning.persistence;
+
+import org.apache.marmotta.kiwi.persistence.KiWiDialect;
+import org.apache.marmotta.kiwi.persistence.KiWiPersistence;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.sql.SQLException;
+
+/**
+ * This class handles all database access of the versioning component of the KiWi triple store. It provides
+ * methods for creating versions, deleting versions, listing versions, and accessing snapshots (following
+ * the Memento specification, http://mementoweb.org/)
+ * <p/>
+ * The KiWiVersioningPersistence makes use of a wrapped KiWiPersistence object passed as constructor argument.
+ * <p/>
+ * Author: Sebastian Schaffert
+ */
+public class KiWiVersioningPersistence {
+
+    private static Logger log = LoggerFactory.getLogger(KiWiVersioningPersistence.class);
+
+
+    /**
+     * Get the parent persistence service to access the database
+     */
+    private KiWiPersistence persistence;
+
+
+    public KiWiVersioningPersistence(KiWiPersistence persistence) {
+        this.persistence = persistence;
+
+        persistence.addNodeTableDependency("versions", "creator");
+        persistence.addTripleTableDependency("versions_added","triple_id");
+        persistence.addTripleTableDependency("versions_removed","triple_id");
+    }
+
+    /**
+     * Initialise the database, creating or upgrading tables if they do not exist or are of the wrong version.
+     * This method must only be called after the initDatabase of the wrapped KiWiPersistence has been evaluated.
+     */
+    public void initDatabase() throws SQLException {
+        persistence.initDatabase("versioning", new String[] {"versions", "versions_added", "versions_removed"});
+    }
+
+    /**
+     * Drop the versioning tables; this method must be called before the dropDatabase method of the underlying
+     * KiWiPersistence is called.
+     *
+     * @throws SQLException
+     */
+    public void dropDatabase() throws SQLException {
+        persistence.dropDatabase("versioning");
+    }
+
+    /**
+     * Return a connection from the connection pool which already has the auto-commit disabled.
+     *
+     * @return a fresh JDBC connection from the connection pool
+     * @throws java.sql.SQLException in case a new connection could not be established
+     */
+    public KiWiVersioningConnection getConnection() throws SQLException {
+        return new KiWiVersioningConnection(persistence.getJDBCConnection(), persistence.getDialect(), persistence.getCacheManager());
+    }
+
+
+    public KiWiDialect getDialect() {
+        return persistence.getDialect();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-marmotta/blob/c32963d5/kiwi/kiwi-versioning/src/main/java/org/apache/marmotta/kiwi/versioning/repository/SnapshotRepository.java
----------------------------------------------------------------------
diff --git a/kiwi/kiwi-versioning/src/main/java/org/apache/marmotta/kiwi/versioning/repository/SnapshotRepository.java b/kiwi/kiwi-versioning/src/main/java/org/apache/marmotta/kiwi/versioning/repository/SnapshotRepository.java
new file mode 100644
index 0000000..3469640
--- /dev/null
+++ b/kiwi/kiwi-versioning/src/main/java/org/apache/marmotta/kiwi/versioning/repository/SnapshotRepository.java
@@ -0,0 +1,81 @@
+/**
+ * Copyright (C) 2013 Salzburg Research.
+ *
+ * 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.apache.marmotta.kiwi.versioning.repository;
+
+import org.apache.marmotta.kiwi.versioning.model.Version;
+import org.apache.marmotta.kiwi.versioning.sail.KiWiVersioningSail;
+import org.openrdf.repository.RepositoryException;
+import org.openrdf.repository.RepositoryResult;
+import org.openrdf.repository.sail.SailRepository;
+import org.openrdf.repository.sail.SailRepositoryConnection;
+import org.openrdf.sail.SailException;
+
+import java.util.Date;
+
+/**
+ * A wrapper around a KiWiVersioningSail that allows accessing snapshots using the Repository API instead of the
+ * SAIL API.
+ * <p/>
+ * Author: Sebastian Schaffert
+ */
+public class SnapshotRepository extends SailRepository {
+
+    KiWiVersioningSail sail;
+
+    public SnapshotRepository(KiWiVersioningSail sail) {
+        super(sail);
+
+        this.sail = sail;
+    }
+
+    /**
+     * Get a read-only snapshot of the repository at the given date. Returns a repository 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
+     */
+    public SailRepositoryConnection getSnapshot(Date snapshotDate) throws RepositoryException {
+        try {
+            return new SnapshotRepositoryConnection(this, sail.getSnapshot(snapshotDate));
+        }
+        catch (SailException e) {
+            throw new RepositoryException(e);
+        }
+
+    }
+
+    /**
+     * List all versions of this repository.
+     *
+     * @return
+     */
+    public RepositoryResult<Version> listVersions() throws SailException {
+        return sail.listVersions();
+    }
+
+    /**
+     * List all versions of this repository between a start and end date.
+     *
+     * @return
+     */
+    public RepositoryResult<Version> listVersions(Date from, Date to) throws SailException {
+        return sail.listVersions(from, to);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-marmotta/blob/c32963d5/kiwi/kiwi-versioning/src/main/java/org/apache/marmotta/kiwi/versioning/repository/SnapshotRepositoryConnection.java
----------------------------------------------------------------------
diff --git a/kiwi/kiwi-versioning/src/main/java/org/apache/marmotta/kiwi/versioning/repository/SnapshotRepositoryConnection.java b/kiwi/kiwi-versioning/src/main/java/org/apache/marmotta/kiwi/versioning/repository/SnapshotRepositoryConnection.java
new file mode 100644
index 0000000..7de6136
--- /dev/null
+++ b/kiwi/kiwi-versioning/src/main/java/org/apache/marmotta/kiwi/versioning/repository/SnapshotRepositoryConnection.java
@@ -0,0 +1,32 @@
+/**
+ * Copyright (C) 2013 Salzburg Research.
+ *
+ * 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.apache.marmotta.kiwi.versioning.repository;
+
+import org.apache.marmotta.kiwi.versioning.sail.KiWiSnapshotConnection;
+import org.openrdf.repository.sail.SailRepository;
+import org.openrdf.repository.sail.SailRepositoryConnection;
+
+/**
+ * A wrapper class for snapshot repository connections.
+ * <p/>
+ * Author: Sebastian Schaffert
+ */
+public class SnapshotRepositoryConnection extends SailRepositoryConnection {
+
+    public SnapshotRepositoryConnection(SailRepository repository, KiWiSnapshotConnection sailConnection) {
+        super(repository, sailConnection);
+    }
+}