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

[42/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-triplestore/src/main/java/org/apache/marmotta/kiwi/model/rdf/KiWiTriple.java
----------------------------------------------------------------------
diff --git a/kiwi/kiwi-triplestore/src/main/java/org/apache/marmotta/kiwi/model/rdf/KiWiTriple.java b/kiwi/kiwi-triplestore/src/main/java/org/apache/marmotta/kiwi/model/rdf/KiWiTriple.java
new file mode 100644
index 0000000..298ea02
--- /dev/null
+++ b/kiwi/kiwi-triplestore/src/main/java/org/apache/marmotta/kiwi/model/rdf/KiWiTriple.java
@@ -0,0 +1,318 @@
+/**
+ * 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.rdf;
+
+import org.openrdf.model.Statement;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * KiWiTriples are one of the core concepts of the KiWi system. They
+ * correspond to triples in RDF, but extend them with additional information
+ * that is useful and/or needed in the KiWi system, for example author information,
+ * version information, justifications (in case of inferred triples), etc.
+ * 
+ * Like a KiWiResource, each triple is associated with a single TripleStore.
+ * All triples together essentially make up the TripleStore. As with KiWiResources,
+ * this means that there is no 1:1 correspondence between RDF triples and KiWi triples.
+ * An RDF triple may be represented by several KiWi triples in different KnowledgeSpaces.
+ * <p>
+ * Base/inferred flags denote the status of support of a triple:
+ * <ul>
+ * <li>base=true, inferred=true means  the triple is both explicit and supported by a rule</li>
+ * <li>base=false, inferred=true means triple is supported by a rule but not explicit</li>
+ * <li>base=true, inferred=false means triple is only explicit</li>
+ * <li>base=false, inferred=false does not exist</li>
+ * </ul>
+ * Only triples where base=true can be deleted using TripleStore.removeTriple; triples with
+ * base=false and inferred=true are managed by the reasoner.
+ *
+ * @author Sebastian Schaffert
+ */
+public class KiWiTriple  implements Statement, Serializable {
+    
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = -8726615974625660845L;
+	
+	private Long id;
+	
+
+    private KiWiResource    subject;
+    
+    private KiWiUriResource predicate;
+	
+    private KiWiNode        object;
+
+    private KiWiResource context;
+    
+    private KiWiResource creator;
+	
+	private Date created;
+
+    private Date deletedAt;
+
+	private Boolean deleted;
+
+    private Boolean inferred;
+
+    //@Transient
+    private Boolean markedForReasoning;
+
+	
+	public KiWiTriple() {
+		this.created = new Date();
+		this.deleted = false;
+        this.inferred = false;
+        this.markedForReasoning = false;
+        this.deletedAt = null;
+	}
+	
+
+
+	public KiWiTriple(KiWiResource subject, KiWiUriResource predicate, KiWiNode object, KiWiResource context) {
+		this();
+		this.subject = subject;
+		this.predicate = predicate;
+		this.object   = object;
+        this.context  = context;
+        this.deletedAt = null;
+
+        assert(subject  != null);
+        assert(predicate != null);
+        assert(object   != null);
+        assert(context  != null);
+	}
+
+   /**
+     * Get the object of this extended triple.
+     * @return
+     */
+    public KiWiNode getObject() {
+        return object;
+    }
+
+    /**
+     * Set the object of this extended triple to the given KiWiNode (either a resource or a literal)
+     * @param object
+     */
+    public void setObject(KiWiNode object) {
+        this.object = object;
+    }
+
+    /**
+     * Get the property of this extended triple. Always a KiWiUriResource.
+     * @return
+     */
+    public KiWiUriResource getPredicate() {
+        return predicate;
+    }
+
+    /**
+     * Set the property of this extended triple. Always needs to be a KiWiUriResource
+     * @param property
+     */
+    public void setPredicate(KiWiUriResource property) {
+        this.predicate = property;
+    }
+
+    /**
+     * Get the subject of this extended triple. Always a resource.
+     * @return
+     */
+    public KiWiResource getSubject() {
+        return subject;
+    }
+
+    /**
+     * Set the subject of this extended triple to the provided KiWiResource
+     * @param subject
+     */
+    public void setSubject(KiWiResource subject) {
+        this.subject = subject;
+    }
+
+    /**
+     * Get the unique triple identifier of this extended triple. Returns a KiWiUriResource identifying this triple.
+     * @return
+     */
+    public KiWiResource getContext() {
+        return context;
+    }
+
+    /**
+     * Set the unique triple identifier of this extended triple to the provided KiWiUriResource. The caller needs
+     * to ensure that the tripleId is unique over the KiWi system; otherwise, the system might not function correctly.
+     * @param context
+     */
+    public void setContext(KiWiResource context) {
+        this.context = context;
+    }
+
+    
+    /**
+     * Return the author of this extended triple.
+     * 
+     * Internally, this is determined using the tripleId of the extended triple and looking it up in the 
+     * database.
+     * 
+     * @return
+     */
+    public KiWiResource getCreator() {
+        return creator;
+    }
+    
+    /**
+     * Set the author of this extended triple.
+     * 
+     * Changes will be persisted as part of the database using the tripleId as unique identifier.
+     * 
+     * @param author
+     */
+    public void setCreator(KiWiResource author) {
+        this.creator = author;
+    }
+
+    
+    
+	/**
+	 * @return the created
+	 */
+	public Date getCreated() {
+		return new Date(created.getTime());
+	}
+
+	/**
+	 * @param created the created to set
+	 */
+	public void setCreated(Date created) {
+		this.created = new Date(created.getTime());
+	}
+
+    /**
+     * Return the date the triple has been deleted, or null in case the triple is not deleted
+     *
+     * @return
+     */
+    public Date getDeletedAt() {
+        return new Date(deletedAt.getTime());
+    }
+
+    /**
+     * Set the date the triple has been deleted.
+     *
+     * @param deletedAt
+     */
+    public void setDeletedAt(Date deletedAt) {
+        this.deletedAt = new Date(deletedAt.getTime());
+    }
+
+    /**
+	 * @return the id
+	 */
+	public Long getId() {
+		return id;
+	}
+
+	/**
+	 * @param id the id to set
+	 */
+	public void setId(Long id) {
+		this.id = id;
+	}
+
+	
+	
+	/**
+	 * @return the deleted
+	 */
+	public Boolean isDeleted() {
+		return deleted;
+	}
+
+
+	/**
+	 * @param deleted the deleted to set
+	 */
+	public void setDeleted(Boolean deleted) {
+		this.deleted = deleted;
+	}
+
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+//        if (!(o instanceof KiWiTriple)) return false;
+
+
+        KiWiTriple triple = (KiWiTriple) o;
+
+        if (!getContext().equals(triple.getContext())) return false;
+        if (!getObject().equals(triple.getObject())) return false;
+        if (!getPredicate().equals(triple.getPredicate())) return false;
+        return getSubject().equals(triple.getSubject());
+
+    }
+
+    @Override
+    public int hashCode() {
+        int result = getSubject().hashCode();
+        result = 31 * result + getPredicate().hashCode();
+        result = 31 * result + getObject().hashCode();
+        result = 31 * result + getContext().hashCode();
+        return result;
+    }
+
+
+    @Override
+    public String toString() {
+    	if(context != null) {
+    		return "{"+subject.toString()+" "+ predicate.toString()+" "+object.toString()+"}@"+context.toString();
+    	} else {
+    		return "{"+subject.toString()+" "+ predicate.toString()+" "+object.toString()+"}@GLOBAL";   		
+    	}
+    }
+    
+    /**
+     * Return a unique key to be used in caches and similar.
+     * 
+     * @return
+     */
+    public String getKey() {
+    	return toString();
+    }
+
+
+
+    public boolean isInferred() {
+        return inferred;
+    }
+
+    public void setInferred(boolean inferred) {
+        this.inferred = inferred;
+    }
+
+
+    public Boolean isMarkedForReasoning() {
+        return markedForReasoning;
+    }
+
+    public void setMarkedForReasoning(Boolean markedForReasoning) {
+        this.markedForReasoning = markedForReasoning;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-marmotta/blob/c32963d5/kiwi/kiwi-triplestore/src/main/java/org/apache/marmotta/kiwi/model/rdf/KiWiUriResource.java
----------------------------------------------------------------------
diff --git a/kiwi/kiwi-triplestore/src/main/java/org/apache/marmotta/kiwi/model/rdf/KiWiUriResource.java b/kiwi/kiwi-triplestore/src/main/java/org/apache/marmotta/kiwi/model/rdf/KiWiUriResource.java
new file mode 100644
index 0000000..5b4dce5
--- /dev/null
+++ b/kiwi/kiwi-triplestore/src/main/java/org/apache/marmotta/kiwi/model/rdf/KiWiUriResource.java
@@ -0,0 +1,151 @@
+/**
+ * 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.rdf;
+
+import at.newmedialab.sesame.commons.model.URICommons;
+import com.google.common.hash.HashCode;
+import com.google.common.hash.HashFunction;
+import com.google.common.hash.Hashing;
+import org.openrdf.model.URI;
+
+/**
+ * Add file description here!
+ * <p/>
+ * User: sschaffe
+ */
+public class KiWiUriResource extends KiWiResource implements URI {
+
+	private static final long serialVersionUID = -6399293877969640084L;
+
+	private static HashFunction hasher = Hashing.goodFastHash(16);
+
+    //@Transient
+    private HashCode goodHashCode;
+
+    private String uri;
+
+
+    //@Transient
+    private String namespace;
+
+    //@Transient
+    private String localName;
+
+    @Deprecated
+    public KiWiUriResource() {
+        super();
+    }
+
+    public KiWiUriResource(String uri) {
+        super();
+        this.uri = uri;
+    }
+
+    /**
+     * @deprecated use {@link #stringValue()} instead.
+     */
+    @Deprecated
+    public String getUri() {
+        return uri;
+    }
+
+    @Deprecated
+    public void setUri(String uri) {
+        this.uri = uri;
+    }
+
+    /**
+     * Gets the local name of this URI. The local name is defined as per the
+     * algorithm described in the class documentation.
+     *
+     * @return The URI's local name.
+     */
+    @Override
+    public String getLocalName() {
+        initNamespace();
+
+        return localName;
+    }
+
+    /**
+     * Gets the namespace of this URI. The namespace is defined as per the
+     * algorithm described in the class documentation.
+     *
+     * @return The URI's namespace.
+     */
+    @Override
+    public String getNamespace() {
+        initNamespace();
+
+        return namespace;
+    }
+
+    /**
+     * Returns the String-value of a <tt>Value</tt> object. This returns either
+     * a {@link org.openrdf.model.Literal}'s label, a {@link org.openrdf.model.URI}'s URI or a {@link org.openrdf.model.BNode}'s ID.
+     */
+    @Override
+    public String stringValue() {
+        return uri;
+    }
+
+    @Override
+    public boolean isAnonymousResource() {
+        return false;
+    }
+
+    @Override
+    public boolean isUriResource() {
+        return true;
+    }
+
+
+    @Override
+    public String toString() {
+        return uri;
+    }
+
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+
+        if(o instanceof URI) {
+            return this.stringValue().equals(((URI)o).stringValue());
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        if(goodHashCode == null) {
+            goodHashCode = hasher.newHasher().putChar('U').putString(getUri()).hash();
+        }
+
+        return goodHashCode.hashCode();
+    }
+
+
+    private void initNamespace() {
+        if(namespace == null || localName == null) {
+            String[] components = URICommons.splitNamespace(uri);
+            namespace = components[0];
+            localName = components[1];
+        }
+    }
+
+}
+

http://git-wip-us.apache.org/repos/asf/incubator-marmotta/blob/c32963d5/kiwi/kiwi-triplestore/src/main/java/org/apache/marmotta/kiwi/persistence/KiWiConnection.java
----------------------------------------------------------------------
diff --git a/kiwi/kiwi-triplestore/src/main/java/org/apache/marmotta/kiwi/persistence/KiWiConnection.java b/kiwi/kiwi-triplestore/src/main/java/org/apache/marmotta/kiwi/persistence/KiWiConnection.java
new file mode 100644
index 0000000..faa578c
--- /dev/null
+++ b/kiwi/kiwi-triplestore/src/main/java/org/apache/marmotta/kiwi/persistence/KiWiConnection.java
@@ -0,0 +1,1535 @@
+/**
+ * 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.persistence;
+
+import at.newmedialab.sesame.commons.model.LiteralCommons;
+import at.newmedialab.sesame.commons.model.Namespaces;
+import at.newmedialab.sesame.commons.util.DateUtils;
+import com.google.common.base.Preconditions;
+import info.aduna.iteration.CloseableIteration;
+import info.aduna.iteration.EmptyIteration;
+import info.aduna.iteration.ExceptionConvertingIteration;
+import net.sf.ehcache.Cache;
+import net.sf.ehcache.Element;
+import org.apache.commons.lang.LocaleUtils;
+import org.apache.marmotta.kiwi.caching.KiWiCacheManager;
+import org.apache.marmotta.kiwi.model.rdf.*;
+import org.apache.marmotta.kiwi.persistence.util.ResultSetIteration;
+import org.apache.marmotta.kiwi.persistence.util.ResultTransformerFunction;
+import org.openrdf.model.Literal;
+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;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A KiWiConnection offers methods for storing and retrieving KiWiTriples, KiWiNodes, and KiWiNamespaces in the
+ * database. It wraps a JDBC connection which will be committed on commit(), rolled back on rollback() and
+ * closed on close();
+ * <p/>
+ * Author: Sebastian Schaffert
+ */
+public class KiWiConnection {
+
+    private static Logger log = LoggerFactory.getLogger(KiWiConnection.class);
+
+
+    protected KiWiDialect dialect;
+
+    protected Connection connection;
+
+
+    protected KiWiCacheManager cacheManager;
+
+
+    /**
+     * Cache nodes by database ID
+     */
+    private Cache nodeCache;
+
+    /**
+     * Cache triples by database ID
+     */
+    private Cache tripleCache;
+
+
+    /**
+     * Cache URI resources by uri
+     */
+    private Cache uriCache;
+
+
+    /**
+     * Cache BNodes by BNode ID
+     */
+    private Cache bnodeCache;
+
+    /**
+     * Cache literals by literal cache key (LiteralCommons#createCacheKey(String,Locale,URI))
+     */
+    private Cache literalCache;
+
+
+    /**
+     * Look up namespaces by URI
+     */
+    private Cache namespaceUriCache;
+
+    /**
+     * Look up namespaces by prefix
+     */
+    private Cache namespacePrefixCache;
+
+    /**
+     * Cache instances of locales for language tags
+     */
+    private static Map<String,Locale> localeMap = new HashMap<String, Locale>();
+
+
+    private Map<String,PreparedStatement> statementCache;
+
+
+    public KiWiConnection(Connection connection, KiWiDialect dialect, KiWiCacheManager cacheManager) throws SQLException {
+        this.cacheManager = cacheManager;
+        this.connection = connection;
+        this.dialect = dialect;
+
+        initCachePool();
+        initStatementCache();
+    }
+
+    private void initCachePool() {
+        nodeCache    = cacheManager.getNodeCache();
+        tripleCache  = cacheManager.getTripleCache();
+        uriCache     = cacheManager.getUriCache();
+        bnodeCache   = cacheManager.getBNodeCache();
+        literalCache = cacheManager.getLiteralCache();
+
+        namespacePrefixCache = cacheManager.getNamespacePrefixCache();
+        namespaceUriCache    = cacheManager.getNamespaceUriCache();
+    }
+
+    /**
+     * Load all prepared statements of the dialect into the statement cache
+     * @throws SQLException
+     */
+    private void initStatementCache() throws SQLException {
+        statementCache = new HashMap<String, PreparedStatement>();
+
+        /*
+        for(String key : dialect.getStatementIdentifiers()) {
+            statementCache.put(key,connection.prepareStatement(dialect.getStatement(key)));
+        }
+        */
+    }
+
+
+    /**
+     * Get direct access to the JDBC connection used by this KiWiConnection.
+     *
+     * @return
+     */
+    public Connection getJDBCConnection() {
+        return connection;
+    }
+
+    /**
+     * Return the cache manager used by this connection
+     * @return
+     */
+    public KiWiCacheManager getCacheManager() {
+        return cacheManager;
+    }
+
+    /**
+     * Load a KiWiNamespace with the given prefix, or null if the namespace does not exist. The method will first
+     * look in the node cache for cached nodes. If no cache entry is found, it will run a database query
+     * ("load.namespace_prefix").
+     *
+     * @param prefix  the prefix to look for
+     * @return the KiWiNamespace with this prefix or null if it does not exist
+     * @throws SQLException
+     */
+    public KiWiNamespace loadNamespaceByPrefix(String prefix) throws SQLException {
+        Element element = namespacePrefixCache.get(prefix);
+        if(element != null) {
+            return (KiWiNamespace)element.getObjectValue();
+        }
+
+        // prepare a query; we will only iterate once, read only, and need only one result row since the id is unique
+        PreparedStatement query = getPreparedStatement("load.namespace_prefix");
+        query.setString(1, prefix);
+        query.setMaxRows(1);
+
+        // run the database query and if it yields a result, construct a new node; the method call will take care of
+        // caching the constructed node for future calls
+        ResultSet result = query.executeQuery();
+        try {
+            if(result.next()) {
+                return constructNamespaceFromDatabase(result);
+            } else {
+                return null;
+            }
+        } finally {
+            result.close();
+        }
+    }
+
+    /**
+     * Load a KiWiNamespace with the given uri, or null if the namespace does not exist. The method will first
+     * look in the node cache for cached nodes. If no cache entry is found, it will run a database query
+     * ("load.namespace_prefix").
+     *
+     * @param uri  the uri to look for
+     * @return the KiWiNamespace with this uri or null if it does not exist
+     * @throws SQLException
+     */
+    public KiWiNamespace loadNamespaceByUri(String uri) throws SQLException {
+        Element element = namespaceUriCache.get(uri);
+        if(element != null) {
+            return (KiWiNamespace)element.getObjectValue();
+        }
+
+        // prepare a query; we will only iterate once, read only, and need only one result row since the id is unique
+        PreparedStatement query = getPreparedStatement("load.namespace_uri");
+        query.setString(1, uri);
+        query.setMaxRows(1);
+
+        // run the database query and if it yields a result, construct a new node; the method call will take care of
+        // caching the constructed node for future calls
+        ResultSet result = query.executeQuery();
+        try {
+            if(result.next()) {
+                return constructNamespaceFromDatabase(result);
+            } else {
+                return null;
+            }
+        } finally {
+            result.close();
+        }
+    }
+
+    /**
+     * Store the namespace passed as argument in the database. The database might enfore unique constraints and
+     * thus throw an exception in case the prefix or URI is already used.
+     *
+     * @param namespace the namespace to store
+     * @throws SQLException the prefix or URI is already used, or a database error occurred
+     */
+    public void storeNamespace(KiWiNamespace namespace) throws SQLException {
+        // TODO: add unique constraints to table
+        if(namespace.getId() != null) {
+            log.warn("trying to store namespace which is already persisted: {}",namespace);
+            return;
+        }
+
+        namespace.setId(getNextSequence("seq.namespaces"));
+
+        PreparedStatement insertNamespace = getPreparedStatement("store.namespace");
+        insertNamespace.setLong(1,namespace.getId());
+        insertNamespace.setString(2,namespace.getPrefix());
+        insertNamespace.setString(3,namespace.getUri());
+        insertNamespace.setTimestamp(4,new Timestamp(namespace.getCreated().getTime()));
+
+        insertNamespace.executeUpdate();
+
+        namespacePrefixCache.put(new Element(namespace.getPrefix(),namespace));
+        namespaceUriCache.put(new Element(namespace.getUri(),namespace));
+    }
+
+    /**
+     * Delete the namespace passed as argument from the database and from the caches.
+     * @param namespace the namespace to delete
+     * @throws SQLException in case a database error occurred
+     */
+    public void deleteNamespace(KiWiNamespace namespace) throws SQLException {
+        if(namespace.getId() == null) {
+            log.warn("trying to remove namespace which is not persisted: {}",namespace);
+            return;
+        }
+
+        PreparedStatement deleteNamespace = getPreparedStatement("delete.namespace");
+        deleteNamespace.setLong(1, namespace.getId());
+        deleteNamespace.executeUpdate();
+
+        namespacePrefixCache.remove(namespace.getPrefix());
+        namespaceUriCache.remove(namespace.getUri());
+    }
+
+    /**
+     * Count all non-deleted triples in the triple store
+     * @return
+     * @throws SQLException
+     */
+    public long getSize() throws SQLException {
+        PreparedStatement querySize = getPreparedStatement("query.size");
+        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
+     * @return
+     * @throws SQLException
+     */
+    public long getSize(KiWiResource context) throws SQLException {
+        if(context.getId() == null) {
+            return 0;
+        };
+        PreparedStatement querySize = getPreparedStatement("query.size_ctx");
+        querySize.setLong(1,context.getId());
+
+        ResultSet result = querySize.executeQuery();
+        try {
+            if(result.next()) {
+                return result.getLong(1);
+            } else {
+                return 0;
+            }
+        } finally {
+            result.close();
+        }
+    }
+
+    /**
+     * Load a KiWiNode by database ID. The method will first look in the node cache for cached nodes. If
+     * no cache entry is found, it will run a database query ('load.node_by_id') on the NODES table and
+     * construct an appropriate subclass instance of KiWiNode with the obtained values. The result will be
+     * constructed based on the value of the NTYPE column as follows:
+     * <ul>
+     *     <li>'uri' - KiWiUriResource using the id and svalue (as URI) columns</li>
+     *     <li>'bnode' - KiWiAnonResource using the id and svalue (as AnonId) columns</li>
+     *     <li>'string' - KiWiStringLiteral using the id, svalue (literal value), lang (literal
+     *         language) and ltype (literal type) columns</li>
+     *     <li>'int' - KiWiIntLiteral using the id, svalue (string value), ivalue (integer value)
+     *         and ltype (literal type) columns</li>
+     *     <li>'double' - KiWiDoubleLiteral using the id, svalue (string value), dvalue (double
+     *         value) and ltype (literal type) columns</li>
+     *     <li>'boolean' - KiWiBooleanLiteral using the id, svalue (string value), bvalue (boolean
+     *         value) and ltype (literal type) columns</li>
+     *     <li>'date' - KiWiDateLiteral using the id, svalue (string value), tvalue (time value)
+     *         and ltype (literal type) columns</li>
+     * </ul>
+     * When a node is loaded from the database, it will be added to the different caches to speed up
+     * subsequent requests.
+     *
+     * @param id the database id of the node to load
+     * @return an instance of a KiWiNode subclass representing the node with the given database id;
+     *     type depends on value of the ntype column
+     */
+    public KiWiNode loadNodeById(Long id) throws SQLException {
+
+        // look in cache
+        Element element = nodeCache.get(id);
+        if(element != null) {
+            return (KiWiNode)element.getObjectValue();
+        }
+
+        // prepare a query; we will only iterate once, read only, and need only one result row since the id is unique
+        PreparedStatement query = getPreparedStatement("load.node_by_id");
+        query.setLong(1,id);
+        query.setMaxRows(1);
+
+        // run the database query and if it yields a result, construct a new node; the method call will take care of
+        // caching the constructed node for future calls
+        ResultSet result = query.executeQuery();
+        try {
+            if(result.next()) {
+                return constructNodeFromDatabase(result);
+            } else {
+                return null;
+            }
+        } finally {
+            result.close();
+        }
+
+    }
+
+    public KiWiTriple loadTripleById(Long id) throws SQLException {
+
+        // look in cache
+        Element element = tripleCache.get(id);
+        if(element != null) {
+            return (KiWiTriple)element.getObjectValue();
+        }
+
+        // prepare a query; we will only iterate once, read only, and need only one result row since the id is unique
+        PreparedStatement query = getPreparedStatement("load.triple_by_id");
+        query.setLong(1,id);
+        query.setMaxRows(1);
+
+        // run the database query and if it yields a result, construct a new node; the method call will take care of
+        // caching the constructed node for future calls
+        ResultSet result = query.executeQuery();
+        try {
+            if(result.next()) {
+                return constructTripleFromDatabase(result);
+            } else {
+                return null;
+            }
+        } finally {
+            result.close();
+        }
+
+    }
+
+    /**
+     * Load a KiWiUriResource by URI. The method will first look in the node cache for cached nodes. If
+     * no cache entry is found, it will run a database query ('load.uri_by_uri') on the NODES table and
+     * construct a new KiWiUriResource using the values of the id and svalue columns.
+     * <p/>
+     * When a node is loaded from the database, it will be added to the different caches to speed up
+     * subsequent requests.
+     *
+     * @param uri the URI of the resource to load
+     * @return the KiWiUriResource identified by the given URI  or null if it does not exist
+     */
+    public KiWiUriResource loadUriResource(String uri) throws SQLException {
+        // look in cache
+        Element element = uriCache.get(uri);
+        if(element != null) {
+            return (KiWiUriResource)element.getObjectValue();
+        }
+
+        // prepare a query; we will only iterate once, read only, and need only one result row since the id is unique
+        PreparedStatement query = getPreparedStatement("load.uri_by_uri");
+        query.setString(1, uri);
+        query.setMaxRows(1);
+
+        // run the database query and if it yields a result, construct a new node; the method call will take care of
+        // caching the constructed node for future calls
+        ResultSet result = query.executeQuery();
+        try {
+            if(result.next()) {
+                return (KiWiUriResource)constructNodeFromDatabase(result);
+            } else {
+                return null;
+            }
+        } finally {
+            result.close();
+        }
+    }
+
+
+    /**
+     * Load a KiWiAnonResource by anonymous ID. The method will first look in the node cache for
+     * cached nodes. If no cache entry is found, it will run a database query ('load.bnode_by_anonid')
+     * on the NODES table and construct a new KiWiAnonResource using the values of the id and
+     * svalue columns.
+     * <p/>
+     * When a node is loaded from the database, it will be added to the different caches to speed up
+     * subsequent requests.
+     *
+     * @param id the anonymous ID of the resource to load
+     * @return the KiWiAnonResource identified by the given internal ID or null if it does not exist
+     */
+    public KiWiAnonResource loadAnonResource(String id) throws SQLException {
+        // look in cache
+        Element element = bnodeCache.get(id);
+        if(element != null) {
+            return (KiWiAnonResource)element.getObjectValue();
+        }
+
+        // prepare a query; we will only iterate once, read only, and need only one result row since the id is unique
+        PreparedStatement query = getPreparedStatement("load.bnode_by_anonid");
+        query.setString(1,id);
+        query.setMaxRows(1);
+
+        // run the database query and if it yields a result, construct a new node; the method call will take care of
+        // caching the constructed node for future calls
+        ResultSet result = query.executeQuery();
+        try {
+            if(result.next()) {
+                return (KiWiAnonResource)constructNodeFromDatabase(result);
+            } else {
+                return null;
+            }
+        } finally {
+            result.close();
+        }
+    }
+
+    /**
+     * Load a literal based on the value, language and type passed as argument. The method will first look in the node cache for
+     * cached nodes. If no cache entry is found, it will run a database query ("load.literal_by_v")
+     * on the NODES table and construct a new KiWiLiteral using the values of the literal columns (svalue, ivalue, ...). The
+     * type of literal returned depends on the value of the ntype column.
+     * <p/>
+     * When a node is loaded from the database, it will be added to the different caches to speed up
+     * subsequent requests.
+     *
+     * @param value string value of the literal to load
+     * @param lang  language of the literal to load (optional, 2-letter language code with optional country)
+     * @param ltype the type of the literal to load (optional)
+     * @return the literal matching the given arguments or null if it does not exist
+     * @throws SQLException
+     */
+    public KiWiLiteral loadLiteral(String value, String lang, KiWiUriResource ltype) throws SQLException {
+        // look in cache
+        final Element element = literalCache.get(LiteralCommons.createCacheKey(value,getLocale(lang), ltype));
+        if(element != null) {
+            return (KiWiLiteral)element.getObjectValue();
+        }
+
+        // ltype not persisted
+        if(ltype != null && ltype.getId() == null) {
+            return null;
+        }
+
+        // otherwise prepare a query, depending on the parameters given
+        final PreparedStatement query;
+        if(lang == null && ltype == null) {
+            query = getPreparedStatement("load.literal_by_v");
+            query.setString(1,value);
+        } else if(lang != null) {
+            query = getPreparedStatement("load.literal_by_vl");
+            query.setString(1,value);
+            query.setString(2, lang);
+        } else if(ltype != null) {
+            query = getPreparedStatement("load.literal_by_vt");
+            query.setString(1,value);
+            query.setLong(2,ltype.getId());
+        } else {
+            // This cannot happen...
+            throw new IllegalArgumentException("Impossible combination of lang/type in loadLiteral!");
+        }
+
+        // run the database query and if it yields a result, construct a new node; the method call will take care of
+        // caching the constructed node for future calls
+        ResultSet result = query.executeQuery();
+        try {
+            if(result.next()) {
+                return (KiWiLiteral)constructNodeFromDatabase(result);
+            } else {
+                return null;
+            }
+        } finally {
+            result.close();
+        }
+    }
+
+    /**
+     * Load a literal with the date value given as argument if it exists. The method will first look in
+     * the node cache for cached nodes. If no cache entry is found, it will run a database query ("load.literal_by_tv")
+     * on the NODES table and construct a new KiWiLiteral using the values of the literal columns
+     * (svalue, ivalue, ...). The type of literal returned depends on the value of the ntype column.
+     * <p/>
+     * When a node is loaded from the database, it will be added to the different caches to speed up
+     * subsequent requests.
+     *
+     * @param date the date of the date literal to load
+     * @return a KiWiDateLiteral with the correct date, or null if it does not exist
+     * @throws SQLException
+     */
+    public KiWiDateLiteral loadLiteral(Date date) throws SQLException {
+        KiWiUriResource ltype = loadUriResource(Namespaces.NS_XSD + "dateTime");
+
+        if(ltype == null || ltype.getId() == null) {
+            return null;
+        }
+
+        // look in cache
+        Element element = literalCache.get(LiteralCommons.createCacheKey(DateUtils.getDateWithoutFraction(date),ltype.stringValue()));
+        if(element != null) {
+            return (KiWiDateLiteral)element.getObjectValue();
+        }
+
+        // otherwise prepare a query, depending on the parameters given
+        PreparedStatement query = getPreparedStatement("load.literal_by_tv");
+        query.setTimestamp(1, new Timestamp(DateUtils.getDateWithoutFraction(date).getTime()));
+        query.setLong(2,ltype.getId());
+
+        // run the database query and if it yields a result, construct a new node; the method call will take care of
+        // caching the constructed node for future calls
+        ResultSet result = query.executeQuery();
+        try {
+            if(result.next()) {
+                return (KiWiDateLiteral)constructNodeFromDatabase(result);
+            } else {
+                return null;
+            }
+        } finally {
+            result.close();
+        }
+    }
+
+
+    /**
+     * Load a integer literal with the long value given as argument if it exists. The method will first look in
+     * the node cache for cached nodes. If no cache entry is found, it will run a database query ("load.literal_by_iv")
+     * on the NODES table and construct a new KiWiLiteral using the values of the literal columns
+     * (svalue, ivalue, ...). The type of literal returned depends on the value of the ntype column.
+     * <p/>
+     * When a node is loaded from the database, it will be added to the different caches to speed up
+     * subsequent requests.
+     *
+     * @param value the value of the integer literal to load
+     * @return a KiWiIntLiteral with the correct value, or null if it does not exist
+     * @throws SQLException
+     */
+    public KiWiIntLiteral loadLiteral(long value) throws SQLException {
+        KiWiUriResource ltype = loadUriResource(Namespaces.NS_XSD + "integer");
+
+        // ltype not persisted
+        if(ltype == null || ltype.getId() == null) {
+            return null;
+        }
+
+        // look in cache
+        Element element = literalCache.get(LiteralCommons.createCacheKey(Long.toString(value),null,ltype.stringValue()));
+        if(element != null) {
+            return (KiWiIntLiteral)element.getObjectValue();
+        }
+
+        // otherwise prepare a query, depending on the parameters given
+        PreparedStatement query = getPreparedStatement("load.literal_by_iv");
+        query.setLong(1,value);
+        query.setLong(2,ltype.getId());
+
+        // run the database query and if it yields a result, construct a new node; the method call will take care of
+        // caching the constructed node for future calls
+        ResultSet result = query.executeQuery();
+        try {
+            if(result.next()) {
+                return (KiWiIntLiteral)constructNodeFromDatabase(result);
+            } else {
+                return null;
+            }
+        } finally {
+            result.close();
+        }
+    }
+
+    /**
+     * Load a double literal with the double value given as argument if it exists. The method will first look in
+     * the node cache for cached nodes. If no cache entry is found, it will run a database query ("load.literal_by_dv")
+     * on the NODES table and construct a new KiWiLiteral using the values of the literal columns
+     * (svalue, ivalue, ...). The type of literal returned depends on the value of the ntype column.
+     * <p/>
+     * When a node is loaded from the database, it will be added to the different caches to speed up
+     * subsequent requests.
+     *
+     * @param value the value of the integer literal to load
+     * @return a KiWiDoubleLiteral with the correct value, or null if it does not exist
+     * @throws SQLException
+     */
+    public KiWiDoubleLiteral loadLiteral(double value) throws SQLException {
+        KiWiUriResource ltype = loadUriResource(Namespaces.NS_XSD + "double");
+
+        // ltype not persisted
+        if(ltype == null || ltype.getId() == null) {
+            return null;
+        }
+
+        // look in cache
+        Element element = literalCache.get(LiteralCommons.createCacheKey(Double.toString(value),null,ltype.stringValue()));
+        if(element != null) {
+            return (KiWiDoubleLiteral)element.getObjectValue();
+        }
+
+        // otherwise prepare a query, depending on the parameters given
+        PreparedStatement query = getPreparedStatement("load.literal_by_dv");
+        query.setDouble(1, value);
+        query.setLong(2,ltype.getId());
+
+        // run the database query and if it yields a result, construct a new node; the method call will take care of
+        // caching the constructed node for future calls
+        ResultSet result = query.executeQuery();
+        try {
+            if(result.next()) {
+                return (KiWiDoubleLiteral)constructNodeFromDatabase(result);
+            } else {
+                return null;
+            }
+        } finally {
+            result.close();
+        }
+    }
+
+    /**
+     * Load a boolean literal with the boolean value given as argument if it exists. The method will first look in
+     * the node cache for cached nodes. If no cache entry is found, it will run a database query ("load.literal_by_bv")
+     * on the NODES table and construct a new KiWiLiteral using the values of the literal columns
+     * (svalue, ivalue, ...). The type of literal returned depends on the value of the ntype column.
+     * <p/>
+     * When a node is loaded from the database, it will be added to the different caches to speed up
+     * subsequent requests.
+     *
+     * @param value the value of the integer literal to load
+     * @return a KiWiBooleanLiteral with the correct value, or null if it does not exist
+     * @throws SQLException
+     */
+    public KiWiBooleanLiteral loadLiteral(boolean value) throws SQLException {
+        KiWiUriResource ltype = loadUriResource(Namespaces.NS_XSD + "boolean");
+
+        // ltype not persisted
+        if(ltype == null || ltype.getId() == null) {
+            return null;
+        }
+
+        // look in cache
+        Element element = literalCache.get(LiteralCommons.createCacheKey(Boolean.toString(value),null,ltype.stringValue()));
+        if(element != null) {
+            return (KiWiBooleanLiteral)element.getObjectValue();
+        }
+
+
+        // otherwise prepare a query, depending on the parameters given
+        PreparedStatement query = getPreparedStatement("load.literal_by_bv");
+        query.setBoolean(1, value);
+        query.setLong(2,ltype.getId());
+
+        // run the database query and if it yields a result, construct a new node; the method call will take care of
+        // caching the constructed node for future calls
+        ResultSet result = query.executeQuery();
+        try {
+            if(result.next()) {
+                return (KiWiBooleanLiteral)constructNodeFromDatabase(result);
+            } else {
+                return null;
+            }
+        } finally {
+            result.close();
+        }
+    }
+
+//    public KiWiTriple loadTripleById(Long id) {
+//        // TODO: transactional caching
+//        return null;
+//    }
+
+
+    /**
+     * Store a new node in the database. The method will retrieve a new database id for the node and update the
+     * passed object. Afterwards, the node data will be inserted into the database using appropriate INSERT
+     * statements. The caller must make sure the connection is committed and closed properly.
+     * <p/>
+     * If the node already has an ID, the method will do nothing (assuming that it is already persistent)
+     *
+     * @param node
+     * @throws SQLException
+     */
+    public synchronized void storeNode(KiWiNode node) throws SQLException {
+        // if the node already has an ID, storeNode should not be called, since it is already persisted
+        if(node.getId() != null) {
+            log.warn("node {} already had a node ID, not persisting", node);
+            return;
+        }
+
+        // ensure the data type of a literal is persisted first
+        if(node instanceof KiWiLiteral) {
+            KiWiLiteral literal = (KiWiLiteral)node;
+            if(literal.getType() != null && literal.getType().getId() == null) {
+                storeNode(literal.getType());
+            }
+        }
+
+        // retrieve a new node id and set it in the node object
+        node.setId(getNextSequence("seq.nodes"));
+
+        // distinguish the different node types and run the appropriate updates
+        if(node instanceof KiWiUriResource) {
+            KiWiUriResource uriResource = (KiWiUriResource)node;
+
+            PreparedStatement insertNode = getPreparedStatement("store.uri");
+            insertNode.setLong(1,node.getId());
+            insertNode.setString(2,uriResource.stringValue());
+            insertNode.setTimestamp(3, new Timestamp(uriResource.getCreated().getTime()));
+            insertNode.executeUpdate();
+
+        } else if(node instanceof KiWiAnonResource) {
+            KiWiAnonResource anonResource = (KiWiAnonResource)node;
+
+            PreparedStatement insertNode = getPreparedStatement("store.bnode");
+            insertNode.setLong(1,node.getId());
+            insertNode.setString(2,anonResource.stringValue());
+            insertNode.setTimestamp(3, new Timestamp(anonResource.getCreated().getTime()));
+            insertNode.executeUpdate();
+        } else if(node instanceof KiWiDateLiteral) {
+            KiWiDateLiteral dateLiteral = (KiWiDateLiteral)node;
+
+            PreparedStatement insertNode = getPreparedStatement("store.tliteral");
+            insertNode.setLong(1,node.getId());
+            insertNode.setString(2, dateLiteral.stringValue());
+            insertNode.setTimestamp(3, new Timestamp(dateLiteral.getDateContent().getTime()));
+            if(dateLiteral.getType() != null)
+                insertNode.setLong(4,dateLiteral.getType().getId());
+            else
+                throw new IllegalStateException("a date literal must have a datatype");
+            insertNode.setTimestamp(5, new Timestamp(dateLiteral.getCreated().getTime()));
+
+            insertNode.executeUpdate();
+        } else if(node instanceof KiWiIntLiteral) {
+            KiWiIntLiteral intLiteral = (KiWiIntLiteral)node;
+
+            PreparedStatement insertNode = getPreparedStatement("store.iliteral");
+            insertNode.setLong(1,node.getId());
+            insertNode.setString(2, intLiteral.getContent());
+            insertNode.setDouble(3, intLiteral.getDoubleContent());
+            insertNode.setLong(4, intLiteral.getIntContent());
+            if(intLiteral.getType() != null)
+                insertNode.setLong(5,intLiteral.getType().getId());
+            else
+                throw new IllegalStateException("an integer literal must have a datatype");
+            insertNode.setTimestamp(6, new Timestamp(intLiteral.getCreated().getTime()));
+
+            insertNode.executeUpdate();
+        } else if(node instanceof KiWiDoubleLiteral) {
+            KiWiDoubleLiteral doubleLiteral = (KiWiDoubleLiteral)node;
+
+            PreparedStatement insertNode = getPreparedStatement("store.dliteral");
+            insertNode.setLong(1, node.getId());
+            insertNode.setString(2, doubleLiteral.getContent());
+            insertNode.setDouble(3, doubleLiteral.getDoubleContent());
+            if(doubleLiteral.getType() != null)
+                insertNode.setLong(4,doubleLiteral.getType().getId());
+            else
+                throw new IllegalStateException("a double literal must have a datatype");
+            insertNode.setTimestamp(5, new Timestamp(doubleLiteral.getCreated().getTime()));
+
+            insertNode.executeUpdate();
+        } else if(node instanceof KiWiBooleanLiteral) {
+            KiWiBooleanLiteral booleanLiteral = (KiWiBooleanLiteral)node;
+
+            PreparedStatement insertNode = getPreparedStatement("store.bliteral");
+            insertNode.setLong(1,node.getId());
+            insertNode.setString(2, booleanLiteral.getContent());
+            insertNode.setBoolean(3, booleanLiteral.booleanValue());
+            if(booleanLiteral.getType() != null)
+                insertNode.setLong(4,booleanLiteral.getType().getId());
+            else
+                throw new IllegalStateException("a boolean literal must have a datatype");
+            insertNode.setTimestamp(5, new Timestamp(booleanLiteral.getCreated().getTime()));
+
+            insertNode.executeUpdate();
+        } else if(node instanceof KiWiStringLiteral) {
+            KiWiStringLiteral stringLiteral = (KiWiStringLiteral)node;
+
+            PreparedStatement insertNode = getPreparedStatement("store.sliteral");
+            insertNode.setLong(1,node.getId());
+            insertNode.setString(2, stringLiteral.getContent());
+            if(stringLiteral.getLocale() != null) {
+                insertNode.setString(3,stringLiteral.getLocale().getLanguage());
+            } else {
+                insertNode.setObject(3, null);
+            }
+            if(stringLiteral.getType() != null) {
+                insertNode.setLong(4,stringLiteral.getType().getId());
+            } else {
+                insertNode.setObject(4, null);
+            }
+            insertNode.setTimestamp(5, new Timestamp(stringLiteral.getCreated().getTime()));
+
+            insertNode.executeUpdate();
+        } else {
+            log.warn("unrecognized node type: {}", node.getClass().getCanonicalName());
+        }
+
+        cacheNode(node);
+    }
+
+    /**
+     * Store a triple in the database. This method assumes that all nodes used by the triple are already persisted.
+     *
+     * @param triple     the triple to store
+     * @throws SQLException
+     * @throws NullPointerException in case the subject, predicate, object or context have not been persisted
+     */
+    public synchronized void storeTriple(KiWiTriple triple) throws SQLException {
+        // if the node already has an ID, storeNode should not be called, since it is already persisted
+        if(triple.getId() != null) {
+            log.warn("triple {} already had a triple ID, not persisting", triple);
+            return;
+        }
+
+        Preconditions.checkNotNull(triple.getSubject().getId());
+        Preconditions.checkNotNull(triple.getPredicate().getId());
+        Preconditions.checkNotNull(triple.getObject().getId());
+        Preconditions.checkNotNull(triple.getContext().getId());
+
+
+        // retrieve a new triple ID and set it in the object
+        triple.setId(getNextSequence("seq.triples"));
+
+        PreparedStatement insertTriple = getPreparedStatement("store.triple");
+        insertTriple.setLong(1,triple.getId());
+        insertTriple.setLong(2,triple.getSubject().getId());
+        insertTriple.setLong(3,triple.getPredicate().getId());
+        insertTriple.setLong(4,triple.getObject().getId());
+        insertTriple.setLong(5,triple.getContext().getId());
+        insertTriple.setBoolean(6,triple.isInferred());
+        insertTriple.setTimestamp(7, new Timestamp(triple.getCreated().getTime()));
+        insertTriple.executeUpdate();
+
+        cacheTriple(triple);
+    }
+
+
+    /**
+     * Mark the triple passed as argument as deleted, setting the "deleted" flag to true and
+     * updating the timestamp value of "deletedAt".
+     * <p/>
+     * The triple remains in the database, because other entities might still reference it (e.g. a version).
+     * Use the method cleanupTriples() to fully remove all deleted triples without references.
+     *
+     * @param triple
+     */
+    public void deleteTriple(KiWiTriple triple) throws SQLException {
+        if(triple.getId() == null) {
+            log.warn("attempting to remove non-persistent triple: {}",triple);
+            return;
+        }
+
+        PreparedStatement deleteTriple = getPreparedStatement("delete.triple");
+        deleteTriple.setLong(1,triple.getId());
+        deleteTriple.executeUpdate();
+
+        removeCachedTriple(triple);
+
+        // make sure the triple is marked as deleted in case some service still holds a reference
+        triple.setDeleted(true);
+        triple.setDeletedAt(new Date());
+    }
+
+    /**
+     * Remove from the database all triples that have been marked as deleted and are not referenced by any other
+     * entity.
+     */
+    public void cleanupTriples() {
+        throw new UnsupportedOperationException("garbage collection of triples is not yet supported!");
+    }
+
+
+    /**
+     * List all contexts used in this triple store. See query.contexts .
+     * @return
+     * @throws SQLException
+     */
+    public CloseableIteration<KiWiResource, SQLException> listContexts() throws SQLException {
+        PreparedStatement queryContexts = getPreparedStatement("query.contexts");
+
+        final ResultSet result = queryContexts.executeQuery();
+
+        return new ResultSetIteration<KiWiResource>(result, new ResultTransformerFunction<KiWiResource>() {
+            @Override
+            public KiWiResource apply(ResultSet row) throws SQLException {
+                return (KiWiResource)loadNodeById(result.getLong("context"));
+            }
+        });
+
+    }
+
+
+    public CloseableIteration<KiWiNamespace, SQLException> listNamespaces() throws SQLException {
+        PreparedStatement queryContexts = getPreparedStatement("query.namespaces");
+
+        final ResultSet result = queryContexts.executeQuery();
+
+        return new ResultSetIteration<KiWiNamespace>(result, new ResultTransformerFunction<KiWiNamespace>() {
+            @Override
+            public KiWiNamespace apply(ResultSet input) throws SQLException {
+                return constructNamespaceFromDatabase(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> listTriples(KiWiResource subject, KiWiUriResource predicate, KiWiNode object, KiWiResource context, boolean inferred) throws SQLException {
+
+        return new RepositoryResult<Statement>(
+                new ExceptionConvertingIteration<Statement, RepositoryException>(listTriplesInternal(subject,predicate,object,context,inferred)) {
+                    @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> listTriplesInternal(KiWiResource subject, KiWiUriResource predicate, KiWiNode object, KiWiResource context, boolean inferred) 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(
+                constructTripleQuery(subject,predicate,object,context,inferred),
+                ResultSet.TYPE_FORWARD_ONLY,
+                ResultSet.CONCUR_READ_ONLY
+        );
+        query.clearParameters();
+
+        // set query parameters
+        int position = 1;
+        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 constructTripleQuery(KiWiResource subject, KiWiUriResource predicate, KiWiNode object, KiWiResource context, boolean inferred) {
+        StringBuilder builder = new StringBuilder();
+        builder.append("SELECT id,subject,predicate,object,context,deleted,inferred,creator,createdAt,deletedAt FROM triples WHERE deleted = false");
+        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();
+
+    }
+
+    protected KiWiNamespace constructNamespaceFromDatabase(ResultSet row) throws SQLException {
+        KiWiNamespace result = new KiWiNamespace(row.getString("prefix"),row.getString("uri"));
+        result.setId(row.getLong("id"));
+        result.setCreated(new Date(row.getTimestamp("createdAt").getTime()));
+
+        namespacePrefixCache.put(new Element(result.getPrefix(),result));
+        namespaceUriCache.put(new Element(result.getUri(),result));
+
+        return result;
+    }
+
+    /**
+     * Construct an appropriate KiWiNode from the result of an SQL query. 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
+     */
+    protected KiWiNode constructNodeFromDatabase(ResultSet row) throws SQLException {
+
+        Long id = row.getLong("id");
+
+        Element cached = nodeCache.get(id);
+
+        // lookup element in cache first, so we can avoid reconstructing it if it is already there
+        if(cached != null) {
+            return (KiWiNode)cached.getObjectValue();
+        }
+
+        String ntype = row.getString("ntype");
+        if("uri".equals(ntype)) {
+            KiWiUriResource result = new KiWiUriResource(row.getString("svalue"));
+            result.setId(id);
+            result.setCreated(new Date(row.getTimestamp("createdAt").getTime()));
+
+            cacheNode(result);
+            return result;
+        } else if("bnode".equals(ntype)) {
+            KiWiAnonResource result = new KiWiAnonResource(row.getString("svalue"));
+            result.setId(row.getLong("id"));
+            result.setCreated(new Date(row.getTimestamp("createdAt").getTime()));
+
+            cacheNode(result);
+            return result;
+        } else if("string".equals(ntype)) {
+            final KiWiStringLiteral result = new KiWiStringLiteral(row.getString("svalue"));
+            result.setId(row.getLong("id"));
+            result.setCreated(new Date(row.getTimestamp("createdAt").getTime()));
+
+            if(row.getString("lang") != null) {
+                result.setLocale(getLocale(row.getString("lang")));
+            }
+            if(row.getLong("ltype") != 0) {
+                result.setType((KiWiUriResource) loadNodeById(row.getLong("ltype")));
+            } else {
+                log.warn("Loaded literal without type: '{}' (id:{}).", result.getContent(), result.getId());
+            }
+
+            cacheNode(result);
+            return result;
+        } else if("int".equals(ntype)) {
+            KiWiIntLiteral result = new KiWiIntLiteral();
+            result.setId(row.getLong("id"));
+            result.setCreated(new Date(row.getTimestamp("createdAt").getTime()));
+            result.setIntContent(row.getLong("ivalue"));
+            if(row.getLong("ltype") != 0) {
+                result.setType((KiWiUriResource) loadNodeById(row.getLong("ltype")));
+            }
+
+            cacheNode(result);
+            return result;
+        } else if("double".equals(ntype)) {
+            KiWiDoubleLiteral result = new KiWiDoubleLiteral();
+            result.setId(row.getLong("id"));
+            result.setCreated(new Date(row.getTimestamp("createdAt").getTime()));
+            result.setDoubleContent(row.getDouble("dvalue"));
+            if(row.getLong("ltype") != 0) {
+                result.setType((KiWiUriResource) loadNodeById(row.getLong("ltype")));
+            }
+
+            cacheNode(result);
+            return result;
+        } else if("boolean".equals(ntype)) {
+            KiWiBooleanLiteral result = new KiWiBooleanLiteral();
+            result.setId(row.getLong("id"));
+            result.setCreated(new Date(row.getTimestamp("createdAt").getTime()));
+            result.setValue(row.getBoolean("bvalue"));
+
+            if(row.getLong("ltype") != 0) {
+                result.setType((KiWiUriResource) loadNodeById(row.getLong("ltype")));
+            }
+
+            cacheNode(result);
+            return result;
+        } else if("date".equals(ntype)) {
+            KiWiDateLiteral result = new KiWiDateLiteral();
+            result.setId(row.getLong("id"));
+            result.setCreated(new Date(row.getTimestamp("createdAt").getTime()));
+            result.setDateContent(new Date(row.getTimestamp("tvalue").getTime()));
+
+            if(row.getLong("ltype") != 0) {
+                result.setType((KiWiUriResource) loadNodeById(row.getLong("ltype")));
+            }
+
+            cacheNode(result);
+            return result;
+        } else {
+            throw new IllegalArgumentException("unknown node type in database result: "+ntype);
+        }
+    }
+
+    /**
+     * Construct a KiWiTriple from the result of an SQL query. The query result is expected to contain the
+     * following columns:
+     * <ul>
+     *     <li>id: the database id of the triple (long value)</li>
+     *     <li>subject: the database id of the subject (long value); the node will be loaded using the loadNodeById method</li>
+     *     <li>predicate: the database id of the predicate (long value); the node will be loaded using the loadNodeById method</li>
+     *     <li>object: the database id of the object (long value); the node will be loaded using the loadNodeById method</li>
+     *     <li>context: the database id of the context (long value); the node will be loaded using the loadNodeById method</li>
+     *     <li>creator: the database id of the creator (long value); the node will be loaded using the loadNodeById method; may be null</li>
+     *     <li>deleted: a flag (boolean) indicating whether this triple has been deleted</li>
+     *     <li>inferred: a flag (boolean) indicating whether this triple has been inferred by the KiWi reasoner</li>
+     *     <li>createdAt: a timestamp representing the creation date of the triple</li>
+     *     <li>createdAt: a timestamp representing the deletion date of the triple (null in case triple is not deleted)</li>
+     * </ul>
+     * The method will not change the ResultSet iterator, only read its values, so it needs to be executed for each row separately.
+     *
+     * @param row a database result containing the columns described above
+     * @return a KiWiTriple representation of the database result
+     */
+    protected KiWiTriple constructTripleFromDatabase(ResultSet row) throws SQLException {
+        Long id = row.getLong("id");
+
+        Element cached = tripleCache.get(id);
+
+        // lookup element in cache first, so we can avoid reconstructing it if it is already there
+        if(cached != null) {
+            return (KiWiTriple)cached.getObjectValue();
+        }
+
+        KiWiTriple result = new KiWiTriple();
+        result.setId(id);
+        result.setSubject((KiWiResource)loadNodeById(row.getLong("subject")));
+        result.setPredicate((KiWiUriResource) loadNodeById(row.getLong("predicate")));
+        result.setObject(loadNodeById(row.getLong("object")));
+        result.setContext((KiWiResource) loadNodeById(row.getLong("context")));
+        if(row.getLong("creator") != 0) {
+            result.setCreator((KiWiResource)loadNodeById(row.getLong("creator")));
+        }
+        result.setDeleted(row.getBoolean("deleted"));
+        result.setInferred(row.getBoolean("deleted"));
+        result.setCreated(new Date(row.getTimestamp("createdAt").getTime()));
+        try {
+            if(row.getDate("deletedAt") != null) {
+                result.setDeletedAt(new Date(row.getTimestamp("deletedAt").getTime()));
+            }
+        } catch (SQLException ex) {
+            // work around a MySQL problem with null dates
+            // (see http://stackoverflow.com/questions/782823/handling-datetime-values-0000-00-00-000000-in-jdbc)
+        }
+
+        tripleCache.put(new Element(id,result));
+
+        return result;
+    }
+
+
+    protected static Locale getLocale(String language) {
+        Locale locale = localeMap.get(language);
+        if(locale == null) {
+            locale = LocaleUtils.toLocale(language);
+            localeMap.put(language,locale);
+
+        }
+        return locale;
+    }
+
+    /**
+     * Return the prepared statement with the given identifier; first looks in the statement cache and if it does
+     * not exist there create a new statement.
+     *
+     * @param key the id of the statement in statements.properties
+     * @return
+     * @throws SQLException
+     */
+    public PreparedStatement getPreparedStatement(String key) throws SQLException {
+        PreparedStatement statement = statementCache.get(key);
+        if(statement == null) {
+            statement = connection.prepareStatement(dialect.getStatement(key));
+            statementCache.put(key,statement);
+        }
+        statement.clearParameters();
+        return statement;
+    }
+
+    /**
+     * Get next number in a sequence; for databases without sequence support (e.g. MySQL), this method will first update a
+     * sequence table and then return the value.
+     *
+     * @param sequenceName the identifier in statements.properties for querying the sequence
+     * @return a new sequence ID
+     * @throws SQLException
+     */
+    public long getNextSequence(String sequenceName) throws SQLException {
+        // retrieve a new node id and set it in the node object
+
+        // if there is a preparation needed to update the transaction, run it first
+        if(dialect.hasStatement(sequenceName+".prep")) {
+            PreparedStatement prepNodeId = getPreparedStatement(sequenceName+".prep");
+            prepNodeId.executeUpdate();
+        }
+
+        PreparedStatement queryNodeId = getPreparedStatement(sequenceName);
+        ResultSet resultNodeId = queryNodeId.executeQuery();
+        try {
+            if(resultNodeId.next()) {
+                return resultNodeId.getLong(1);
+            } else {
+                throw new SQLException("the sequence did not return a new value");
+            }
+        } finally {
+            resultNodeId.close();
+        }
+
+    }
+
+
+    private void cacheNode(KiWiNode node) {
+        if(node.getId() != null) {
+            nodeCache.put(new Element(node.getId(),node));
+        }
+        if(node instanceof KiWiUriResource) {
+            uriCache.put(new Element(node.stringValue(), node));
+        } else if(node instanceof KiWiAnonResource) {
+            bnodeCache.put(new Element(node.stringValue(),node));
+        } else if(node instanceof KiWiLiteral) {
+            literalCache.put(new Element(LiteralCommons.createCacheKey((Literal) node),node));
+        }
+    }
+
+    private void cacheTriple(KiWiTriple triple) {
+        if(triple.getId() != null) {
+            tripleCache.put(new Element(triple.getId(),triple));
+        }
+    }
+
+    private void removeCachedTriple(KiWiTriple triple) {
+        if(triple.getId() != null) {
+            tripleCache.remove(triple.getId());
+        }
+    }
+
+    /**
+     * Return a collection of database tables contained in the database. This query is used for checking whether
+     * the database needs to be created when initialising the system.
+     *
+     *
+     *
+     * @return
+     * @throws SQLException
+     */
+    public Set<String> getDatabaseTables() throws SQLException {
+        PreparedStatement statement = getPreparedStatement("meta.tables");
+        ResultSet result = statement.executeQuery();
+        try {
+            Set<String> tables = new HashSet<String>();
+            while(result.next()) {
+                tables.add(result.getString(1).toLowerCase());
+            }
+            return tables;
+        } finally {
+            result.close();
+        }
+    }
+
+
+    /**
+     * Return the KiWi version of the database this connection is operating on. This query is necessary for
+     * checking proper state of a database when initialising the system.
+     *
+     * @return
+     */
+    public int getDatabaseVersion() throws SQLException {
+        PreparedStatement statement = getPreparedStatement("meta.version");
+        ResultSet result = statement.executeQuery();
+        try {
+            if(result.next()) {
+                return Integer.parseInt(result.getString(1));
+            } else {
+                throw new SQLException("no version information available");
+            }
+        } finally {
+            result.close();
+        }
+    }
+
+
+    /**
+     * Sets this connection's auto-commit mode to the given state.
+     * If a connection is in auto-commit mode, then all its SQL
+     * statements will be executed and committed as individual
+     * transactions.  Otherwise, its SQL statements are grouped into
+     * transactions that are terminated by a call to either
+     * the method <code>commit</code> or the method <code>rollback</code>.
+     * By default, new connections are in auto-commit
+     * mode.
+     * <P>
+     * The commit occurs when the statement completes. The time when the statement
+     * completes depends on the type of SQL Statement:
+     * <ul>
+     * <li>For DML statements, such as Insert, Update or Delete, and DDL statements,
+     * the statement is complete as soon as it has finished executing.
+     * <li>For Select statements, the statement is complete when the associated result
+     * set is closed.
+     * <li>For <code>CallableStatement</code> objects or for statements that return
+     * multiple results, the statement is complete
+     * when all of the associated result sets have been closed, and all update
+     * counts and output parameters have been retrieved.
+     *</ul>
+     * <P>
+     * <B>NOTE:</B>  If this method is called during a transaction and the
+     * auto-commit mode is changed, the transaction is committed.  If
+     * <code>setAutoCommit</code> is called and the auto-commit mode is
+     * not changed, the call is a no-op.
+     *
+     * @param autoCommit <code>true</code> to enable auto-commit mode;
+     *         <code>false</code> to disable it
+     * @exception java.sql.SQLException if a database access error occurs,
+     *  setAutoCommit(true) is called while participating in a distributed transaction,
+     * or this method is called on a closed connection
+     * @see #getAutoCommit
+     */
+    public void setAutoCommit(boolean autoCommit) throws SQLException {
+        connection.setAutoCommit(autoCommit);
+    }
+
+
+    /**
+     * Retrieves the current auto-commit mode for this <code>Connection</code>
+     * object.
+     *
+     * @return the current state of this <code>Connection</code> object's
+     *         auto-commit mode
+     * @exception java.sql.SQLException if a database access error occurs
+     * or this method is called on a closed connection
+     * @see #setAutoCommit
+     */
+    public boolean getAutoCommit() throws SQLException {
+        return connection.getAutoCommit();
+    }
+
+    /**
+     * Makes all changes made since the previous
+     * commit/rollback permanent and releases any database locks
+     * currently held by this <code>Connection</code> object.
+     * This method should be
+     * used only when auto-commit mode has been disabled.
+     *
+     * @exception java.sql.SQLException if a database access error occurs,
+     * this method is called while participating in a distributed transaction,
+     * if this method is called on a closed conection or this
+     *            <code>Connection</code> object is in auto-commit mode
+     * @see #setAutoCommit
+     */
+    public void commit() throws SQLException {
+        connection.commit();
+    }
+
+    /**
+     * Undoes all changes made in the current transaction
+     * and releases any database locks currently held
+     * by this <code>Connection</code> object. This method should be
+     * used only when auto-commit mode has been disabled.
+     *
+     * @exception java.sql.SQLException if a database access error occurs,
+     * this method is called while participating in a distributed transaction,
+     * this method is called on a closed connection or this
+     *            <code>Connection</code> object is in auto-commit mode
+     * @see #setAutoCommit
+     */
+    public void rollback() throws SQLException {
+        if(!connection.isClosed()) {
+            connection.rollback();
+        }
+    }
+
+    /**
+     * Retrieves whether this <code>Connection</code> object has been
+     * closed.  A connection is closed if the method <code>close</code>
+     * has been called on it or if certain fatal errors have occurred.
+     * This method is guaranteed to return <code>true</code> only when
+     * it is called after the method <code>Connection.close</code> has
+     * been called.
+     * <P>
+     * This method generally cannot be called to determine whether a
+     * connection to a database is valid or invalid.  A typical client
+     * can determine that a connection is invalid by catching any
+     * exceptions that might be thrown when an operation is attempted.
+     *
+     * @return <code>true</code> if this <code>Connection</code> object
+     *         is closed; <code>false</code> if it is still open
+     * @exception java.sql.SQLException if a database access error occurs
+     */
+    public boolean isClosed() throws SQLException {
+        return connection.isClosed();
+    }
+
+
+    /**
+     * Releases this <code>Connection</code> object's database and JDBC resources
+     * immediately instead of waiting for them to be automatically released.
+     * <P>
+     * Calling the method <code>close</code> on a <code>Connection</code>
+     * object that is already closed is a no-op.
+     * <P>
+     * It is <b>strongly recommended</b> that an application explicitly
+     * commits or rolls back an active transaction prior to calling the
+     * <code>close</code> method.  If the <code>close</code> method is called
+     * and there is an active transaction, the results are implementation-defined.
+     * <P>
+     *
+     * @exception java.sql.SQLException SQLException if a database access error occurs
+     */
+    public void close() throws SQLException {
+        // close all prepared statements
+        try {
+            for(Map.Entry<String,PreparedStatement> entry : statementCache.entrySet()) {
+                try {
+                    entry.getValue().close();
+                } catch (SQLException ex) {}
+            }
+        } catch(AbstractMethodError ex) {
+            log.debug("database system does not allow closing statements");
+        }
+
+        connection.close();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-marmotta/blob/c32963d5/kiwi/kiwi-triplestore/src/main/java/org/apache/marmotta/kiwi/persistence/KiWiDialect.java
----------------------------------------------------------------------
diff --git a/kiwi/kiwi-triplestore/src/main/java/org/apache/marmotta/kiwi/persistence/KiWiDialect.java b/kiwi/kiwi-triplestore/src/main/java/org/apache/marmotta/kiwi/persistence/KiWiDialect.java
new file mode 100644
index 0000000..ef8569d
--- /dev/null
+++ b/kiwi/kiwi-triplestore/src/main/java/org/apache/marmotta/kiwi/persistence/KiWiDialect.java
@@ -0,0 +1,153 @@
+/**
+ * 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.persistence;
+
+import org.apache.commons.io.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.Properties;
+import java.util.Set;
+
+/**
+ * A dialect provides the SQL statements necessary to access the different types of database systems. Each
+ * method should return a PreparedStatement that can be executed on the respective JDBC connection
+ * <p/>
+ * Author: Sebastian Schaffert
+ */
+public abstract class KiWiDialect {
+
+    private static Logger log = LoggerFactory.getLogger(KiWiDialect.class);
+
+    private final static int VERSION = 1;
+
+    private Properties statements;
+
+
+    protected KiWiDialect() {
+        statements = new Properties();
+
+        // load all statements.properties files that can be located in the same package (from different modules in different jar files)
+        try {
+            Enumeration<URL> urls = this.getClass().getClassLoader().getResources(this.getClass().getPackage().getName().replace('.','/')+"/statements.properties");
+            while(urls.hasMoreElements()) {
+                statements.load(urls.nextElement().openStream());
+            }
+        } catch (Exception e) {
+            log.error("could not load statement definitions (statement.properties)",e);
+        }
+
+    }
+
+    public int getVersion() {
+        return VERSION;
+    }
+
+
+    /**
+     * Return the name of the driver class (used for properly initialising JDBC connections)
+     * @return
+     */
+    public abstract String getDriverClass();
+
+
+    /**
+     * Return the contents of the SQL create script used for initialising an empty database
+     * @return
+     */
+    public String getCreateScript(String scriptName) {
+        return getScript("create_"+scriptName+"_tables.sql");
+    }
+
+
+    /**
+     * Return the contents of the SQL drop script used for cleaning up all traces of the KiWi triple store
+     * @return
+     */
+    public String getDropScript(String scriptName) {
+        return getScript("drop_"+scriptName+"_tables.sql");
+    }
+
+
+    /**
+     * Return the contents of the SQL script with the given file name (relative to the current class)
+     * @return
+     */
+    protected String getScript(String scriptName) {
+        try {
+            return IOUtils.toString(this.getClass().getResourceAsStream(scriptName));
+        } catch (Exception e) {
+            return "";
+        }
+    }
+
+    /**
+     * Return the contents of the SQL scripts used for migrating from the version given as argument to the
+     * current version. In case the update covers several version steps, this method should concatenate the
+     * migration scripts in proper order.
+     *
+     * @param oldVersion the version to migrate the database from
+     * @param name       name of the script to create; the method will look for scripts with name upgrade_name_oldv_newv.sql
+     * @return the migration script from the old version to the current version of the database schema, or null
+     *         if the database is already the current version
+     */
+    public String getMigrationScript(int oldVersion, String name) {
+        StringBuilder builder = new StringBuilder();
+        for(int i = oldVersion+1; i <= VERSION; i++ ) {
+            try {
+                String script = String.format("upgrade_"+name+"_%02d_%02d.sql",i-1,i);
+
+                builder.append(IOUtils.toString(this.getClass().getResourceAsStream(script)));
+            } catch (Exception e) {
+                log.warn("upgrade script {} -> {} not found or not readable!", i-1, i);
+            }
+        }
+        return builder.toString();
+    }
+
+
+    /**
+     * Return the SQL statement with the given identifier, or null if such a statement does not exist. The
+     * statement identifiers are usually the keys in the statements.properties file for the respective database.
+     *
+     * @param identifier property key of the statement in statements.properties
+     * @return the SQL statement, or null if it does not exist
+     */
+    public String getStatement(String identifier) {
+        return statements.getProperty(identifier);
+    }
+
+    /**
+     * Return true if a statement with the given identifier exists
+     * @param identifier key of the statement to check
+     * @return true if a statement with the given identifier exists
+     */
+    public boolean hasStatement(String identifier) {
+        return statements.getProperty(identifier) != null;
+    }
+
+    /**
+     * Return all available statement identifiers. The statement identifiers are usually the keys in the
+     * statements.properties file for the respective database.
+     *
+     * @return a set of all statement identifiers
+     */
+    public Set<String> getStatementIdentifiers() {
+        return statements.stringPropertyNames();
+    }
+}