diff --git a/webapp/rdf/display/everytime/searchIndexerConfigurationVitro.n3 b/webapp/rdf/display/everytime/searchIndexerConfigurationVitro.n3 index 4990dc99f..74b34e211 100644 --- a/webapp/rdf/display/everytime/searchIndexerConfigurationVitro.n3 +++ b/webapp/rdf/display/everytime/searchIndexerConfigurationVitro.n3 @@ -61,11 +61,11 @@ # ------------------------------------ :documentModifier_AllNames - a , + a , ; rdfs:label "All labels are added to name fields." ; :hasTargetField "nameRaw" ; - :hasSparqlQuery """ + :hasSelectQuery """ PREFIX rdfs: SELECT ?label WHERE { diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/searchindex/documentBuilding/SimpleSparqlQueryDocumentModifier.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/searchindex/documentBuilding/SelectQueryDocumentModifier.java similarity index 65% rename from webapp/src/edu/cornell/mannlib/vitro/webapp/searchindex/documentBuilding/SimpleSparqlQueryDocumentModifier.java rename to webapp/src/edu/cornell/mannlib/vitro/webapp/searchindex/documentBuilding/SelectQueryDocumentModifier.java index 6804a8e55..1494ddbbb 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/searchindex/documentBuilding/SimpleSparqlQueryDocumentModifier.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/searchindex/documentBuilding/SelectQueryDocumentModifier.java @@ -5,34 +5,32 @@ package edu.cornell.mannlib.vitro.webapp.searchindex.documentBuilding; import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess.WhichService.CONTENT; import static edu.cornell.mannlib.vitro.webapp.search.VitroSearchTermNames.ALLTEXT; import static edu.cornell.mannlib.vitro.webapp.search.VitroSearchTermNames.ALLTEXTUNSTEMMED; +import static edu.cornell.mannlib.vitro.webapp.utils.sparql.SelectQueryRunner.createQueryContext; import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; -import java.util.Iterator; import java.util.List; import java.util.Set; -import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import com.hp.hpl.jena.query.QuerySolution; -import com.hp.hpl.jena.query.ResultSet; -import com.hp.hpl.jena.rdf.model.RDFNode; - import edu.cornell.mannlib.vitro.webapp.beans.Individual; import edu.cornell.mannlib.vitro.webapp.beans.VClass; import edu.cornell.mannlib.vitro.webapp.modelaccess.ContextModelAccess; import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchInputDocument; import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService; -import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.RDFServiceUtils; import edu.cornell.mannlib.vitro.webapp.utils.configuration.ContextModelsUser; import edu.cornell.mannlib.vitro.webapp.utils.configuration.Property; import edu.cornell.mannlib.vitro.webapp.utils.configuration.Validation; +import edu.cornell.mannlib.vitro.webapp.utils.sparql.SelectQueryHolder; /** - * If the individual qualifies, execute the SPARQL queries and add the results - * to the specified search fields. + * Modify the document, adding the results of one or more select queries. + * + * If the individual qualifies, execute the queries and add the results to the + * specified search fields. * * If there are no specified search fields, ALLTEXT and ALLTEXTUNSTEMMED are * assumed. @@ -44,16 +42,15 @@ import edu.cornell.mannlib.vitro.webapp.utils.configuration.Validation; * of the individual. * * All of the result fields of all result rows of all of the queries will be - * concatenated into a single result, separated by spaces. That result will be - * added to each of the specified search fields. + * converted to strings and added to each of the specified search fields. * * A label may be supplied to the instance, for use in logging. If no label is * supplied, one will be generated. */ -public class SimpleSparqlQueryDocumentModifier implements DocumentModifier, +public class SelectQueryDocumentModifier implements DocumentModifier, ContextModelsUser { private static final Log log = LogFactory - .getLog(SimpleSparqlQueryDocumentModifier.class); + .getLog(SelectQueryDocumentModifier.class); private RDFService rdfService; @@ -85,7 +82,7 @@ public class SimpleSparqlQueryDocumentModifier implements DocumentModifier, label = l; } - @Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasSparqlQuery") + @Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasSelectQuery") public void addQuery(String query) { queries.add(query); } @@ -122,14 +119,12 @@ public class SimpleSparqlQueryDocumentModifier implements DocumentModifier, @Override public void modifyDocument(Individual ind, SearchInputDocument doc) { - if (!passesTypeRestrictions(ind)) { - return; - } + if (passesTypeRestrictions(ind)) { + List values = getTextForQueries(ind); - String text = getTextForQueries(ind); - - for (String fieldName : fieldNames) { - doc.addField(fieldName, text); + for (String fieldName : fieldNames) { + doc.addField(fieldName, values); + } } } @@ -146,59 +141,25 @@ public class SimpleSparqlQueryDocumentModifier implements DocumentModifier, return false; } - private String getTextForQueries(Individual ind) { + private List getTextForQueries(Individual ind) { List list = new ArrayList<>(); for (String query : queries) { - String text = getTextForQuery(substituteUri(ind, query)); - if (StringUtils.isNotBlank(text)) { - list.add(text); - } + list.addAll(getTextForQuery(query, ind)); } - return StringUtils.join(list, " "); + return list; } - private String substituteUri(Individual ind, String query) { - return query.replace("?uri", "<" + ind.getURI() + "> "); - } - - private String getTextForQuery(String query) { - List list = new ArrayList<>(); + private List getTextForQuery(String query, Individual ind) { try { - ResultSet results = RDFServiceUtils.sparqlSelectQuery(query, - rdfService); - while (results.hasNext()) { - String text = getTextForRow(results.nextSolution()); - if (StringUtils.isNotBlank(text)) { - list.add(text); - } - } + SelectQueryHolder queryHolder = new SelectQueryHolder(query) + .bindToUri("uri", ind.getURI()); + List list = createQueryContext(rdfService, queryHolder) + .execute().getStringFields().flatten(); + log.debug(label + " - query: '" + query + "' returns " + list); + return list; } catch (Throwable t) { log.error("problem while running query '" + query + "'", t); - } - log.debug(label + " - query: '" + query + "' returns " + list); - return StringUtils.join(list, " "); - } - - private String getTextForRow(QuerySolution row) { - List list = new ArrayList<>(); - Iterator names = row.varNames(); - while (names.hasNext()) { - RDFNode node = row.get(names.next()); - String text = getTextForNode(node); - if (StringUtils.isNotBlank(text)) { - list.add(text); - } - } - return StringUtils.join(list, " "); - } - - private String getTextForNode(RDFNode node) { - if (node == null) { - return ""; - } else if (node.isLiteral()) { - return node.asLiteral().getString().trim(); - } else { - return node.toString().trim(); + return Collections.emptyList(); } } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/searchindex/indexing/SelectQueryUriFinder.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/searchindex/indexing/SelectQueryUriFinder.java new file mode 100644 index 000000000..a0e50797a --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/searchindex/indexing/SelectQueryUriFinder.java @@ -0,0 +1,157 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.searchindex.indexing; + +import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess.WhichService.CONTENT; +import static edu.cornell.mannlib.vitro.webapp.utils.sparql.SelectQueryRunner.createQueryContext; +import static edu.cornell.mannlib.vitro.webapp.utils.sparql.SelectQueryRunner.selectQuery; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.hp.hpl.jena.rdf.model.RDFNode; +import com.hp.hpl.jena.rdf.model.Statement; + +import edu.cornell.mannlib.vitro.webapp.modelaccess.ContextModelAccess; +import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService; +import edu.cornell.mannlib.vitro.webapp.utils.configuration.ContextModelsUser; +import edu.cornell.mannlib.vitro.webapp.utils.configuration.Property; +import edu.cornell.mannlib.vitro.webapp.utils.configuration.Validation; +import edu.cornell.mannlib.vitro.webapp.utils.sparql.SelectQueryHolder; + +/** + * Find URIs based on one or more select queries. + * + * If the statement qualifies, execute the queries and return the accumulated + * results. + * + * A statement qualifies if the predicate matches any of the restrictions, or if + * there are no restrictions. + * + * If a query contains a ?subject, ?predicate, or ?object variable, it will be + * bound to the URI of the subject, predicate, or object of the statement, + * respectively. If the subject or object has no URI and the query expects one, + * then the query will be ignored. (Predicates always have URIs.) + * + * All of the result fields of all result rows of all of the queries will be + * returned. + * + * A label may be supplied to the instance, for use in logging. If no label is + * supplied, one will be generated. + */ +public class SelectQueryUriFinder implements IndexingUriFinder, + ContextModelsUser { + private static final Log log = LogFactory + .getLog(SelectQueryUriFinder.class); + + private RDFService rdfService; + + /** A name to be used in logging, to identify this instance. */ + private String label; + + /** The queries to be executed. There must be at least one. */ + private List queries = new ArrayList<>(); + + /** + * URIs of the predicates that will trigger these queries. If empty, then + * the queries apply to all statements. + */ + private Set predicateRestrictions = new HashSet<>(); + + @Override + public void setContextModels(ContextModelAccess models) { + this.rdfService = models.getRDFService(CONTENT); + } + + @Property(uri = "http://www.w3.org/2000/01/rdf-schema#label") + public void setLabel(String l) { + label = l; + } + + @Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasSelectQuery") + public void addQuery(String query) { + queries.add(query); + } + + @Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasPredicateRestriction") + public void addPredicateRestriction(String predicateUri) { + predicateRestrictions.add(predicateUri); + } + + @Validation + public void validate() { + if (label == null) { + label = this.getClass().getSimpleName() + ":" + this.hashCode(); + } + if (queries.isEmpty()) { + throw new IllegalStateException( + "Configuration contains no queries for " + label); + } + } + + @Override + public String toString() { + return (label == null) ? super.toString() : label; + } + + @Override + public void startIndexing() { + // Nothing to do. + } + + @Override + public List findAdditionalURIsToIndex(Statement stmt) { + List list = new ArrayList<>(); + if (passesTypePredicateRestrictions(stmt)) { + for (String query : queries) { + list.addAll(getUrisForQuery(stmt, query)); + } + } + return list; + } + + private boolean passesTypePredicateRestrictions(Statement stmt) { + return predicateRestrictions.isEmpty() + || predicateRestrictions.contains(stmt.getPredicate().getURI()); + } + + private List getUrisForQuery(Statement stmt, String queryString) { + SelectQueryHolder query = selectQuery(queryString); + query = query.bindToUri("predicate", stmt.getPredicate().getURI()); + + query = tryToBindUri(query, "subject", stmt.getSubject()); + query = tryToBindUri(query, "object", stmt.getObject()); + if (query == null) { + return Collections.emptyList(); + } + + return createQueryContext(rdfService, query).execute() + .getStringFields().flatten(); + } + + private SelectQueryHolder tryToBindUri(SelectQueryHolder query, + String name, RDFNode node) { + if (query == null) { + return null; + } + if (!query.hasVariable(name)) { + return query; + } + if (!node.isURIResource()) { + return null; + } + return query.bindToUri(name, node.asResource().getURI()); + } + + @Override + public void endIndexing() { + // Nothing to do. + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/sparql/RdfServiceQueryContext.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/sparql/RdfServiceQueryContext.java new file mode 100644 index 000000000..148acda48 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/sparql/RdfServiceQueryContext.java @@ -0,0 +1,125 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.utils.sparql; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.hp.hpl.jena.query.QuerySolution; +import com.hp.hpl.jena.query.ResultSet; +import com.hp.hpl.jena.rdf.model.RDFNode; + +import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService; +import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.RDFServiceUtils; +import edu.cornell.mannlib.vitro.webapp.utils.sparql.SelectQueryRunner.ExecutingSelectQueryContext; +import edu.cornell.mannlib.vitro.webapp.utils.sparql.SelectQueryRunner.SelectQueryContext; +import edu.cornell.mannlib.vitro.webapp.utils.sparql.SelectQueryRunner.StringResultsMapping; +import edu.cornell.mannlib.vitro.webapp.utils.sparql.SelectQueryRunner.StringResultsMappingImpl; + +/** + * An implementation of QueryContext based on an RDFService. + * + * Package access. Instances should be created only by SelectQueryRunner, or by + * a method on this class. + */ +class RdfServiceQueryContext implements SelectQueryContext { + private static final Log log = LogFactory + .getLog(RdfServiceQueryContext.class); + + private final RDFService rdfService; + private final SelectQueryHolder query; + + RdfServiceQueryContext(RDFService rdfService, SelectQueryHolder query) { + this.rdfService = rdfService; + this.query = query; + } + + @Override + public RdfServiceQueryContext bindVariableToUri(String name, String uri) { + return new RdfServiceQueryContext(rdfService, + query.bindToUri(name, uri)); + } + + @Override + public ExecutingSelectQueryContext execute() { + return new RdfServiceExecutingQueryContext(rdfService, query); + } + + private static class RdfServiceExecutingQueryContext implements + ExecutingSelectQueryContext { + private final RDFService rdfService; + private final SelectQueryHolder query; + + public RdfServiceExecutingQueryContext(RDFService rdfService, + SelectQueryHolder query) { + this.rdfService = rdfService; + this.query = query; + } + + @Override + public StringResultsMapping getStringFields(String... names) { + Set fieldNames = new HashSet<>(Arrays.asList(names)); + StringResultsMappingImpl mapping = new StringResultsMappingImpl(); + try { + ResultSet results = RDFServiceUtils.sparqlSelectQuery( + query.getQueryString(), rdfService); + return mapResultsForQuery(results, fieldNames); + } catch (Exception e) { + log.error( + "problem while running query '" + + query.getQueryString() + "'", e); + } + return mapping; + } + + private StringResultsMapping mapResultsForQuery(ResultSet results, + Set fieldNames) { + StringResultsMappingImpl mapping = new StringResultsMappingImpl(); + while (results.hasNext()) { + Map rowMapping = mapResultsForRow( + results.nextSolution(), fieldNames); + if (!rowMapping.isEmpty()) { + mapping.add(rowMapping); + } + } + return mapping; + } + + private Map mapResultsForRow(QuerySolution row, + Set fieldNames) { + Map map = new HashMap<>(); + for (Iterator names = row.varNames(); names.hasNext();) { + String name = names.next(); + RDFNode node = row.get(name); + String text = getTextForNode(node); + if (StringUtils.isNotBlank(text)) { + map.put(name, text); + } + } + if (!fieldNames.isEmpty()) { + map.keySet().retainAll(fieldNames); + } + return map; + } + + private String getTextForNode(RDFNode node) { + if (node == null) { + return ""; + } else if (node.isLiteral()) { + return node.asLiteral().getString().trim(); + } else { + return node.toString().trim(); + } + } + + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/sparql/SelectQueryHolder.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/sparql/SelectQueryHolder.java new file mode 100644 index 000000000..d59ce1bea --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/sparql/SelectQueryHolder.java @@ -0,0 +1,36 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.utils.sparql; + +import java.util.regex.Pattern; + +/** + * Holds the text of a SPARQL Select query, and allows you to perform some lexical + * operations on it. + * + * This is immutable, so don't forget to get the result of the operations. + */ +public class SelectQueryHolder { + private final String queryString; + + public SelectQueryHolder(String queryString) { + this.queryString = queryString; + } + + public String getQueryString() { + return queryString; + } + + public boolean hasVariable(String name) { + String regex = "\\?" + name + "\\b"; + return Pattern.compile(regex).matcher(queryString).find(); + } + + public SelectQueryHolder bindToUri(String name, String uri) { + String regex = "\\?" + name + "\\b"; + String replacement = "<" + uri + ">"; + String bound = queryString.replaceAll(regex, replacement); + return new SelectQueryHolder(bound); + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/sparql/SelectQueryRunner.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/sparql/SelectQueryRunner.java new file mode 100644 index 000000000..cbf81068a --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/sparql/SelectQueryRunner.java @@ -0,0 +1,104 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.utils.sparql; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService; + +/** + * A conversational tool for handling SPARQL queries. + * + *
+ * Examples:
+ *   List values = createQueryContext(rdfService, queryString)
+ *                             .bindVariableToUri("uri", uri)
+ * 				               .execute()
+ * 				               .getStringFields("partner")
+ * 				               .flatten();
+ * 
+ *   SelectQueryHolder q = selectQuery(queryString)
+ *                             .bindToUri("uri", uri));
+ *   List map = createQueryContext(rdfService, q)
+ *                             .execute()
+ *                             .getStringFields();
+ * 
+ * + * The execute() method does not actually execute the query: it merely sets it + * up syntactically. + * + * If you don't supply any field names to getStringFields(), you get all of + * them. + * + * Any string value that returns a blank or empty string is omitted from the + * results. Any row that returns no values is omitted from the results. + */ +public final class SelectQueryRunner { + private static final Log log = LogFactory.getLog(SelectQueryRunner.class); + + private SelectQueryRunner() { + // No need to create an instance. + } + + public static SelectQueryHolder selectQuery(String queryString) { + return new SelectQueryHolder(queryString); + } + + public static SelectQueryContext createQueryContext(RDFService rdfService, + String queryString) { + return createQueryContext(rdfService, selectQuery(queryString)); + } + + public static SelectQueryContext createQueryContext(RDFService rdfService, + SelectQueryHolder query) { + return new RdfServiceQueryContext(rdfService, query); + } + + public static interface SelectQueryContext { + public SelectQueryContext bindVariableToUri(String name, String uri); + + public ExecutingSelectQueryContext execute(); + } + + public static interface ExecutingSelectQueryContext { + public StringResultsMapping getStringFields(String... fieldNames); + } + + public static interface StringResultsMapping extends + List> { + public List flatten(); + + public Set flattenToSet(); + } + + // ---------------------------------------------------------------------- + // Helper classes + // ---------------------------------------------------------------------- + + static class StringResultsMappingImpl extends + ArrayList> implements StringResultsMapping { + + @Override + public List flatten() { + List flat = new ArrayList<>(); + for (Map map : this) { + flat.addAll(map.values()); + } + return flat; + } + + @Override + public Set flattenToSet() { + return new HashSet<>(flatten()); + } + + } + +} diff --git a/webapp/test/edu/cornell/mannlib/vitro/webapp/searchindex/indexing/SelectQueryUriFinderTest.java b/webapp/test/edu/cornell/mannlib/vitro/webapp/searchindex/indexing/SelectQueryUriFinderTest.java new file mode 100644 index 000000000..59bed0f75 --- /dev/null +++ b/webapp/test/edu/cornell/mannlib/vitro/webapp/searchindex/indexing/SelectQueryUriFinderTest.java @@ -0,0 +1,156 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.searchindex.indexing; + +import static com.hp.hpl.jena.rdf.model.ResourceFactory.createPlainLiteral; +import static com.hp.hpl.jena.rdf.model.ResourceFactory.createProperty; +import static com.hp.hpl.jena.rdf.model.ResourceFactory.createResource; +import static com.hp.hpl.jena.rdf.model.ResourceFactory.createStatement; +import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess.WhichService.CONTENT; +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.junit.Before; +import org.junit.Test; + +import stubs.edu.cornell.mannlib.vitro.webapp.modelaccess.ContextModelAccessStub; + +import com.hp.hpl.jena.rdf.model.Model; +import com.hp.hpl.jena.rdf.model.ModelFactory; +import com.hp.hpl.jena.rdf.model.Property; +import com.hp.hpl.jena.rdf.model.RDFNode; +import com.hp.hpl.jena.rdf.model.Resource; + +import edu.cornell.mannlib.vitro.testing.AbstractTestClass; +import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService; +import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.jena.model.RDFServiceModel; + +/** + * TODO + * + * If the statement qualifies, execute the queries and return the accumulated + * results. + * + * A statement qualifies if the predicate matches any of the restrictions, or if + * there are no restrictions. + * + * If a query contains a ?subject or ?object variable, it will be bound to the + * URI of the subject or object of the statement, respectively. If the subject + * or object has no URI for the query, then the query will be ignored. + * + * All of the result fields of all result rows of all of the queries will be + * returned. + * + * A label may be supplied to the instance, for use in logging. If no label is + * supplied, one will be generated. + */ +public class SelectQueryUriFinderTest extends AbstractTestClass { + private static final Log log = LogFactory + .getLog(SelectQueryUriFinderTest.class); + + private static final String BOB_URI = "http://ns#Bob"; + private static final String BETTY_URI = "http://ns#Betty"; + private static final String DICK_URI = "http://ns#Dick"; + private static final String JANE_URI = "http://ns#Jane"; + private static final String FRIEND_URI = "http://ns#Friend"; + private static final String SEES_URI = "http://ns#Sees"; + private static final String OTHER_URI = "http://ns#Other"; + + private static final Resource BOB = createResource(BOB_URI); + private static final Resource BETTY = createResource(BETTY_URI); + private static final Resource DICK = createResource(DICK_URI); + private static final Resource JANE = createResource(JANE_URI); + private static final Property FRIEND = createProperty(FRIEND_URI); + private static final Property SEES = createProperty(SEES_URI); + + private static final String QUERY1 = "SELECT ?friend WHERE {?subject <" + + FRIEND_URI + "> ?friend}"; + private static final String QUERY2 = "SELECT ?partner WHERE {?object <" + + FRIEND_URI + "> ?partner}"; + + private Model m; + private RDFService rdfService; + private SelectQueryUriFinder finder; + private List foundUris; + + @Before + public void populateModel() { + m = ModelFactory.createDefaultModel(); + m.add(createStatement(BOB, FRIEND, BETTY)); + m.add(createStatement(DICK, FRIEND, JANE)); + + rdfService = new RDFServiceModel(m); + + ContextModelAccessStub models = new ContextModelAccessStub(); + models.setRDFService(CONTENT, rdfService); + + finder = new SelectQueryUriFinder(); + finder.setContextModels(models); + finder.addQuery(QUERY1); + finder.addQuery(QUERY2); + } + + @Test + public void fullSuccess_bothResults() { + setPredicateRestrictions(); + exerciseUriFinder(BOB, SEES, DICK); + assertExpectedUris(BETTY_URI, JANE_URI); + } + + @Test + public void acceptableRestriction_bothResults() { + setPredicateRestrictions(SEES_URI); + exerciseUriFinder(BOB, SEES, DICK); + assertExpectedUris(BETTY_URI, JANE_URI); + } + + @Test + public void excludingRestriction_noResults() { + setPredicateRestrictions(OTHER_URI); + exerciseUriFinder(BOB, SEES, DICK); + assertExpectedUris(); + } + + @Test + public void blankSubject_justObjectResult() { + setPredicateRestrictions(); + exerciseUriFinder(createResource(), SEES, DICK); + assertExpectedUris(JANE_URI); + } + + @Test + public void literalObject_justSubjectResult() { + setPredicateRestrictions(); + exerciseUriFinder(BOB, SEES, createPlainLiteral("Bogus")); + assertExpectedUris(BETTY_URI); + } + + // ---------------------------------------------------------------------- + // Helper methods + // ---------------------------------------------------------------------- + + private void setPredicateRestrictions(String... uris) { + for (String uri : uris) { + finder.addPredicateRestriction(uri); + } + } + + private void exerciseUriFinder(Resource subject, Property predicate, + RDFNode object) { + foundUris = finder.findAdditionalURIsToIndex(createStatement(subject, + predicate, object)); + } + + private void assertExpectedUris(String... expectedArray) { + Set expected = new HashSet<>(Arrays.asList(expectedArray)); + Set actual = new HashSet<>(foundUris); + assertEquals("found URIs", expected, actual); + } + +}