Repository: incubator-rya Updated Branches: refs/heads/master 9c12630bb -> fc8d30ac6
RYA-294 owl:someValuesFrom inference. Closes #217. Project: http://git-wip-us.apache.org/repos/asf/incubator-rya/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-rya/commit/fc8d30ac Tree: http://git-wip-us.apache.org/repos/asf/incubator-rya/tree/fc8d30ac Diff: http://git-wip-us.apache.org/repos/asf/incubator-rya/diff/fc8d30ac Branch: refs/heads/master Commit: fc8d30ac6ca107face91c8c3f573def60dc7af5d Parents: 9c12630 Author: Jesse Hatfield <[email protected]> Authored: Wed Aug 23 21:14:48 2017 -0400 Committer: Caleb Meier <[email protected]> Committed: Fri Aug 25 10:55:28 2017 -0700 ---------------------------------------------------------------------- .../api/RdfCloudTripleStoreConfiguration.java | 20 ++ .../src/main/java/MongoRyaDirectExample.java | 54 +++++ .../RdfCloudTripleStoreConnection.java | 2 + .../inference/InferenceEngine.java | 237 +++++++++++++++---- .../inference/SomeValuesFromVisitor.java | 110 +++++++++ .../inference/AllValuesFromVisitorTest.java | 8 +- .../inference/InferenceEngineTest.java | 42 ++++ .../rdftriplestore/inference/InferenceIT.java | 68 +++++- .../inference/SomeValuesFromVisitorTest.java | 152 ++++++++++++ 9 files changed, 639 insertions(+), 54 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/fc8d30ac/common/rya.api/src/main/java/org/apache/rya/api/RdfCloudTripleStoreConfiguration.java ---------------------------------------------------------------------- diff --git a/common/rya.api/src/main/java/org/apache/rya/api/RdfCloudTripleStoreConfiguration.java b/common/rya.api/src/main/java/org/apache/rya/api/RdfCloudTripleStoreConfiguration.java index eeb49b5..6bebb86 100644 --- a/common/rya.api/src/main/java/org/apache/rya/api/RdfCloudTripleStoreConfiguration.java +++ b/common/rya.api/src/main/java/org/apache/rya/api/RdfCloudTripleStoreConfiguration.java @@ -75,6 +75,7 @@ public abstract class RdfCloudTripleStoreConfiguration extends Configuration { public static final String INFER_INCLUDE_INTERSECTION_OF = "infer.include.intersectionof"; public static final String INFER_INCLUDE_INVERSEOF = "infer.include.inverseof"; public static final String INFER_INCLUDE_ONE_OF = "infer.include.oneof"; + public static final String INFER_INCLUDE_SOME_VALUES_FROM = "infer.include.somevaluesfrom"; public static final String INFER_INCLUDE_SUBCLASSOF = "infer.include.subclassof"; public static final String INFER_INCLUDE_SUBPROPOF = "infer.include.subpropof"; public static final String INFER_INCLUDE_SYMMPROP = "infer.include.symmprop"; @@ -378,6 +379,25 @@ public abstract class RdfCloudTripleStoreConfiguration extends Configuration { setBoolean(INFER_INCLUDE_ONE_OF, value); } + /** + * @return {@code true} if owl:someValuesFrom inferencing is enabled. + * {@code false} otherwise. Defaults to {@code true} if nothing is + * specified. + */ + public Boolean isInferSomeValuesFrom() { + return getBoolean(INFER_INCLUDE_SOME_VALUES_FROM, true); + } + + /** + * Sets whether owl:someValuesFrom inferencing is enabled or disabled. + * @param value {@code true} if owl:someValuesFrom inferencing is enabled. + * {@code false} otherwise. + */ + public void setInferSomeValuesFrom(final Boolean value) { + Preconditions.checkNotNull(value); + setBoolean(INFER_INCLUDE_SOME_VALUES_FROM, value); + } + public Boolean isInferSubClassOf() { return getBoolean(INFER_INCLUDE_SUBCLASSOF, true); } http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/fc8d30ac/extras/indexingExample/src/main/java/MongoRyaDirectExample.java ---------------------------------------------------------------------- diff --git a/extras/indexingExample/src/main/java/MongoRyaDirectExample.java b/extras/indexingExample/src/main/java/MongoRyaDirectExample.java index 7f0d308..a7cf2e5 100644 --- a/extras/indexingExample/src/main/java/MongoRyaDirectExample.java +++ b/extras/indexingExample/src/main/java/MongoRyaDirectExample.java @@ -119,6 +119,7 @@ public class MongoRyaDirectExample { testInfer(conn, sail); testPropertyChainInference(conn, sail); testPropertyChainInferenceAltRepresentation(conn, sail); + testSomeValuesFromInference(conn, sail); testAllValuesFromInference(conn, sail); testIntersectionOfInference(conn, sail); testOneOfInference(conn, sail); @@ -519,6 +520,59 @@ public class MongoRyaDirectExample { Validate.isTrue(resultHandler.getCount() == 2); } + public static void testSomeValuesFromInference(final SailRepositoryConnection conn, final Sail sail) throws MalformedQueryException, RepositoryException, + UpdateExecutionException, QueryEvaluationException, TupleQueryResultHandlerException, InferenceEngineException { + final String lubm = "http://swat.cse.lehigh.edu/onto/univ-bench.owl#"; + log.info("Adding Data"); + String insert = "PREFIX lubm: <" + lubm + ">\n" + + "INSERT DATA { GRAPH <http://updated/test> {\n" + + " <urn:Department0> a lubm:Department; lubm:subOrganizationOf <urn:University0> .\n" + + " <urn:ResearchGroup0> a lubm:ResearchGroup; lubm:subOrganizationOf <urn:Department0> .\n" + + " <urn:Alice> lubm:headOf <urn:Department0> .\n" + + " <urn:Bob> lubm:headOf <urn:ResearchGroup0> .\n" + + " <urn:Carol> lubm:worksFor <urn:Department0> .\n" + + "}}"; + Update update = conn.prepareUpdate(QueryLanguage.SPARQL, insert); + update.execute(); + final String inferQuery = "select distinct ?x { GRAPH <http://updated/test> { ?x a <" + lubm + "Chair> }}"; + final String explicitQuery = "prefix lubm: <" + lubm + ">\n" + + "select distinct ?x { GRAPH <http://updated/test> {\n" + + " { ?x a lubm:Chair }\n" + + " UNION\n" + + " { ?x lubm:headOf [ a lubm:Department ] }\n" + + "}}"; + log.info("Running Explicit Query"); + final CountingResultHandler resultHandler = new CountingResultHandler(); + TupleQuery tupleQuery = conn.prepareTupleQuery(QueryLanguage.SPARQL, explicitQuery); + tupleQuery.evaluate(resultHandler); + log.info("Result count : " + resultHandler.getCount()); + Validate.isTrue(resultHandler.getCount() == 1); + log.info("Running Inference-dependent Query"); + resultHandler.resetCount(); + tupleQuery = conn.prepareTupleQuery(QueryLanguage.SPARQL, inferQuery); + tupleQuery.evaluate(resultHandler); + log.info("Result count : " + resultHandler.getCount()); + Validate.isTrue(resultHandler.getCount() == 0); + log.info("Adding owl:someValuesFrom Schema"); + insert = "PREFIX rdfs: <" + RDFS.NAMESPACE + ">\n" + + "PREFIX owl: <" + OWL.NAMESPACE + ">\n" + + "PREFIX lubm: <" + lubm + ">\n" + + "INSERT DATA\n" + + "{ GRAPH <http://updated/test> {\n" + + " lubm:Chair owl:equivalentClass [ owl:onProperty lubm:headOf ; owl:someValuesFrom lubm:Department ] ." + + "}}"; + update = conn.prepareUpdate(QueryLanguage.SPARQL, insert); + update.execute(); + log.info("Refreshing InferenceEngine"); + ((RdfCloudTripleStore) sail).getInferenceEngine().refreshGraph(); + log.info("Re-running Inference-dependent Query"); + resultHandler.resetCount(); + tupleQuery = conn.prepareTupleQuery(QueryLanguage.SPARQL, inferQuery); + tupleQuery.evaluate(resultHandler); + log.info("Result count : " + resultHandler.getCount()); + Validate.isTrue(resultHandler.getCount() == 1); + } + public static void testAllValuesFromInference(final SailRepositoryConnection conn, final Sail sail) throws MalformedQueryException, RepositoryException, UpdateExecutionException, QueryEvaluationException, TupleQueryResultHandlerException, InferenceEngineException { log.info("Adding Data"); http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/fc8d30ac/sail/src/main/java/org/apache/rya/rdftriplestore/RdfCloudTripleStoreConnection.java ---------------------------------------------------------------------- diff --git a/sail/src/main/java/org/apache/rya/rdftriplestore/RdfCloudTripleStoreConnection.java b/sail/src/main/java/org/apache/rya/rdftriplestore/RdfCloudTripleStoreConnection.java index dee5c8d..40a69b0 100644 --- a/sail/src/main/java/org/apache/rya/rdftriplestore/RdfCloudTripleStoreConnection.java +++ b/sail/src/main/java/org/apache/rya/rdftriplestore/RdfCloudTripleStoreConnection.java @@ -60,6 +60,7 @@ import org.apache.rya.rdftriplestore.inference.InverseOfVisitor; import org.apache.rya.rdftriplestore.inference.OneOfVisitor; import org.apache.rya.rdftriplestore.inference.PropertyChainVisitor; import org.apache.rya.rdftriplestore.inference.SameAsVisitor; +import org.apache.rya.rdftriplestore.inference.SomeValuesFromVisitor; import org.apache.rya.rdftriplestore.inference.SubClassOfVisitor; import org.apache.rya.rdftriplestore.inference.SubPropertyOfVisitor; import org.apache.rya.rdftriplestore.inference.SymmetricPropertyVisitor; @@ -353,6 +354,7 @@ public class RdfCloudTripleStoreConnection extends SailConnectionBase { ) { try { tupleExpr.visit(new DomainRangeVisitor(queryConf, inferenceEngine)); + tupleExpr.visit(new SomeValuesFromVisitor(queryConf, inferenceEngine)); tupleExpr.visit(new AllValuesFromVisitor(queryConf, inferenceEngine)); tupleExpr.visit(new HasValueVisitor(queryConf, inferenceEngine)); tupleExpr.visit(new IntersectionOfVisitor(queryConf, inferenceEngine)); http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/fc8d30ac/sail/src/main/java/org/apache/rya/rdftriplestore/inference/InferenceEngine.java ---------------------------------------------------------------------- diff --git a/sail/src/main/java/org/apache/rya/rdftriplestore/inference/InferenceEngine.java b/sail/src/main/java/org/apache/rya/rdftriplestore/inference/InferenceEngine.java index 936fd41..a0a5cfc 100644 --- a/sail/src/main/java/org/apache/rya/rdftriplestore/inference/InferenceEngine.java +++ b/sail/src/main/java/org/apache/rya/rdftriplestore/inference/InferenceEngine.java @@ -83,6 +83,7 @@ public class InferenceEngine { private Map<URI, Set<URI>> rangeByType; private Map<Resource, Map<URI, Value>> hasValueByType; private Map<URI, Map<Resource, Value>> hasValueByProperty; + private Map<Resource, Map<Resource, URI>> someValuesFromByRestrictionType; private Map<Resource, Map<Resource, URI>> allValuesFromByValueType; private final ConcurrentHashMap<Resource, List<Set<Resource>>> intersections = new ConcurrentHashMap<>(); private final ConcurrentHashMap<Resource, Set<Resource>> enumerations = new ConcurrentHashMap<>(); @@ -540,7 +541,7 @@ public class InferenceEngine { // p's subproperties. Would be redundant for properties discovered via this rule. while (!domainViaInverseProperty.isEmpty()) { final URI property = domainViaInverseProperty.pop(); - final Set<URI> subProperties = findParents(subPropertyOfGraph, property); + final Set<URI> subProperties = getSubProperties(property); subProperties.removeAll(propertiesWithDomain); propertiesWithDomain.addAll(subProperties); domainViaSuperProperty.addAll(subProperties); @@ -549,7 +550,7 @@ public class InferenceEngine { // p's subproperties. Would be redundant for properties discovered via this rule. while (!rangeViaInverseProperty.isEmpty()) { final URI property = rangeViaInverseProperty.pop(); - final Set<URI> subProperties = findParents(subPropertyOfGraph, property); + final Set<URI> subProperties = getSubProperties(property); subProperties.removeAll(propertiesWithRange); propertiesWithRange.addAll(subProperties); rangeViaSuperProperty.addAll(subProperties); @@ -606,6 +607,7 @@ public class InferenceEngine { } // Query for specific types of restriction and add their details to the schema refreshHasValueRestrictions(restrictions); + refreshSomeValuesFromRestrictions(restrictions); refreshAllValuesFromRestrictions(restrictions); } @@ -637,34 +639,56 @@ public class InferenceEngine { } } + private void refreshSomeValuesFromRestrictions(final Map<Resource, URI> restrictions) throws QueryEvaluationException { + someValuesFromByRestrictionType = new ConcurrentHashMap<>(); + ryaDaoQueryWrapper.queryAll(null, OWL.SOMEVALUESFROM, null, new RDFHandlerBase() { + @Override + public void handleStatement(final Statement statement) throws RDFHandlerException { + final Resource restrictionClass = statement.getSubject(); + if (restrictions.containsKey(restrictionClass) && statement.getObject() instanceof Resource) { + final URI property = restrictions.get(restrictionClass); + final Resource valueClass = (Resource) statement.getObject(); + // Should also be triggered by subclasses of the value class + final Set<Resource> valueClasses = new HashSet<>(); + valueClasses.add(valueClass); + if (valueClass instanceof URI) { + valueClasses.addAll(getSubClasses((URI) valueClass)); + } + for (final Resource valueSubClass : valueClasses) { + if (!someValuesFromByRestrictionType.containsKey(restrictionClass)) { + someValuesFromByRestrictionType.put(restrictionClass, new ConcurrentHashMap<>()); + } + someValuesFromByRestrictionType.get(restrictionClass).put(valueSubClass, property); + } + } + } + }); + } + private void refreshAllValuesFromRestrictions(final Map<Resource, URI> restrictions) throws QueryEvaluationException { - allValuesFromByValueType = new HashMap<>(); - final CloseableIteration<Statement, QueryEvaluationException> iter = RyaDAOHelper.query(ryaDAO, null, OWL.ALLVALUESFROM, null, conf); - try { - while (iter.hasNext()) { - final Statement st = iter.next(); - if (restrictions.containsKey(st.getSubject()) && st.getObject() instanceof URI) { - final URI property = restrictions.get(st.getSubject()); - final URI valueClass = (URI) st.getObject(); + allValuesFromByValueType = new ConcurrentHashMap<>(); + ryaDaoQueryWrapper.queryAll(null, OWL.ALLVALUESFROM, null, new RDFHandlerBase() { + @Override + public void handleStatement(final Statement statement) throws RDFHandlerException { + final Resource directRestrictionClass = statement.getSubject(); + if (restrictions.containsKey(directRestrictionClass) && statement.getObject() instanceof Resource) { + final URI property = restrictions.get(directRestrictionClass); + final Resource valueClass = (Resource) statement.getObject(); // Should also be triggered by subclasses of the property restriction final Set<Resource> restrictionClasses = new HashSet<>(); - restrictionClasses.add(st.getSubject()); - if (st.getSubject() instanceof URI) { - restrictionClasses.addAll(getSubClasses((URI) st.getSubject())); + restrictionClasses.add(directRestrictionClass); + if (directRestrictionClass instanceof URI) { + restrictionClasses.addAll(getSubClasses((URI) directRestrictionClass)); } for (final Resource restrictionClass : restrictionClasses) { if (!allValuesFromByValueType.containsKey(valueClass)) { - allValuesFromByValueType.put(valueClass, new HashMap<>()); + allValuesFromByValueType.put(valueClass, new ConcurrentHashMap<>()); } allValuesFromByValueType.get(valueClass).put(restrictionClass, property); } } } - } finally { - if (iter != null) { - iter.close(); - } - } + }); } private void refreshIntersectionOf() throws QueryEvaluationException { @@ -904,7 +928,8 @@ public class InferenceEngine { * internal subclass graph. * @param type the type {@link URI} to find super classes for. * @return the {@link Set} of {@link URI} types that are super classes types - * of the specified {@code type}. Returns an empty set if nothing was found. + * of the specified {@code type}. Returns an empty set if nothing was found, + * or if either type or the subclass graph is {@code null}. */ public Set<URI> getSuperClasses(final URI type) { return findChildren(subClassOfGraph, type); @@ -915,31 +940,102 @@ public class InferenceEngine { * internal subclass graph. * @param type the type {@link URI} to find sub classes for. * @return the {@link Set} of {@link URI} types that are sub classes types - * of the specified {@code type}. Returns an empty set if nothing was found. + * of the specified {@code type}. Returns an empty set if nothing was found, + * or if either type or the subclass graph is {@code null}. */ public Set<URI> getSubClasses(final URI type) { return findParents(subClassOfGraph, type); } + /** + * Returns all superproperties of the specified property based on the + * internal subproperty graph. + * @param property the property {@link URI} to find superproperties for. + * @return the {@link Set} of {@link URI} properties that are superproperties + * of the specified {@code property}. Returns an empty set if nothing was found, + * or if either property or the subproperty graph is {@code null}. + */ + public Set<URI> getSuperProperties(final URI property) { + return findChildren(subPropertyOfGraph, property); + } + + /** + * Returns all subproperties of the specified property based on the + * internal subproperty graph. + * @param property the property {@link URI} to find subproperties for. + * @return the {@link Set} of {@link URI} properties that are subproperties + * of the specified {@code property}. Returns an empty set if nothing was found, + * or if either property or the subproperty graph is {@code null}. + */ + public Set<URI> getSubProperties(final URI property) { + return findParents(subPropertyOfGraph, property); + } + + /** + * Given a graph and a node, recursively traverse the graph from that node + * to find all predecessors. + * @param graph A {@link Graph} + * @param vertexId The starting node + * @return The set of predecessors, or an empty set if none are found or if + * either argument is {@code null} + */ public static Set<URI> findParents(final Graph graph, final URI vertexId) { return findParents(graph, vertexId, true); } + /** + * Given a graph and a node, find all immediate parents and optionally + * traverse the graph recursively to find all indirect predecessors. + * @param graph A {@link Graph} + * @param vertexId The starting node + * @param isRecursive If true, traverse the graph recursively + * @return The set of predecessors, or an empty set if none are found or if + * either argument is {@code null} + */ public static Set<URI> findParents(final Graph graph, final URI vertexId, final boolean isRecursive) { return findConnected(graph, vertexId, Direction.IN, isRecursive); } + /** + * Given a graph and a node, recursively traverse the graph from that node + * to find all successors. + * @param graph A {@link Graph} + * @param vertexId The starting node + * @return The set of successors, or an empty set if none are found or if + * either argument is {@code null} + */ public static Set<URI> findChildren(final Graph graph, final URI vertexId) { return findChildren(graph, vertexId, true); } + /** + * Given a graph and a node, find all immediate children and optionally + * traverse the graph recursively to find all indirect successors. + * @param graph A {@link Graph} + * @param vertexId The starting node + * @param isRecursive If true, traverse the graph recursively + * @return The set of successors, or an empty set if none are found or if + * either argument is {@code null} + */ public static Set<URI> findChildren(final Graph graph, final URI vertexId, final boolean isRecursive) { return findConnected(graph, vertexId, Direction.OUT, isRecursive); } + /** + * Given a graph, a starting node, and a direction, find immediate neighbors + * of the start node in that direction, and optionally traverse the graph + * recursively in that same direction to find indirect connections. + * @param graph A {@link Graph} + * @param vertexId The starting node + * @param traversal Look for connected nodes in this direction + * @param isRecursive If true, recursively follow the connected nodes' own + * edges (in the same direction) + * @return The set of connected nodes, or an empty set if none are found, or + * if either the graph or the starting vertex are {@code null}. + */ private static Set<URI> findConnected(final Graph graph, final URI vertexId, final Direction traversal, final boolean isRecursive) { final Set<URI> connected = new HashSet<>(); - if (graph == null) { + if (graph == null || vertexId == null) { return connected; } final Vertex v = getVertex(graph, vertexId); @@ -1263,6 +1359,75 @@ public class InferenceEngine { } /** + * Given some schema mapping types to (type, property) pairs that somehow imply the key type, + * and given a particular type being queried for, expand the combinations of types and + * properties that can imply the query type by including any pairs that could imply subtypes of + * the query type (using the subclass graph), and by expanding each property into a set of all + * subproperties that imply it (using the subproperty graph). Does not consider subtypes of + * potential triggering types. + * @param queryType The type whose possible derivations are needed + * @param schemaMap Map of schema information such that each key represents a type that can + * somehow be derived from (other type x property) combinations, and the value provides + * those combinations that can be used for the implication. + * @return Combinations of types and properties that can directly or indirectly imply the query + * type according to the schema provided and the subclass/superproperty graphs. Any + * individual type/property combination is sufficient. Returns an empty map if either + * parameter is {@code null}. + */ + private Map<Resource, Set<URI>> getTypePropertyImplyingType(final Resource queryType, final Map<Resource, Map<Resource, URI>> schemaMap) { + final Map<Resource, Set<URI>> implications = new HashMap<>(); + if (schemaMap != null && queryType != null) { + // Check for any subtypes which would in turn imply the type being queried for + final HashSet<Resource> queryTypes = new HashSet<>(); + queryTypes.add(queryType); + if (queryType instanceof URI) { + queryTypes.addAll(getSubClasses((URI) queryType)); + } + for (final Resource querySubType : queryTypes) { + if (schemaMap.containsKey(querySubType)) { + final Map<Resource, URI> otherTypeToProperty = schemaMap.get(querySubType); + for (final Resource otherType : otherTypeToProperty.keySet()) { + if (!implications.containsKey(otherType)) { + implications.put(otherType, new HashSet<>()); + } + final URI property = otherTypeToProperty.get(otherType); + if (property != null) { + implications.get(otherType).add(property); + // Also add subproperties that would in turn imply the property + implications.get(otherType).addAll(getSubProperties(property)); + } + } + } + } + } + return implications; + } + + /** + * For a given type, return information about any owl:someValuesFrom restriction that could + * imply an individual's membership in that type: When a property restriction R applies to + * property p and states "R owl:someValuesFrom T", then whenever the object of a triple belongs + * to T, and the predicate is p, then the subject of the triple is implied to have the type R + * (it belongs to the class defined by the restriction). + * @param restrictionType The type to be inferred, which is the type of the subject of the + * triple, or the type for which all members are stated to have some value of the + * appropriate type. Takes class hierarchy into account, so possible inferences include + * any ways of inferring subtypes of the restriction type, and object types that trigger + * inference include any subtypes of relevant value types. Also considers property + * hierarchy, so properties that trigger inference will include subproperties of those + * referenced by relevant restrictions. + * @return A map from object type (the object of the someValuesFrom condition or the subtype of + * such a type) to the set of properties (including any property referenced by such a + * restriction and all of its subproperties) such that for any individual which belongs to + * the object type, any subject which has some value of that type for that property belongs + * to the restriction type. Empty map if the parameter is {@code null} or if the + * someValuesFrom schema has not been populated. + */ + public Map<Resource, Set<URI>> getSomeValuesFromByRestrictionType(Resource restrictionType) { + return getTypePropertyImplyingType(restrictionType, someValuesFromByRestrictionType); + } + + /** * For a given type, return information about any owl:allValuesFrom restriction that could imply * an individual's membership in that type: If the subject of a triple belongs to the type * associated with the restriction itself, and the predicate is the one referenced by the @@ -1276,33 +1441,11 @@ public class InferenceEngine { * @return A map from subject type (a property restriction type or a subtype of one) to the set * of properties (including any property referenced by such a restriction and all of its * subproperties) such that for any individual which belongs to the subject type, all - * values it has for any of those properties belong to the value type. + * values it has for any of those properties belong to the value type. Empty map if the + * parameter is {@code null} or if the allValuesFrom schema has not been populated. */ public Map<Resource, Set<URI>> getAllValuesFromByValueType(final Resource valueType) { - final Map<Resource, Set<URI>> implications = new HashMap<>(); - if (allValuesFromByValueType != null) { - // Check for any subtypes which would in turn imply the value type - final HashSet<Resource> valueTypes = new HashSet<>(); - valueTypes.add(valueType); - if (valueType instanceof URI) { - valueTypes.addAll(getSubClasses((URI) valueType)); - } - for (final Resource valueSubType : valueTypes) { - if (allValuesFromByValueType.containsKey(valueSubType)) { - final Map<Resource, URI> restrictionToProperty = allValuesFromByValueType.get(valueSubType); - for (final Resource restrictionType : restrictionToProperty.keySet()) { - if (!implications.containsKey(restrictionType)) { - implications.put(restrictionType, new HashSet<>()); - } - final URI property = restrictionToProperty.get(restrictionType); - implications.get(restrictionType).add(property); - // Also add subproperties that would in turn imply the property - implications.get(restrictionType).addAll(findParents(subPropertyOfGraph, property)); - } - } - } - } - return implications; + return getTypePropertyImplyingType(valueType, allValuesFromByValueType); } /** http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/fc8d30ac/sail/src/main/java/org/apache/rya/rdftriplestore/inference/SomeValuesFromVisitor.java ---------------------------------------------------------------------- diff --git a/sail/src/main/java/org/apache/rya/rdftriplestore/inference/SomeValuesFromVisitor.java b/sail/src/main/java/org/apache/rya/rdftriplestore/inference/SomeValuesFromVisitor.java new file mode 100644 index 0000000..16a315e --- /dev/null +++ b/sail/src/main/java/org/apache/rya/rdftriplestore/inference/SomeValuesFromVisitor.java @@ -0,0 +1,110 @@ +package org.apache.rya.rdftriplestore.inference; +/* + * 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. + */ + +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import org.apache.rya.api.RdfCloudTripleStoreConfiguration; +import org.apache.rya.api.utils.NullableStatementImpl; +import org.apache.rya.rdftriplestore.utils.FixedStatementPattern; +import org.openrdf.model.Resource; +import org.openrdf.model.URI; +import org.openrdf.model.vocabulary.OWL; +import org.openrdf.model.vocabulary.RDF; +import org.openrdf.query.algebra.StatementPattern; +import org.openrdf.query.algebra.Var; + +/** + * Expands the query tree to account for any existential class expressions (property restrictions + * using owl:someValuesFrom) in the ontology known to the {@link InferenceEngine}. + * + * Operates on {@link StatementPattern} nodes whose predicate is rdf:type and whose object is a + * defined type (not a variable) which corresponds to a someValuesFrom expression in the ontology. + * When applicable, replaces the node with a union of itself and a subtree that matches any instance + * that can be inferred to have the type in question via the semantics of owl:someValuesFrom. + * + * An existential class expression references a predicate and a value class, and represents the set + * of individuals with at least one value of that class for that predicate. Therefore, membership + * in the class expression should be inferred for any individual which is the subject of a triple + * with that predicate and with an object belonging to the value type. This implication is similar + * to rdfs:domain except that it only applies when the object of the triple belongs to a specific + * type. + * + * (Note: The inference in the other direction would be that, if an individual is declared to belong + * to the class expression, then there exists some other individual which satisfies the requirement + * that there is at least one value of the appropriate type. However, this other individual may be + * any arbitrary resource, explicitly represented in the data or otherwise, so this implication is + * not used.) + */ +public class SomeValuesFromVisitor extends AbstractInferVisitor { + /** + * Creates a new {@link SomeValuesFromVisitor}. + * @param conf The {@link RdfCloudTripleStoreConfiguration}. + * @param inferenceEngine The InferenceEngine containing the relevant ontology. + */ + public SomeValuesFromVisitor(RdfCloudTripleStoreConfiguration conf, InferenceEngine inferenceEngine) { + super(conf, inferenceEngine); + include = conf.isInferSomeValuesFrom(); + } + + /** + * Checks whether the StatementPattern is a type query whose solutions could be inferred by + * someValuesFrom inference, and if so, replaces the node with a union of itself and any + * possible inference. + */ + @Override + protected void meetSP(StatementPattern node) throws Exception { + final Var subjVar = node.getSubjectVar(); + final Var predVar = node.getPredicateVar(); + final Var objVar = node.getObjectVar(); + // Only applies to type queries where the type is defined + if (predVar != null && RDF.TYPE.equals(predVar.getValue()) && objVar != null && objVar.getValue() instanceof Resource) { + final Resource typeToInfer = (Resource) objVar.getValue(); + Map<Resource, Set<URI>> relevantSvfRestrictions = inferenceEngine.getSomeValuesFromByRestrictionType(typeToInfer); + if (!relevantSvfRestrictions.isEmpty()) { + // We can infer the queried type if it is to a someValuesFrom restriction (or a + // supertype of one), and the node in question (subjVar) is the subject of a triple + // whose predicate is the restriction's property and whose object is an arbitrary + // node of the restriction's value type. + final Var valueTypeVar = new Var("t-" + UUID.randomUUID()); + final Var svfPredVar = new Var("p-" + UUID.randomUUID()); + final Var neighborVar = new Var("n-" + UUID.randomUUID()); + neighborVar.setAnonymous(true); + final StatementPattern membershipPattern = new DoNotExpandSP(neighborVar, + new Var(RDF.TYPE.stringValue(), RDF.TYPE), valueTypeVar); + final StatementPattern valuePattern = new StatementPattern(subjVar, svfPredVar, neighborVar); + final InferJoin svfPattern = new InferJoin(membershipPattern, valuePattern); + // Use a FixedStatementPattern to contain the appropriate (predicate, value type) + // pairs, and check each one against the general pattern. + final FixedStatementPattern svfPropertyTypes = new FixedStatementPattern(svfPredVar, + new Var(OWL.SOMEVALUESFROM.stringValue(), OWL.SOMEVALUESFROM), valueTypeVar); + for (Resource svfValueType : relevantSvfRestrictions.keySet()) { + for (URI svfProperty : relevantSvfRestrictions.get(svfValueType)) { + svfPropertyTypes.statements.add(new NullableStatementImpl(svfProperty, + OWL.SOMEVALUESFROM, svfValueType)); + } + } + final InferJoin svfInferenceQuery = new InferJoin(svfPropertyTypes, svfPattern); + node.replaceWith(new InferUnion(node.clone(), svfInferenceQuery)); + } + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/fc8d30ac/sail/src/test/java/org/apache/rya/rdftriplestore/inference/AllValuesFromVisitorTest.java ---------------------------------------------------------------------- diff --git a/sail/src/test/java/org/apache/rya/rdftriplestore/inference/AllValuesFromVisitorTest.java b/sail/src/test/java/org/apache/rya/rdftriplestore/inference/AllValuesFromVisitorTest.java index d284d57..d239577 100644 --- a/sail/src/test/java/org/apache/rya/rdftriplestore/inference/AllValuesFromVisitorTest.java +++ b/sail/src/test/java/org/apache/rya/rdftriplestore/inference/AllValuesFromVisitorTest.java @@ -108,11 +108,11 @@ public class AllValuesFromVisitorTest { Assert.assertTrue(left instanceof StatementPattern); Assert.assertTrue(right instanceof StatementPattern); // Verify expected predicate/restriction pairs + Assert.assertTrue(fsp.statements.contains(new NullableStatementImpl(parentsArePeople, OWL.ONPROPERTY, parent))); + Assert.assertTrue(fsp.statements.contains(new NullableStatementImpl(relativesArePeople, OWL.ONPROPERTY, relative))); + Assert.assertTrue(fsp.statements.contains(new NullableStatementImpl(relativesArePeople, OWL.ONPROPERTY, parent))); + Assert.assertTrue(fsp.statements.contains(new NullableStatementImpl(parentsAreTallPeople, OWL.ONPROPERTY, parent))); Assert.assertEquals(4, fsp.statements.size()); - fsp.statements.contains(new NullableStatementImpl(parentsArePeople, OWL.ONPROPERTY, parent)); - fsp.statements.contains(new NullableStatementImpl(relativesArePeople, OWL.ONPROPERTY, relative)); - fsp.statements.contains(new NullableStatementImpl(relativesArePeople, OWL.ONPROPERTY, parent)); - fsp.statements.contains(new NullableStatementImpl(parentsAreTallPeople, OWL.ONPROPERTY, parent)); // Verify general pattern for matching instances of each pair: Join on unknown subject; left // triple states it belongs to the restriction while right triple relates it to the original // subject variable by the relevant property. Restriction and property variables are given http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/fc8d30ac/sail/src/test/java/org/apache/rya/rdftriplestore/inference/InferenceEngineTest.java ---------------------------------------------------------------------- diff --git a/sail/src/test/java/org/apache/rya/rdftriplestore/inference/InferenceEngineTest.java b/sail/src/test/java/org/apache/rya/rdftriplestore/inference/InferenceEngineTest.java index f290324..7ef56c5 100644 --- a/sail/src/test/java/org/apache/rya/rdftriplestore/inference/InferenceEngineTest.java +++ b/sail/src/test/java/org/apache/rya/rdftriplestore/inference/InferenceEngineTest.java @@ -227,6 +227,48 @@ public class InferenceEngineTest extends TestCase { } @Test + public void testSomeValuesFrom() throws Exception { + final String insert = "INSERT DATA { GRAPH <http://updated/test> {\n" + // base restrictions + + " <urn:Chair> owl:onProperty <urn:headOf> ; owl:someValuesFrom <urn:Department> .\n" + + " <urn:Dean> owl:onProperty <urn:headOf> ; owl:someValuesFrom <urn:College> .\n" + // classes related to the restriction type + + " <urn:ScienceDepartmentChair> rdfs:subClassOf <urn:Chair> .\n" + + " <urn:Chair> rdfs:subClassOf <urn:Person> .\n" + + " <urn:Dean> rdfs:subClassOf <urn:Person> .\n" + + " <urn:Student> rdfs:subClassOf <urn:Person> .\n" + // classes related to the value type + + " <urn:ScienceDepartment> rdfs:subClassOf <urn:Department> .\n" + + " <urn:HumanitiesDepartment> rdfs:subClassOf <urn:Department> .\n" + + " <urn:Department> rdfs:subClassOf <urn:Organization> .\n" + + " <urn:College> rdfs:subClassOf <urn:Organization> .\n" + // properties related to the restriction property + + " <urn:temporaryHeadOf> rdfs:subPropertyOf <urn:headOf> .\n" + + " <urn:headOf> rdfs:subPropertyOf <urn:worksFor> .\n" + + "}}"; + conn.prepareUpdate(QueryLanguage.SPARQL, insert).execute(); + inferenceEngine.refreshGraph(); + final Set<URI> properties = new HashSet<>(); + properties.add(vf.createURI("urn:headOf")); + properties.add(vf.createURI("urn:temporaryHeadOf")); + final Map<Resource, Set<URI>> chairDerivations = new HashMap<>(); + chairDerivations.put(vf.createURI("urn:Department"), properties); + chairDerivations.put(vf.createURI("urn:ScienceDepartment"), properties); + chairDerivations.put(vf.createURI("urn:HumanitiesDepartment"), properties); + final Map<Resource, Set<URI>> deanDerivations = new HashMap<>(); + deanDerivations.put(vf.createURI("urn:College"), properties); + final Map<Resource, Set<URI>> combinedDerivations = new HashMap<>(chairDerivations); + combinedDerivations.put(vf.createURI("urn:College"), properties); + // Get someValuesFrom restrictions given the direct types + Assert.assertEquals(deanDerivations, inferenceEngine.getSomeValuesFromByRestrictionType(vf.createURI("urn:Dean"))); + Assert.assertEquals(chairDerivations, inferenceEngine.getSomeValuesFromByRestrictionType(vf.createURI("urn:Chair"))); + // Finds the subtype's restrictions given the supertype + Assert.assertEquals(combinedDerivations, inferenceEngine.getSomeValuesFromByRestrictionType(vf.createURI("urn:Person"))); + // Finds nothing if given a subtype which is not a restriction + Assert.assertEquals(new HashMap<>(), inferenceEngine.getSomeValuesFromByRestrictionType(vf.createURI("urn:ScienceDepartmentChair"))); + } + + @Test public void testAllValuesFrom() throws Exception { final String insert = "INSERT DATA { GRAPH <http://updated/test> {\n" + " <urn:Dog> owl:onProperty <urn:relative> ; owl:allValuesFrom <urn:Dog> .\n" http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/fc8d30ac/sail/src/test/java/org/apache/rya/rdftriplestore/inference/InferenceIT.java ---------------------------------------------------------------------- diff --git a/sail/src/test/java/org/apache/rya/rdftriplestore/inference/InferenceIT.java b/sail/src/test/java/org/apache/rya/rdftriplestore/inference/InferenceIT.java index 1fcfa2c..11dfeb0 100644 --- a/sail/src/test/java/org/apache/rya/rdftriplestore/inference/InferenceIT.java +++ b/sail/src/test/java/org/apache/rya/rdftriplestore/inference/InferenceIT.java @@ -18,9 +18,11 @@ package org.apache.rya.rdftriplestore.inference; * under the License. */ +import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Set; import org.apache.accumulo.core.client.Connector; @@ -48,6 +50,8 @@ import org.openrdf.repository.sail.SailRepositoryConnection; import junit.framework.TestCase; public class InferenceIT extends TestCase { + private final static String LUBM = "http://swat.cse.lehigh.edu/onto/univ-bench.owl#"; + private Connector connector; private AccumuloRyaDAO dao; private final ValueFactory vf = new ValueFactoryImpl(); @@ -148,7 +152,7 @@ public class InferenceIT extends TestCase { @Test public void testDomainRangeQuery() throws Exception { - final String ontology = "PREFIX lubm: <http://swat.lehigh.edu/onto/univ-bench.owl#>\n" + final String ontology = "PREFIX lubm: <" + LUBM + ">\n" + "INSERT DATA {\n" + " lubm:advisor rdfs:domain lubm:Person ;\n" + " rdfs:range lubm:Professor ;" @@ -162,7 +166,7 @@ public class InferenceIT extends TestCase { + " lubm:Faculty rdfs:subClassOf lubm:Person .\n" + " lubm:Student rdfs:subClassOf lubm:Person .\n" + "}"; - final String instances = "PREFIX lubm: <http://swat.lehigh.edu/onto/univ-bench.owl#>\n" + final String instances = "PREFIX lubm: <" + LUBM + ">\n" + "INSERT DATA {\n" + " <urn:Professor1> a lubm:Professor .\n" + " <urn:Student1> a lubm:Student .\n" @@ -172,7 +176,7 @@ public class InferenceIT extends TestCase { + " <urn:Professor4> lubm:teachesCourse <urn:CS100> .\n" + " <urn:Student1> lubm:takesCourse <urn:CS100> .\n" + "}"; - final String query = "SELECT ?x { ?x a <http://swat.lehigh.edu/onto/univ-bench.owl#Faculty> }"; + final String query = "SELECT ?x { ?x a <" + LUBM + "Faculty> }"; conn.prepareUpdate(QueryLanguage.SPARQL, ontology).execute(); inferenceEngine.refreshGraph(); conn.prepareUpdate(QueryLanguage.SPARQL, instances).execute(); @@ -191,6 +195,64 @@ public class InferenceIT extends TestCase { } @Test + public void testSomeValuesFromQuery() throws Exception { + final String ontology = "PREFIX lubm: <" + LUBM + ">\n" + + "INSERT DATA { GRAPH <http://updated/test> {\n" + + " lubm:Chair rdfs:subClassOf lubm:Professor; \n" + + " a owl:Restriction ;\n" + + " owl:onProperty lubm:headOf ;\n" + + " owl:someValuesFrom lubm:Department .\n" + + " lubm:Dean rdfs:subClassOf lubm:Professor; \n" + + " a owl:Restriction ;\n" + + " owl:onProperty lubm:headOf ;\n" + + " owl:someValuesFrom lubm:College .\n" + + " lubm:Student rdfs:subClassOf lubm:Person ;\n" + + " a owl:Restriction ;\n" + + " owl:onProperty lubm:takesCourse ;\n" + + " owl:someValuesFrom lubm:Course .\n" + + " lubm:GraduateStudent rdfs:subClassOf lubm:Student; \n" + + " a owl:Restriction ;\n" + + " owl:onProperty lubm:takesCourse ;\n" + + " owl:someValuesFrom lubm:GraduateCourse .\n" + + " lubm:Professor rdfs:subClassOf lubm:Person .\n" + + " lubm:headOf rdfs:subPropertyOf lubm:worksFor .\n" + + " <urn:passesCourse> rdfs:subPropertyOf lubm:takesCourse ." + + "}}"; + final String instances = "PREFIX lubm: <" + LUBM + ">\n" + + "INSERT DATA { GRAPH <http://updated/test> {\n" + + " <urn:CS101> a <urn:UndergraduateCourse> .\n" + + " <urn:CS301> a lubm:Course .\n" + + " <urn:CS501> a lubm:GraduateCourse .\n" + // valid ways of inferring Student (including via GraduateStudent): + + " <urn:Alice> lubm:takesCourse <urn:CS301>, <urn:CS501> .\n" + + " <urn:Bob> <urn:passesCourse> [ a lubm:GraduateCourse ] .\n" + + " <urn:Carol> a lubm:GraduateStudent; lubm:takesCourse <urn:CS301> .\n" + // similar patterns that don't match the appropriate restrictions: + + " <urn:Dan> lubm:takesCourse <urn:CS101> .\n" + + " <urn:Eve> lubm:headOf [ a lubm:Department ] .\n" + + " <urn:Frank> lubm:headOf [ a lubm:College ] .\n" + + "}}"; + final String query = "SELECT ?individual { GRAPH <http://updated/test> {\n" + + " ?individual a <" + LUBM + "Student>\n" + + "}} \n"; + // Query should match student and graduate student restrictions, but not the others + conn.prepareUpdate(QueryLanguage.SPARQL, ontology).execute(); + inferenceEngine.refreshGraph(); + conn.prepareUpdate(QueryLanguage.SPARQL, instances).execute(); + conn.prepareTupleQuery(QueryLanguage.SPARQL, query).evaluate(resultHandler); + Map<Value, Integer> expected = new HashMap<>(); + expected.put(vf.createURI("urn:Alice"), 2); // from both courses + expected.put(vf.createURI("urn:Bob"), 1); // from course + expected.put(vf.createURI("urn:Carol"), 2); // from course and explicit type + Map<Value, Integer> returned = new HashMap<>(); + for (BindingSet bs : solutions) { + Value v = bs.getBinding("individual").getValue(); + returned.put(v, returned.getOrDefault(v, 0) + 1); + } + Assert.assertEquals(expected, returned); + } + + @Test public void testAllValuesFromQuery() throws Exception { final String ontology = "INSERT DATA { GRAPH <http://updated/test> {\n" + " <urn:Cairn_Terrier> rdfs:subClassOf <urn:Terrier> .\n" http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/fc8d30ac/sail/src/test/java/org/apache/rya/rdftriplestore/inference/SomeValuesFromVisitorTest.java ---------------------------------------------------------------------- diff --git a/sail/src/test/java/org/apache/rya/rdftriplestore/inference/SomeValuesFromVisitorTest.java b/sail/src/test/java/org/apache/rya/rdftriplestore/inference/SomeValuesFromVisitorTest.java new file mode 100644 index 0000000..013e535 --- /dev/null +++ b/sail/src/test/java/org/apache/rya/rdftriplestore/inference/SomeValuesFromVisitorTest.java @@ -0,0 +1,152 @@ +package org.apache.rya.rdftriplestore.inference; +/* + * 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. + */ + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.apache.rya.accumulo.AccumuloRdfConfiguration; +import org.apache.rya.api.utils.NullableStatementImpl; +import org.apache.rya.rdftriplestore.utils.FixedStatementPattern; +import org.junit.Assert; +import org.junit.Test; +import org.openrdf.model.Resource; +import org.openrdf.model.URI; +import org.openrdf.model.ValueFactory; +import org.openrdf.model.impl.ValueFactoryImpl; +import org.openrdf.model.vocabulary.OWL; +import org.openrdf.model.vocabulary.RDF; +import org.openrdf.query.algebra.Join; +import org.openrdf.query.algebra.Projection; +import org.openrdf.query.algebra.ProjectionElem; +import org.openrdf.query.algebra.ProjectionElemList; +import org.openrdf.query.algebra.StatementPattern; +import org.openrdf.query.algebra.TupleExpr; +import org.openrdf.query.algebra.Union; +import org.openrdf.query.algebra.Var; + +import com.google.common.collect.Sets; + +public class SomeValuesFromVisitorTest { + private static final AccumuloRdfConfiguration conf = new AccumuloRdfConfiguration(); + private static final ValueFactory vf = new ValueFactoryImpl(); + + // Value types + private final URI course = vf.createURI("lubm:Course"); + private final URI gradCourse = vf.createURI("lubm:GraduateCourse"); + private final URI department = vf.createURI("lubm:Department"); + private final URI organization = vf.createURI("lubm:Organization"); + // Predicates + private final URI takesCourse = vf.createURI("lubm:takesCourse"); + private final URI headOf = vf.createURI("lubm:headOf"); + private final URI worksFor = vf.createURI("lubm:worksFor"); + // Supertype of restriction types + private final URI person = vf.createURI("lubm:Person"); + + @Test + public void testSomeValuesFrom() throws Exception { + // Configure a mock instance engine with an ontology: + final InferenceEngine inferenceEngine = mock(InferenceEngine.class); + Map<Resource, Set<URI>> personSVF = new HashMap<>(); + personSVF.put(gradCourse, Sets.newHashSet(takesCourse)); + personSVF.put(course, Sets.newHashSet(takesCourse)); + personSVF.put(department, Sets.newHashSet(headOf)); + personSVF.put(organization, Sets.newHashSet(worksFor, headOf)); + when(inferenceEngine.getSomeValuesFromByRestrictionType(person)).thenReturn(personSVF); + // Query for a specific type and rewrite using the visitor: + StatementPattern originalSP = new StatementPattern(new Var("s"), new Var("p", RDF.TYPE), new Var("o", person)); + final Projection query = new Projection(originalSP, new ProjectionElemList(new ProjectionElem("s", "subject"))); + query.visit(new SomeValuesFromVisitor(conf, inferenceEngine)); + // Expected structure: a union of two elements: one is equal to the original statement + // pattern, and the other one joins a list of predicate/value type combinations + // with another join querying for any nodes who are the subject of a triple with that + // predicate and with an object of that type. + // + // Union( + // SP(?node a :impliedType), + // Join( + // FSP(<?property someValuesFrom ?valueType> { + // takesCourse/Course; + // takesCourse/GraduateCourse; + // headOf/Department; + // headOf/Organization; + // worksFor/Organization; + // }), + // Join( + // SP(_:object a ?valueType), + // SP(?node ?property _:object) + // ) + // ) + Assert.assertTrue(query.getArg() instanceof Union); + TupleExpr left = ((Union) query.getArg()).getLeftArg(); + TupleExpr right = ((Union) query.getArg()).getRightArg(); + Assert.assertEquals(originalSP, left); + Assert.assertTrue(right instanceof Join); + final Join join = (Join) right; + Assert.assertTrue(join.getLeftArg() instanceof FixedStatementPattern); + Assert.assertTrue(join.getRightArg() instanceof Join); + FixedStatementPattern fsp = (FixedStatementPattern) join.getLeftArg(); + left = ((Join) join.getRightArg()).getLeftArg(); + right = ((Join) join.getRightArg()).getRightArg(); + Assert.assertTrue(left instanceof StatementPattern); + Assert.assertTrue(right instanceof StatementPattern); + // Verify expected predicate/type pairs + Assert.assertTrue(fsp.statements.contains(new NullableStatementImpl(takesCourse, OWL.SOMEVALUESFROM, course))); + Assert.assertTrue(fsp.statements.contains(new NullableStatementImpl(takesCourse, OWL.SOMEVALUESFROM, gradCourse))); + Assert.assertTrue(fsp.statements.contains(new NullableStatementImpl(headOf, OWL.SOMEVALUESFROM, department))); + Assert.assertTrue(fsp.statements.contains(new NullableStatementImpl(headOf, OWL.SOMEVALUESFROM, organization))); + Assert.assertTrue(fsp.statements.contains(new NullableStatementImpl(worksFor, OWL.SOMEVALUESFROM, organization))); + Assert.assertEquals(5, fsp.statements.size()); + // Verify pattern for matching instances of each pair: a Join of <_:x rdf:type ?t> and + // <?s ?p _:x> where p and t are the predicate/type pair and s is the original subject + // variable. + StatementPattern leftSP = (StatementPattern) left; + StatementPattern rightSP = (StatementPattern) right; + Assert.assertEquals(rightSP.getObjectVar(), leftSP.getSubjectVar()); + Assert.assertEquals(RDF.TYPE, leftSP.getPredicateVar().getValue()); + Assert.assertEquals(fsp.getObjectVar(), leftSP.getObjectVar()); + Assert.assertEquals(originalSP.getSubjectVar(), rightSP.getSubjectVar()); + Assert.assertEquals(fsp.getSubjectVar(), rightSP.getPredicateVar()); + } + + @Test + public void testSomeValuesFromDisabled() throws Exception { + // Disable someValuesOf inference + final AccumuloRdfConfiguration disabledConf = conf.clone(); + disabledConf.setInferSomeValuesFrom(false); + // Configure a mock instance engine with an ontology: + final InferenceEngine inferenceEngine = mock(InferenceEngine.class); + Map<Resource, Set<URI>> personSVF = new HashMap<>(); + personSVF.put(gradCourse, Sets.newHashSet(takesCourse)); + personSVF.put(course, Sets.newHashSet(takesCourse)); + personSVF.put(department, Sets.newHashSet(headOf)); + personSVF.put(organization, Sets.newHashSet(worksFor, headOf)); + when(inferenceEngine.getSomeValuesFromByRestrictionType(person)).thenReturn(personSVF); + // Query for a specific type visit -- should not change + StatementPattern originalSP = new StatementPattern(new Var("s"), new Var("p", RDF.TYPE), new Var("o", person)); + final Projection originalQuery = new Projection(originalSP, new ProjectionElemList(new ProjectionElem("s", "subject"))); + final Projection modifiedQuery = originalQuery.clone(); + modifiedQuery.visit(new SomeValuesFromVisitor(disabledConf, inferenceEngine)); + Assert.assertEquals(originalQuery, modifiedQuery); + } +}
