From d25176298d660e2c639561c0bc898d9c988b4b25 Mon Sep 17 00:00:00 2001 From: Jim Blake Date: Fri, 6 Feb 2015 15:22:42 -0500 Subject: [PATCH] VIVO-869 Drastically simplify the search configuration. Create LabelsAcrossContextNodes to replace AdditionalURIsForContextNodes. It's more configurable. Create SelectQueryUriFinder to replace AdditionalUrisForVCards. It's more configurable. Rename SimpleSparqlQueryDocumentModifier to SelectQueryDocumentModifier, to conform. Radically simplify the VIVO search configuration, omitting all special cases except those for VCards. --- .../searchIndexerConfigurationVitro.n3 | 4 +- ....java => SelectQueryDocumentModifier.java} | 93 +++-------- .../indexing/SelectQueryUriFinder.java | 157 ++++++++++++++++++ .../utils/sparql/RdfServiceQueryContext.java | 125 ++++++++++++++ .../utils/sparql/SelectQueryHolder.java | 36 ++++ .../utils/sparql/SelectQueryRunner.java | 104 ++++++++++++ .../indexing/SelectQueryUriFinderTest.java | 156 +++++++++++++++++ 7 files changed, 607 insertions(+), 68 deletions(-) rename webapp/src/edu/cornell/mannlib/vitro/webapp/searchindex/documentBuilding/{SimpleSparqlQueryDocumentModifier.java => SelectQueryDocumentModifier.java} (65%) create mode 100644 webapp/src/edu/cornell/mannlib/vitro/webapp/searchindex/indexing/SelectQueryUriFinder.java create mode 100644 webapp/src/edu/cornell/mannlib/vitro/webapp/utils/sparql/RdfServiceQueryContext.java create mode 100644 webapp/src/edu/cornell/mannlib/vitro/webapp/utils/sparql/SelectQueryHolder.java create mode 100644 webapp/src/edu/cornell/mannlib/vitro/webapp/utils/sparql/SelectQueryRunner.java create mode 100644 webapp/test/edu/cornell/mannlib/vitro/webapp/searchindex/indexing/SelectQueryUriFinderTest.java 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); + } + +}