/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.stanbol.entityhub.yard.sesame;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.stanbol.entityhub.core.query.QueryResultListImpl;
import org.apache.stanbol.entityhub.core.query.QueryUtils;
import org.apache.stanbol.entityhub.core.yard.AbstractYard;
import org.apache.stanbol.entityhub.model.sesame.RdfRepresentation;
import org.apache.stanbol.entityhub.model.sesame.RdfValueFactory;
import org.apache.stanbol.entityhub.query.sparql.SparqlEndpointTypeEnum;
import org.apache.stanbol.entityhub.query.sparql.SparqlFieldQuery;
import org.apache.stanbol.entityhub.query.sparql.SparqlFieldQueryFactory;
import org.apache.stanbol.entityhub.query.sparql.SparqlQueryUtils;
import org.apache.stanbol.entityhub.servicesapi.model.Representation;
import org.apache.stanbol.entityhub.servicesapi.model.rdf.RdfResourceEnum;
import org.apache.stanbol.entityhub.servicesapi.query.FieldQuery;
import org.apache.stanbol.entityhub.servicesapi.query.QueryResultList;
import org.apache.stanbol.entityhub.servicesapi.query.UnsupportedQueryTypeException;
import org.apache.stanbol.entityhub.servicesapi.yard.Yard;
import org.apache.stanbol.entityhub.servicesapi.yard.YardException;
import org.openrdf.model.BNode;
import org.openrdf.model.Model;
import org.openrdf.model.Resource;
import org.openrdf.model.Statement;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.model.ValueFactory;
import org.openrdf.model.impl.TreeModel;
import org.openrdf.query.BindingSet;
import org.openrdf.query.Dataset;
import org.openrdf.query.MalformedQueryException;
import org.openrdf.query.QueryEvaluationException;
import org.openrdf.query.QueryLanguage;
import org.openrdf.query.TupleQuery;
import org.openrdf.query.TupleQueryResult;
import org.openrdf.query.impl.DatasetImpl;
import org.openrdf.repository.Repository;
import org.openrdf.repository.RepositoryConnection;
import org.openrdf.repository.RepositoryException;
import org.openrdf.repository.RepositoryResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Implementation of the Yard Interface based on a Sesame {@link Repository}.
* <p>
* This is NOT an OSGI component nor service. It is intended to be used by
* Components that do allow users to configure a Repository implementation.
* Such components will than create a SesameYard instance and register it as
* a OSGI service.
* <p>
* <b>NOTE</b> This Yard does not {@link Repository#initialize() initialize}
* nor {@link Repository#shutDown() shutdown} the Sesame repository. Callers
* are responsible for that. This is because this Yard implementation does
* NOT assume exclusive access to the repository. The same repository can be
* used by multiple Yards (e.g. configured for different
* {@link SesameYardConfig#setContexts(String[]) contexts}) or even other
* components.
*
* @author Rupert Westenthaler
*
*/
public class SesameYard extends AbstractYard implements Yard {
private static Logger log = LoggerFactory.getLogger(SesameYard.class);
/**
* Property used to mark empty Representations managed by this Graph. This is
* needed to workaround the fact, that the Entityhub supports the storage of
* empty Representations but this Yard uses the search for any outgoing
* relation (triple with the id of the representation as Subject) for the
* implementation of {@link #isRepresentation(String)}. Therefore for an
* empty Representation {@link #isRepresentation(String)} would return false
* even if the representation was {@link #store(Representation)} previously.
* <p>
* Adding the Triple<br>
* <code> ?representationId <{@value #MANAGED_REPRESENTATION}> true^^xsd:boolean </code>
* <br> for any empty Representation avoids this unwanted behaviour.
*/
private static final String MANAGED_REPRESENTATION_URI = "urn:org.apache.stanbol:entityhub.yard:rdf.sesame:managesRepresentation";
/**
* used as property for a triple to ensure existence for representations that
* do not define yet any triples
*/
private final URI managedRepresentation;
/**
* used as value for a triple to ensure existence for representations that
* do not define yet any triples
*/
private final Value managedRepresentationState;
/**
* If inferred Triples are included in operations on this Yard.
*/
public static final String INCLUDE_INFERRED = "org.apache.stanbol.entityhub.yard.sesame.includeInferred";
/**
* By default {@link #INCLUDE_INFERRED} is enabled.
*/
public static final boolean DEFAULT_INCLUDE_INFERRED = true;
/**
* Property used to enable/disable Sesame Context. If <code>false</code> the
* {@link #CONTEXT_URI} property gets ignored. If <code>true</code> and
* {@link #CONTEXT_URI} is missing the default context (<code>null</code>) is
* used. Otherwise the contexts as configured for {@link #CONTEXT_URI} are
* used.
*/
public static final String CONTEXT_ENABLED = "org.apache.stanbol.entityhub.yard.sesame.enableContext";
/**
* By default the {@link #CONTEXT_ENABLED} feature is disabled.
*/
public static final boolean DEFAULT_CONTEXT_ENABLED = false;
/**
* Property used to optionally configure one or more context URIs. empty
* values are interpreted as <code>null</code>
*/
public static final String CONTEXT_URI = "org.apache.stanbol.entityhub.yard.sesame.contextUri";
/**
* The context used by this yard. Parsed from {@link SesameYardConfig#getContexts()}
* if <code>{@link SesameYardConfig#isContextEnabled()} == true</code>
*/
private final URI[] contexts;
/**
* The {@link Dataset} similar to {@link #contexts}. Dataset is used for
* SPARQL queries to enforce results to be restricted to the {@link #contexts}
*/
private final Dataset dataset;
/**
* If inferred triples should be included or not. Configured via
* {@link SesameYardConfig#isIncludeInferred()}
*/
private boolean includeInferred;
/**
* The {@link Repository} as parsed in the constructor
*/
private final Repository repository;
/**
* The Entityhub ValueFactory used to create Sesame specific Representations,
* References and Text instances
*/
private final RdfValueFactory valueFactory;
/**
* The Sesame ValueFactory. Shortcut for {@link Repository#getValueFactory()}.
*/
private final ValueFactory sesameFactory;
/**
* The {@link URI} for {@link RdfResourceEnum#QueryResultSet}
*/
private final URI queryRoot;
/**
* The {@link URI} for {@link RdfResourceEnum#queryResult}
*/
private final URI queryResult;
/**
* Constructs a SesameYard for the parsed Repository and configuration.
* @param repo The Repository used by this Yard. The parsed Repository is
* expected to be initialised.
* @param config the configuration for the Yard.
*/
public SesameYard(Repository repo, SesameYardConfig config) {
super();
if(repo == null){
throw new IllegalArgumentException("The parsed repository MUST NOT be NULL!");
}
if(!repo.isInitialized()){
throw new IllegalArgumentException("The parsed repository MUST BE initialised!");
}
this.repository = repo;
if(config == null){
throw new IllegalArgumentException("The parsed configuration MUST NOT be NULL!");
}
this.sesameFactory = repo.getValueFactory();
this.valueFactory = new RdfValueFactory(null, sesameFactory);
this.managedRepresentation = sesameFactory.createURI(MANAGED_REPRESENTATION_URI);
this.managedRepresentationState = sesameFactory.createLiteral(true);
this.includeInferred = config.isIncludeInferred();
//init the super class
activate(this.valueFactory, SparqlFieldQueryFactory.getInstance(), config);
if(config.isContextEnabled()){
//Set the contexts
String[] contexts = config.getContexts();
this.contexts = new URI[contexts.length];
for(int i = 0; i < contexts.length; i++){
this.contexts[i] = contexts[i] == null ? null :
sesameFactory.createURI(contexts[i]);
}
} else {
this.contexts = new URI[]{};
}
//also init the dataset required for SPARQL queries
if(contexts.length > 0){
DatasetImpl dataset = new DatasetImpl();
for(URI context : this.contexts){
dataset.addNamedGraph(context);
dataset.addDefaultGraph(context);
}
this.dataset = dataset;
} else {
this.dataset = null;
}
queryRoot = sesameFactory.createURI(RdfResourceEnum.QueryResultSet.getUri());
queryResult = sesameFactory.createURI(RdfResourceEnum.queryResult.getUri());
}
/**
* Closes this Yard, but <b>does not</b> close the Sesame Repository!
*/
public void close(){
//init the super class
deactivate();
}
/**
* Getter for the context URI used by this yard.
* @return the URI used for the RDF graph that stores all the data of this
* yard.
*/
public final URI[] getContexts(){
return contexts;
}
@Override
public Representation getRepresentation(String id) throws YardException{
if(id == null){
throw new IllegalArgumentException("The parsed representation id MUST NOT be NULL!");
}
if(id.isEmpty()){
throw new IllegalArgumentException("The parsed representation id MUST NOT be EMTPY!");
}
RepositoryConnection con = null;
try {
con = repository.getConnection();
con.begin();
Representation rep = getRepresentation(con, sesameFactory.createURI(id), true);
con.commit();
return rep;
} catch (RepositoryException e) {
throw new YardException("Unable to get Representation "+id, e);
} finally {
if(con != null){
try {
con.close();
} catch (RepositoryException ignore) {}
}
}
}
/**
* Internally used to create Representations for URIs
* @param uri the uri
* @param check if <code>false</code> than there is no check if the URI
* refers to a Resource in the graph that is of type {@link #REPRESENTATION}
* @return the Representation
*/
protected final Representation getRepresentation(RepositoryConnection con, URI uri, boolean check) throws RepositoryException {
if(!check || isRepresentation(con,uri)){
return createRepresentationGraph(con, valueFactory, uri);
} else {
return null; //not found
}
}
/**
* Extracts the triples that belong to the {@link Representation} with the
* parsed id from the Sesame repository.
* @param con the repository connection
* @param valueFactory the {@link RdfValueFactory} to use
* @param uri the subject of the Representation to extract
* @return the representation with the extracted data.
* @throws RepositoryException
*/
protected RdfRepresentation createRepresentationGraph(RepositoryConnection con, RdfValueFactory valueFactory, URI uri) throws RepositoryException{
RdfRepresentation rep = valueFactory.createRdfRepresentation(uri);
Model model = rep.getModel();
extractRepresentation(con, model, uri, new HashSet<BNode>());
return rep;
}
/**
* Recursive Method internally doing all the work for
* {@link #createRepresentationGraph(UriRef, TripleCollection)}
* @param con the repository connection to read the data from
* @param model The model to add the statements retrieved
* @param node the current node. Changes in recursive calls as it follows
* @param visited holding all the visited BNodes to avoid cycles. Other nodes
* need not be added because this implementation would not follow it anyway
* outgoing relations if the object is a {@link BNode} instance.
* @throws RepositoryException
*/
private void extractRepresentation(RepositoryConnection con,Model model, Resource node, Set<BNode> visited) throws RepositoryException{
//we need all the outgoing relations and also want to follow bNodes until
//the next UriRef. However we are not interested in incoming relations!
RepositoryResult<Statement> outgoing = con.getStatements(node, null, null, includeInferred, contexts);
Statement statement;
Set<BNode> bnodes = new HashSet<BNode>();
while(outgoing.hasNext()){
statement = outgoing.next();
model.add(statement);
Value object = statement.getObject();
if(object instanceof BNode && !visited.contains(object)){
bnodes.add((BNode)object);
}
}
outgoing.close();
for(BNode bnode : bnodes){
visited.add(bnode);
//TODO: recursive calls could cause stackoverflows with wired graphs
extractRepresentation(con, model, bnode, visited);
}
}
@Override
public boolean isRepresentation(String id) throws YardException {
if(id == null) {
throw new IllegalArgumentException("The parsed id MUST NOT be NULL!");
}
if(id.isEmpty()){
throw new IllegalArgumentException("The parsed id MUST NOT be EMPTY!");
}
RepositoryConnection con = null;
try {
con = repository.getConnection();
con.begin();
boolean state = isRepresentation(con, sesameFactory.createURI(id));
con.commit();
return state;
} catch (RepositoryException e) {
throw new YardException("Unable to check for Representation "+id, e);
} finally {
if(con != null){
try {
con.close();
} catch (RepositoryException ignore) {}
}
}
}
/**
* Internally used to check if a URI resource represents an representation
* @param con the repository connection
* @param subject the subject URI of the representation to check
* @return the state
* @throws RepositoryException
*/
protected final boolean isRepresentation(RepositoryConnection con , URI subject) throws RepositoryException{
return con.hasStatement(subject, null, null, includeInferred, contexts);
}
@Override
public void remove(String id) throws YardException, IllegalArgumentException {
if(id == null) {
throw new IllegalArgumentException("The parsed Representation id MUST NOT be NULL!");
}
RepositoryConnection con = null;
try {
con = repository.getConnection();
con.begin();
remove(con, sesameFactory.createURI(id));
con.commit();
} catch (RepositoryException e) {
throw new YardException("Unable to remove for Representation "+id, e);
} finally {
if(con != null){
try {
con.close();
} catch (RepositoryException ignore) {}
}
}
}
/**
* Internally used to remove a Representation from the Repository. <p>
* NOTE: this does not remove any {@link Statement}s for {@link BNode}s
* beeing {@link Statement#getObject() object}s of the parsed subjects.
* @param con the connection
* @param subject the subject of the Representation to remove
* @throws RepositoryException
*/
protected void remove(RepositoryConnection con, URI subject) throws RepositoryException{
con.remove(subject, null, null, contexts);
}
@Override
public final void remove(Iterable<String> ids) throws IllegalArgumentException, YardException {
if(ids == null){
throw new IllegalArgumentException("The parsed Iterable over the IDs to remove MUST NOT be NULL!");
}
RepositoryConnection con = null;
try {
con = repository.getConnection();
con.begin();
for(String id : ids){
if(id != null){
remove(con, sesameFactory.createURI(id));
}
}
con.commit();
} catch (RepositoryException e) {
throw new YardException("Unable to remove parsed Representations", e);
} finally {
if(con != null){
try {
con.close();
} catch (RepositoryException ignore) {}
}
}
}
@Override
public final void removeAll() throws YardException {
RepositoryConnection con = null;
try {
con = repository.getConnection();
con.begin();
con.clear(contexts); //removes everything
con.commit();
} catch (RepositoryException e) {
throw new YardException("Unable to remove parsed Representations", e);
} finally {
if(con != null){
try {
con.close();
} catch (RepositoryException ignore) {}
}
}
}
@Override
public final Representation store(Representation representation) throws IllegalArgumentException, YardException {
if(representation == null){
throw new IllegalArgumentException("The parsed Representation MUST NOT be NULL!");
}
return store(representation,true,true);
}
@Override
public final Iterable<Representation> store(Iterable<Representation> representations) throws IllegalArgumentException, YardException {
if(representations == null){
throw new IllegalArgumentException("The parsed Iterable over the Representations to store MUST NOT be NULL!");
}
return store(representations, true);
}
@Override
public final Representation update(Representation representation) throws IllegalArgumentException, YardException {
if(representation == null){
throw new IllegalArgumentException("The parsed Representation MUST NOT be NULL!");
}
return store(representation,false,true);
}
@Override
public final Iterable<Representation> update(Iterable<Representation> representations) throws YardException, IllegalArgumentException {
if(representations == null){
throw new IllegalArgumentException("The parsed Iterable over the Representations to update MUST NOT be NULL!");
}
return store(representations,false);
}
protected final Iterable<Representation> store(Iterable<Representation> representations,boolean allowCreate) throws IllegalArgumentException, YardException{
RepositoryConnection con = null;
try {
con = repository.getConnection();
con.begin();
ArrayList<Representation> added = new ArrayList<Representation>();
for(Representation representation : representations){
if(representation != null){
Representation stored = store(con, representation,allowCreate,false); //reassign
//to check if the store was successful
if(stored != null){
added.add(stored);
} else { //can only be the case if allowCreate==false (update was called)
log.warn(String.format("Unable to update Representation %s in Yard %s because it is not present!",
representation.getId(),getId()));
}
} //ignore null values in the parsed Iterable!
}
con.commit();
return added;
} catch (RepositoryException e) {
throw new YardException("Unable to remove parsed Representations", e);
} catch (IllegalArgumentException e) {
try {
//to avoid Exception logs in case store(..) throws an Exception
//in the case allowCreate and canNotCreateIsError do not allow
//the store operation
con.rollback();
} catch (RepositoryException ignore) {}
throw e;
} finally {
if(con != null){
try {
con.close();
} catch (RepositoryException ignore) {}
}
}
}
/**
* Generic store method used by store and update methods
* @param representation the representation to store/update
* @param allowCreate if new representation are allowed to be created
* @param canNotCreateIsError if updates to existing one are allowed
* @return the representation as added to the yard
* @throws IllegalArgumentException
* @throws YardException
*/
protected final Representation store(Representation representation,boolean allowCreate,boolean canNotCreateIsError) throws IllegalArgumentException, YardException{
RepositoryConnection con = null;
try {
con = repository.getConnection();
con.begin();
Representation added = store(con,representation,allowCreate,canNotCreateIsError);
con.commit();
return added;
} catch (RepositoryException e) {
throw new YardException("Unable to remove parsed Representations", e);
} catch (IllegalArgumentException e) {
try {
//to avoid Exception logs in case store(..) throws an Exception
//in the case allowCreate and canNotCreateIsError do not allow
//the store operation
con.rollback();
} catch (RepositoryException ignore) {}
throw e;
} finally {
if(con != null){
try {
con.close();
} catch (RepositoryException ignore) {}
}
}
}
protected final Representation store(RepositoryConnection con, Representation representation,boolean allowCreate,boolean canNotCreateIsError) throws IllegalArgumentException, RepositoryException {
if(representation == null) {
return null;
}
log.debug("store Representation " + representation.getId());
URI subject = sesameFactory.createURI(representation.getId());
boolean contains = con.hasStatement(subject, null, null, includeInferred, contexts);
con.remove(subject, null, null, contexts);
if(!contains && !allowCreate){
if(canNotCreateIsError) {
throw new IllegalArgumentException("Parsed Representation "+representation.getId()+" in not managed by this Yard "+getName()+"(id="+getId()+")");
} else {
return null;
}
}
//get the graph for the Representation and add it to the store
RdfRepresentation toAdd = valueFactory.toRdfRepresentation(representation);
if(toAdd.getModel().isEmpty()){
con.add(toAdd.getURI(),managedRepresentation,managedRepresentationState, contexts);
} else {
con.add(toAdd.getModel(), contexts);
}
return toAdd;
}
@Override
public QueryResultList<String> findReferences(FieldQuery parsedQuery) throws YardException, IllegalArgumentException {
if(parsedQuery == null){
throw new IllegalArgumentException("The parsed query MUST NOT be NULL!");
}
final SparqlFieldQuery query = SparqlFieldQueryFactory.getSparqlFieldQuery(parsedQuery);
RepositoryConnection con = null;
TupleQueryResult results = null;
try {
con = repository.getConnection();
con.begin();
//execute the query
int limit = QueryUtils.getLimit(query, getConfig().getDefaultQueryResultNumber(),
getConfig().getMaxQueryResultNumber());
results = executeSparqlFieldQuery(con, query, limit, false);
//parse the results
List<String> ids = limit > 0 ? new ArrayList<String>(limit) : new ArrayList<String>();
while(results.hasNext()){
BindingSet result = results.next();
Value value = result.getValue(query.getRootVariableName());
if(value instanceof Resource){
ids.add(value.stringValue());
}
}
con.commit();
return new QueryResultListImpl<String>(query,ids,String.class);
} catch (RepositoryException e) {
throw new YardException("Unable to execute findReferences query", e);
} catch (QueryEvaluationException e) {
throw new YardException("Unable to execute findReferences query", e);
} finally {
if(results != null) { //close the result if present
try {
results.close();
} catch (QueryEvaluationException ignore) {/* ignore */}
}
if(con != null){
try {
con.close();
} catch (RepositoryException ignore) {/* ignore */}
}
}
}
/**
* Returns the SPARQL result set for a given {@link SparqlFieldQuery} that
* was executed on this yard
* @param con the repository connection to use
* @param fieldQuery the SparqlFieldQuery instance
* @param limit the maximum number of results
* @return the results of the SPARQL query in the {@link #contexts} of the
* Sesame Repository
* @throws RepositoryException on any error while using the parsed connection
* @throws QueryEvaluationException on any error while executing the query
* @throws YardException if the SPARQL query created for the parsed FieldQuery
* was illegal formatted or if the {@link #repository} does not support
* SPARQL.
*/
private TupleQueryResult executeSparqlFieldQuery(RepositoryConnection con, final SparqlFieldQuery fieldQuery, int limit, boolean select) throws RepositoryException, YardException, QueryEvaluationException {
log.debug("> execute FieldQuery: {}", fieldQuery);
String sparqlQueryString = SparqlQueryUtils.createSparqlSelectQuery(
fieldQuery, select, limit, SparqlEndpointTypeEnum.Sesame);
log.debug(" - SPARQL Query: {}", sparqlQueryString);
TupleQuery sparqlOuery;
try {
sparqlOuery = con.prepareTupleQuery(QueryLanguage.SPARQL, sparqlQueryString);
} catch (MalformedQueryException e) {
log.error("Unable to pparse SPARQL Query generated for a FieldQuery");
log.error("FieldQuery: {}",fieldQuery);
log.error("SPARQL Query: {}",sparqlQueryString);
log.error("Exception ", e);
throw new YardException("Unable to parse SPARQL query generated for the parse FieldQuery", e);
} catch (UnsupportedQueryTypeException e) {
String message = "The Sesame Repository '" + repository + "'(class: "
+ repository.getClass().getName() + ") does not support SPARQL!";
log.error(message, e);
throw new YardException(message, e);
}
if(dataset != null){ //respect the configured contexts
sparqlOuery.setDataset(dataset);
}
return sparqlOuery.evaluate();
}
@Override
public QueryResultList<Representation> findRepresentation(FieldQuery parsedQuery) throws YardException, IllegalArgumentException {
if(parsedQuery == null){
throw new IllegalArgumentException("The parsed query MUST NOT be NULL!");
}
final SparqlFieldQuery query = SparqlFieldQueryFactory.getSparqlFieldQuery(parsedQuery);
RepositoryConnection con = null;
TupleQueryResult results = null;
try {
con = repository.getConnection();
con.begin();
//execute the query
int limit = QueryUtils.getLimit(query, getConfig().getDefaultQueryResultNumber(),
getConfig().getMaxQueryResultNumber());
results = executeSparqlFieldQuery(con,query, limit, false);
//parse the results and generate the Representations
//create an own valueFactors so that all the data of the query results
//are added to the same Sesame Model
Model model = new TreeModel();
RdfValueFactory valueFactory = new RdfValueFactory(model, sesameFactory);
List<Representation> representations = limit > 0 ?
new ArrayList<Representation>(limit) : new ArrayList<Representation>();
while(results.hasNext()){
BindingSet result = results.next();
Value value = result.getValue(query.getRootVariableName());
if(value instanceof URI){
//copy all data to the model and create the representation
RdfRepresentation rep = createRepresentationGraph(con, valueFactory, (URI)value);
model.add(queryRoot, queryResult, value); //link the result with the query result
representations.add(rep);
} //ignore non URI results
}
con.commit();
return new SesameQueryResultList(model, query, representations);
} catch (RepositoryException e) {
throw new YardException("Unable to execute findReferences query", e);
} catch (QueryEvaluationException e) {
throw new YardException("Unable to execute findReferences query", e);
} finally {
if(results != null) { //close the result if present
try {
results.close();
} catch (QueryEvaluationException ignore) {/* ignore */}
}
if(con != null){
try {
con.close();
} catch (RepositoryException ignore) {/* ignore */}
}
}
}
@Override
public final QueryResultList<Representation> find(FieldQuery parsedQuery) throws YardException, IllegalArgumentException {
if(parsedQuery == null){
throw new IllegalArgumentException("The parsed query MUST NOT be NULL!");
}
final SparqlFieldQuery query = SparqlFieldQueryFactory.getSparqlFieldQuery(parsedQuery);
RepositoryConnection con = null;
TupleQueryResult results = null;
try {
con = repository.getConnection();
con.begin();
//execute the query
int limit = QueryUtils.getLimit(query, getConfig().getDefaultQueryResultNumber(),
getConfig().getMaxQueryResultNumber());
results = executeSparqlFieldQuery(con,query, limit, true);
//parse the results and generate the Representations
//create an own valueFactors so that all the data of the query results
//are added to the same Sesame Model
Model model = new TreeModel();
RdfValueFactory valueFactory = new RdfValueFactory(model, sesameFactory);
List<Representation> representations = limit > 0 ? new ArrayList<Representation>(limit)
: new ArrayList<Representation>();
Map<String,URI> bindings = new HashMap<String,URI>(query.getFieldVariableMappings().size());
for(Entry<String,String> mapping : query.getFieldVariableMappings().entrySet()){
bindings.put(mapping.getValue(), sesameFactory.createURI(mapping.getKey()));
}
while(results.hasNext()){
BindingSet result = results.next();
Value value = result.getValue(query.getRootVariableName());
if(value instanceof URI){
URI subject = (URI) value;
//link the result with the query result
model.add(queryRoot, queryResult, subject);
//now copy over the other selected data
for(String binding : result.getBindingNames()){
URI property = bindings.get(binding);
if(property != null){
model.add(subject, property, result.getValue(binding));
} //else no mapping for the query.getRootVariableName()
}
//create a representation and add it to the results
representations.add(valueFactory.createRdfRepresentation(subject));
} //ignore non URI results
}
con.commit();
return new SesameQueryResultList(model, query, representations);
} catch (RepositoryException e) {
throw new YardException("Unable to execute findReferences query", e);
} catch (QueryEvaluationException e) {
throw new YardException("Unable to execute findReferences query", e);
} finally {
if(results != null) { //close the result if present
try {
results.close();
} catch (QueryEvaluationException ignore) {/* ignore */}
}
if(con != null){
try {
con.close();
} catch (RepositoryException ignore) {/* ignore */}
}
}
}
/**
* Wrapper that converts a Sesame {@link TupleQueryResult} to a {@link Iterator}.
* <b>NOTE</b> this will not close the {@link TupleQueryResult}!
* @author Rupert westenthaler
*
*/
static class TupleResultIterator implements Iterator<BindingSet> {
private final TupleQueryResult resultList;
public TupleResultIterator(TupleQueryResult resultList) {
this.resultList = resultList;
}
@Override
public boolean hasNext() {
try {
return resultList.hasNext();
} catch (QueryEvaluationException e) {
throw new IllegalStateException(e);
}
}
@Override
public BindingSet next() {
try {
return resultList.next();
} catch (QueryEvaluationException e) {
throw new IllegalStateException(e);
}
}
@Override
public void remove() {
throw new UnsupportedOperationException("Remove is not supported by Sesame TupleQueryResult");
}
}
}