AbstractRepositoryConnection.java
/*******************************************************************************
* Copyright (c) 2015 Eclipse RDF4J contributors, Aduna, and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Distribution License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*******************************************************************************/
package org.eclipse.rdf4j.repository.base;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.net.URL;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.rdf4j.common.iteration.CloseableIteration;
import org.eclipse.rdf4j.common.transaction.IsolationLevel;
import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.Namespace;
import org.eclipse.rdf4j.model.NamespaceAware;
import org.eclipse.rdf4j.model.Resource;
import org.eclipse.rdf4j.model.Statement;
import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.model.ValueFactory;
import org.eclipse.rdf4j.query.BooleanQuery;
import org.eclipse.rdf4j.query.GraphQuery;
import org.eclipse.rdf4j.query.MalformedQueryException;
import org.eclipse.rdf4j.query.Query;
import org.eclipse.rdf4j.query.QueryLanguage;
import org.eclipse.rdf4j.query.TupleQuery;
import org.eclipse.rdf4j.query.Update;
import org.eclipse.rdf4j.repository.Repository;
import org.eclipse.rdf4j.repository.RepositoryConnection;
import org.eclipse.rdf4j.repository.RepositoryException;
import org.eclipse.rdf4j.repository.RepositoryResult;
import org.eclipse.rdf4j.repository.UnknownTransactionStateException;
import org.eclipse.rdf4j.repository.util.RDFInserter;
import org.eclipse.rdf4j.repository.util.RDFLoader;
import org.eclipse.rdf4j.rio.ParserConfig;
import org.eclipse.rdf4j.rio.RDFFormat;
import org.eclipse.rdf4j.rio.RDFHandler;
import org.eclipse.rdf4j.rio.RDFHandlerException;
import org.eclipse.rdf4j.rio.RDFParseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Abstract class implementing most 'convenience' methods in the {@link RepositoryConnection} interface by transforming
* parameters and mapping the methods to the basic (abstractly declared) methods.
* <p>
* Open connections are automatically closed when being garbage collected. A warning message will be logged when the
* system property <var>org.eclipse.rdf4j.repository.debug</var> has been set to a non-<var>null</var> value.
*
* @author Jeen Broekstra
* @author Arjohn Kampman
*/
public abstract class AbstractRepositoryConnection implements RepositoryConnection {
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
private final Repository repository;
private volatile ParserConfig parserConfig = new ParserConfig();
private final AtomicBoolean isOpen = new AtomicBoolean(true);
private volatile IsolationLevel isolationLevel;
// private volatile boolean active;
protected AbstractRepositoryConnection(Repository repository) {
this.repository = repository;
}
@Override
public void setParserConfig(ParserConfig parserConfig) {
this.parserConfig = parserConfig;
}
@Override
public ParserConfig getParserConfig() {
return parserConfig;
}
@Override
public Repository getRepository() {
return repository;
}
@Override
public ValueFactory getValueFactory() {
return getRepository().getValueFactory();
}
@Override
public void begin(IsolationLevel level) throws RepositoryException {
setIsolationLevel(level);
begin();
}
@Override
public void setIsolationLevel(IsolationLevel level) throws IllegalStateException {
try {
if (isActive()) {
throw new IllegalStateException(
"Transaction isolation level can not be modified while transaction is active");
}
this.isolationLevel = level;
} catch (UnknownTransactionStateException e) {
throw new IllegalStateException(
"Transaction isolation level can not be modified while transaction state is unknown", e);
} catch (RepositoryException e) {
throw new IllegalStateException("Transaction isolation level can not be modified due to repository error",
e);
}
}
@Override
public IsolationLevel getIsolationLevel() {
return this.isolationLevel;
}
@Override
public boolean isOpen() throws RepositoryException {
return isOpen.get();
}
@Override
public void close() throws RepositoryException {
isOpen.set(false);
}
@Override
public Query prepareQuery(QueryLanguage ql, String query) throws MalformedQueryException, RepositoryException {
return prepareQuery(ql, query, null);
}
@Override
public TupleQuery prepareTupleQuery(QueryLanguage ql, String query)
throws MalformedQueryException, RepositoryException {
return prepareTupleQuery(ql, query, null);
}
@Override
public GraphQuery prepareGraphQuery(QueryLanguage ql, String query)
throws MalformedQueryException, RepositoryException {
return prepareGraphQuery(ql, query, null);
}
@Override
public BooleanQuery prepareBooleanQuery(QueryLanguage ql, String query)
throws MalformedQueryException, RepositoryException {
return prepareBooleanQuery(ql, query, null);
}
@Override
public Update prepareUpdate(QueryLanguage ql, String update) throws MalformedQueryException, RepositoryException {
return prepareUpdate(ql, update, null);
}
@Override
public boolean hasStatement(Resource subj, IRI pred, Value obj, boolean includeInferred, Resource... contexts)
throws RepositoryException {
try (RepositoryResult<Statement> stIter = getStatements(subj, pred, obj, includeInferred, contexts)) {
return stIter.hasNext();
}
}
@Override
public boolean hasStatement(Statement st, boolean includeInferred, Resource... contexts)
throws RepositoryException {
return hasStatement(st.getSubject(), st.getPredicate(), st.getObject(), includeInferred, contexts);
}
@Override
public boolean isEmpty() throws RepositoryException {
return size() == 0;
}
@Override
public void export(RDFHandler handler, Resource... contexts) throws RepositoryException, RDFHandlerException {
exportStatements(null, null, null, false, handler, contexts);
}
/**
* @deprecated Use {@link #begin()} instead.
*/
@Deprecated(since = "2.0")
@Override
public void setAutoCommit(boolean autoCommit) throws RepositoryException {
if (isActive()) {
if (autoCommit) {
// we are switching to autocommit mode from an active transaction.
commit();
}
} else if (!autoCommit) {
// begin a transaction
begin();
}
}
/**
* @deprecated Use {@link #isActive()} instead.
*/
@Deprecated(since = "2.0")
@Override
public boolean isAutoCommit() throws RepositoryException {
return !isActive();
}
@Override
public void add(File file, String baseURI, RDFFormat dataFormat, Resource... contexts)
throws IOException, RDFParseException, RepositoryException {
Objects.requireNonNull(contexts,
"contexts argument may not be null; either the value should be cast to Resource or an empty array should be supplied");
RDFInserter rdfInserter = new RDFInserter(this);
rdfInserter.enforceContext(contexts);
boolean localTransaction = startLocalTransaction();
try {
RDFLoader loader = new RDFLoader(getParserConfig(), getValueFactory());
loader.load(file, baseURI, dataFormat, rdfInserter);
conditionalCommit(localTransaction);
} catch (RDFHandlerException e) {
conditionalRollback(localTransaction);
// RDFInserter only throws wrapped RepositoryExceptions
throw (RepositoryException) e.getCause();
} catch (IOException | RuntimeException e) {
conditionalRollback(localTransaction);
throw e;
}
}
@Override
public void add(URL url, String baseURI, RDFFormat dataFormat, Resource... contexts)
throws IOException, RDFParseException, RepositoryException {
Objects.requireNonNull(contexts,
"contexts argument may not be null; either the value should be cast to Resource or an empty array should be supplied");
RDFInserter rdfInserter = new RDFInserter(this);
rdfInserter.enforceContext(contexts);
boolean localTransaction = startLocalTransaction();
try {
RDFLoader loader = new RDFLoader(getParserConfig(), getValueFactory());
loader.load(url, baseURI, dataFormat, rdfInserter);
conditionalCommit(localTransaction);
} catch (RDFHandlerException e) {
conditionalRollback(localTransaction);
// RDFInserter only throws wrapped RepositoryExceptions
throw (RepositoryException) e.getCause();
} catch (IOException | RuntimeException e) {
conditionalRollback(localTransaction);
throw e;
}
}
@Override
public void add(InputStream in, String baseURI, RDFFormat dataFormat, Resource... contexts)
throws IOException, RDFParseException, RepositoryException {
Objects.requireNonNull(contexts,
"contexts argument may not be null; either the value should be cast to Resource or an empty array should be supplied");
RDFInserter rdfInserter = new RDFInserter(this);
rdfInserter.enforceContext(contexts);
boolean localTransaction = startLocalTransaction();
try {
RDFLoader loader = new RDFLoader(getParserConfig(), getValueFactory());
loader.load(in, baseURI, dataFormat, rdfInserter);
conditionalCommit(localTransaction);
} catch (RDFHandlerException e) {
conditionalRollback(localTransaction);
// RDFInserter only throws wrapped RepositoryExceptions
throw (RepositoryException) e.getCause();
} catch (IOException | RuntimeException e) {
conditionalRollback(localTransaction);
throw e;
}
}
/**
* Starts a new transaction if one is not already active.
*
* @return <code>true</code> if a new transaction was started, <code>false</code> if a transaction was already
* active.
* @throws RepositoryException
*/
protected final boolean startLocalTransaction() throws RepositoryException {
if (!isActive()) {
begin();
return true;
}
return false;
}
/**
* Invokes {@link #commit()} if supplied boolean condition is <code>true</code>.
*
* @param condition a boolean condition.
* @throws RepositoryException
*/
protected final void conditionalCommit(boolean condition) throws RepositoryException {
if (condition) {
commit();
}
}
/**
* Invokes {@link #rollback()} if supplied boolean condition is <code>true</code>.
*
* @param condition a boolean condition.
* @throws RepositoryException
*/
protected final void conditionalRollback(boolean condition) throws RepositoryException {
if (condition) {
rollback();
}
}
@Override
public void add(Reader reader, String baseURI, RDFFormat dataFormat, Resource... contexts)
throws IOException, RDFParseException, RepositoryException {
Objects.requireNonNull(contexts,
"contexts argument may not be null; either the value should be cast to Resource or an empty array should be supplied");
RDFInserter rdfInserter = new RDFInserter(this);
rdfInserter.enforceContext(contexts);
boolean localTransaction = startLocalTransaction();
try {
RDFLoader loader = new RDFLoader(getParserConfig(), getValueFactory());
loader.load(reader, baseURI, dataFormat, rdfInserter);
conditionalCommit(localTransaction);
} catch (RDFHandlerException e) {
conditionalRollback(localTransaction);
// RDFInserter only throws wrapped RepositoryExceptions
throw (RepositoryException) e.getCause();
} catch (IOException | RuntimeException e) {
conditionalRollback(localTransaction);
throw e;
}
}
@Override
public void add(Iterable<? extends Statement> statements, Resource... contexts) throws RepositoryException {
Objects.requireNonNull(contexts,
"contexts argument may not be null; either the value should be cast to Resource or an empty array should be supplied");
boolean localTransaction = startLocalTransaction();
try {
for (Statement st : statements) {
addWithoutCommit(st, contexts);
}
if (statements instanceof NamespaceAware) {
var newNamespaces = ((NamespaceAware) statements).getNamespaces();
for (Namespace newNamespace : newNamespaces) {
String nsPrefix = newNamespace.getPrefix();
if (getNamespace(nsPrefix) == null) {
setNamespace(nsPrefix, newNamespace.getName());
}
}
}
conditionalCommit(localTransaction);
} catch (RuntimeException e) {
conditionalRollback(localTransaction);
throw e;
}
}
@Override
public void add(CloseableIteration<? extends Statement> statements, Resource... contexts)
throws RepositoryException {
try (statements) {
Objects.requireNonNull(contexts,
"contexts argument may not be null; either the value should be cast to Resource or an empty array should be supplied");
boolean localTransaction = startLocalTransaction();
try {
while (statements.hasNext()) {
addWithoutCommit(statements.next(), contexts);
}
conditionalCommit(localTransaction);
} catch (RuntimeException e) {
conditionalRollback(localTransaction);
throw e;
}
}
}
@Override
public void add(Statement st, Resource... contexts) throws RepositoryException {
boolean localTransaction = startLocalTransaction();
Objects.requireNonNull(contexts,
"contexts argument may not be null; either the value should be cast to Resource or an empty array should be supplied");
addWithoutCommit(st, contexts);
conditionalCommit(localTransaction);
}
@Override
public void add(Resource subject, IRI predicate, Value object, Resource... contexts) throws RepositoryException {
boolean localTransaction = startLocalTransaction();
Objects.requireNonNull(contexts,
"contexts argument may not be null; either the value should be cast to Resource or an empty array should be supplied");
addWithoutCommit(subject, predicate, object, contexts);
conditionalCommit(localTransaction);
}
@Override
public void remove(Iterable<? extends Statement> statements, Resource... contexts) throws RepositoryException {
Objects.requireNonNull(contexts,
"contexts argument may not be null; either the value should be cast to Resource or an empty array should be supplied");
boolean localTransaction = startLocalTransaction();
try {
for (Statement st : statements) {
remove(st, contexts);
}
conditionalCommit(localTransaction);
} catch (RuntimeException e) {
conditionalRollback(localTransaction);
throw e;
}
}
@Override
public void remove(CloseableIteration<? extends Statement> statements, Resource... contexts)
throws RepositoryException {
try (statements) {
boolean localTransaction = startLocalTransaction();
try {
while (statements.hasNext()) {
remove(statements.next(), contexts);
}
conditionalCommit(localTransaction);
} catch (RuntimeException e) {
conditionalRollback(localTransaction);
throw e;
}
}
}
@Override
public void remove(Statement st, Resource... contexts) throws RepositoryException {
boolean localTransaction = startLocalTransaction();
Objects.requireNonNull(contexts,
"contexts argument may not be null; either the value should be cast to Resource or an empty array should be supplied");
removeWithoutCommit(st, contexts);
conditionalCommit(localTransaction);
}
@Override
public void remove(Resource subject, IRI predicate, Value object, Resource... contexts) throws RepositoryException {
boolean localTransaction = startLocalTransaction();
Objects.requireNonNull(contexts,
"contexts argument may not be null; either the value should be cast to Resource or an empty array should be supplied");
removeWithoutCommit(subject, predicate, object, contexts);
conditionalCommit(localTransaction);
}
@Override
public void clear(Resource... contexts) throws RepositoryException {
remove(null, null, null, contexts);
}
public abstract String getNamespace(String prefix) throws RepositoryException;
public abstract void setNamespace(String prefix, String name) throws RepositoryException;
protected void addWithoutCommit(Statement st, Resource... contexts) throws RepositoryException {
if (contexts.length == 0 && st.getContext() != null) {
contexts = new Resource[] { st.getContext() };
}
addWithoutCommit(st.getSubject(), st.getPredicate(), st.getObject(), contexts);
}
protected abstract void addWithoutCommit(Resource subject, IRI predicate, Value object, Resource... contexts)
throws RepositoryException;
protected void removeWithoutCommit(Statement st, Resource... contexts) throws RepositoryException {
if (contexts.length == 0 && st.getContext() != null) {
contexts = new Resource[] { st.getContext() };
}
removeWithoutCommit(st.getSubject(), st.getPredicate(), st.getObject(), contexts);
}
protected abstract void removeWithoutCommit(Resource subject, IRI predicate, Value object, Resource... contexts)
throws RepositoryException;
}