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();
+ }
+}