Repository: incubator-rya Updated Branches: refs/heads/master e9488ff69 -> 8431dfb78
RYA-300 Added owl:oneOf inference. Closes #215 Project: http://git-wip-us.apache.org/repos/asf/incubator-rya/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-rya/commit/8431dfb7 Tree: http://git-wip-us.apache.org/repos/asf/incubator-rya/tree/8431dfb7 Diff: http://git-wip-us.apache.org/repos/asf/incubator-rya/diff/8431dfb7 Branch: refs/heads/master Commit: 8431dfb783bed4e6e5d434ec5decccceccefffd1 Parents: e9488ff Author: eric.white <[email protected]> Authored: Wed Aug 23 11:08:58 2017 -0400 Committer: pujav65 <[email protected]> Committed: Thu Aug 24 18:27:52 2017 -0400 ---------------------------------------------------------------------- .../api/RdfCloudTripleStoreConfiguration.java | 20 +++ .../src/main/java/MongoRyaDirectExample.java | 105 +++++++++++ .../RdfCloudTripleStoreConnection.java | 2 + .../inference/InferenceEngine.java | 76 ++++++++ .../rdftriplestore/inference/OneOfVisitor.java | 77 +++++++++ .../inference/InferenceEngineTest.java | 84 +++++++++ .../rdftriplestore/inference/InferenceIT.java | 99 ++++++++++- .../inference/OneOfVisitorTest.java | 172 +++++++++++++++++++ 8 files changed, 633 insertions(+), 2 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/8431dfb7/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 ee91e6b..eeb49b5 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 @@ -74,6 +74,7 @@ public abstract class RdfCloudTripleStoreConfiguration extends Configuration { public static final String STATS_PUSH_EMPTY_RDFTYPE_DOWN = "conf.stats.rdftype.down"; 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_SUBCLASSOF = "infer.include.subclassof"; public static final String INFER_INCLUDE_SUBPROPOF = "infer.include.subpropof"; public static final String INFER_INCLUDE_SYMMPROP = "infer.include.symmprop"; @@ -358,6 +359,25 @@ public abstract class RdfCloudTripleStoreConfiguration extends Configuration { setBoolean(INFER_INCLUDE_INVERSEOF, val); } + /** + * @return {@code true} if owl:oneOf inferencing is enabled. + * {@code false} otherwise. Defaults to {@code true} if nothing is + * specified. + */ + public Boolean isInferOneOf() { + return getBoolean(INFER_INCLUDE_ONE_OF, true); + } + + /** + * Sets whether owl:oneOf inferencing is enabled or disabled. + * @param value {@code true} if owl:oneOf inferencing is enabled. + * {@code false} otherwise. + */ + public void setInferOneOf(final Boolean value) { + Preconditions.checkNotNull(value); + setBoolean(INFER_INCLUDE_ONE_OF, value); + } + public Boolean isInferSubClassOf() { return getBoolean(INFER_INCLUDE_SUBCLASSOF, true); } http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/8431dfb7/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 342916a..a2611d0 100644 --- a/extras/indexingExample/src/main/java/MongoRyaDirectExample.java +++ b/extras/indexingExample/src/main/java/MongoRyaDirectExample.java @@ -120,6 +120,7 @@ public class MongoRyaDirectExample { testPropertyChainInferenceAltRepresentation(conn, sail); testAllValuesFromInference(conn, sail); testIntersectionOfInference(conn, sail); + testOneOfInference(conn, sail); } log.info("TIME: " + (System.currentTimeMillis() - start) / 1000.); @@ -572,6 +573,110 @@ public class MongoRyaDirectExample { Validate.isTrue(resultHandler.getCount() == 2); } + public static void testOneOfInference(final SailRepositoryConnection conn, final Sail sail) throws MalformedQueryException, RepositoryException, UpdateExecutionException, QueryEvaluationException, TupleQueryResultHandlerException, InferenceEngineException { + log.info("Adding Data"); + final String instances = "INSERT DATA" + + "{ GRAPH <http://updated/test> {\n" + + " <urn:FlopCard1> a <urn:Card> . \n" + + " <urn:FlopCard1> <urn:HasRank> <urn:Ace> . \n" + + " <urn:FlopCard1> <urn:HasSuit> <urn:Diamonds> . \n" + + " <urn:FlopCard2> a <urn:Card> . \n" + + " <urn:FlopCard2> <urn:HasRank> <urn:Ace> . \n" + + " <urn:FlopCard2> <urn:HasSuit> <urn:Hearts> . \n" + + " <urn:FlopCard3> a <urn:Card> . \n" + + " <urn:FlopCard3> <urn:HasRank> <urn:King> . \n" + + " <urn:FlopCard3> <urn:HasSuit> <urn:Spades> . \n" + + " <urn:TurnCard> a <urn:Card> . \n" + + " <urn:TurnCard> <urn:HasRank> <urn:10> . \n" + + " <urn:TurnCard> <urn:HasSuit> <urn:Clubs> . \n" + + " <urn:RiverCard> a <urn:Card> . \n" + + " <urn:RiverCard> <urn:HasRank> <urn:Queen> . \n" + + " <urn:RiverCard> <urn:HasSuit> <urn:Hearts> . \n" + + "}}"; + Update update = conn.prepareUpdate(QueryLanguage.SPARQL, instances); + update.execute(); + final String explicitQuery = "select distinct ?card { GRAPH <http://updated/test> {\n" + + " ?card a <urn:Card> . \n" + + " VALUES ?suit { <urn:Clubs> <urn:Diamonds> <urn:Hearts> <urn:Spades> } . \n" + + " ?card <urn:HasSuit> ?suit . \n" + + "}}"; + log.info("Running Explicit Query"); + CountingResultHandler resultHandler = new CountingResultHandler(); + TupleQuery tupleQuery = conn.prepareTupleQuery(QueryLanguage.SPARQL, explicitQuery); + tupleQuery.evaluate(resultHandler); + log.info("Result count : " + resultHandler.getCount()); + Validate.isTrue(resultHandler.getCount() == 5); + log.info("Adding owl:oneOf Schema"); + // ONTOLOGY - :Suits oneOf (:Clubs, :Diamonds, :Hearts, :Spades) + // ONTOLOGY - :Ranks oneOf (:Ace, :1, :2, :3, :4, :5, :6, :7, :8, :9, :10, :Jack, :Queen, :King) + final String ontology = "INSERT DATA { GRAPH <http://updated/test> {\n" + + " <urn:Suits> owl:oneOf _:bnodeS1 . \n" + + " _:bnodeS1 rdf:first <urn:Clubs> . \n" + + " _:bnodeS1 rdf:rest _:bnodeS2 . \n" + + " _:bnodeS2 rdf:first <urn:Diamonds> . \n" + + " _:bnodeS2 rdf:rest _:bnodeS3 . \n" + + " _:bnodeS3 rdf:first <urn:Hearts> . \n" + + " _:bnodeS3 rdf:rest _:bnodeS4 . \n" + + " _:bnodeS4 rdf:first <urn:Spades> . \n" + + " _:bnodeS4 rdf:rest rdf:nil . \n" + + " <urn:Ranks> owl:oneOf _:bnodeR1 . \n" + + " _:bnodeR1 rdf:first <urn:Ace> . \n" + + " _:bnodeR1 rdf:rest _:bnodeR2 . \n" + + " _:bnodeR2 rdf:first <urn:2> . \n" + + " _:bnodeR2 rdf:rest _:bnodeR3 . \n" + + " _:bnodeR3 rdf:first <urn:3> . \n" + + " _:bnodeR3 rdf:rest _:bnodeR4 . \n" + + " _:bnodeR4 rdf:first <urn:4> . \n" + + " _:bnodeR4 rdf:rest _:bnodeR5 . \n" + + " _:bnodeR5 rdf:first <urn:5> . \n" + + " _:bnodeR5 rdf:rest _:bnodeR6 . \n" + + " _:bnodeR6 rdf:first <urn:6> . \n" + + " _:bnodeR6 rdf:rest _:bnodeR7 . \n" + + " _:bnodeR7 rdf:first <urn:7> . \n" + + " _:bnodeR7 rdf:rest _:bnodeR8 . \n" + + " _:bnodeR8 rdf:first <urn:8> . \n" + + " _:bnodeR8 rdf:rest _:bnodeR9 . \n" + + " _:bnodeR9 rdf:first <urn:9> . \n" + + " _:bnodeR9 rdf:rest _:bnodeR10 . \n" + + " _:bnodeR10 rdf:first <urn:10> . \n" + + " _:bnodeR10 rdf:rest _:bnodeR11 . \n" + + " _:bnodeR11 rdf:first <urn:Jack> . \n" + + " _:bnodeR11 rdf:rest _:bnodeR12 . \n" + + " _:bnodeR12 rdf:first <urn:Queen> . \n" + + " _:bnodeR12 rdf:rest _:bnodeR13 . \n" + + " _:bnodeR13 rdf:first <urn:King> . \n" + + " _:bnodeR13 rdf:rest rdf:nil . \n" + + " <urn:Card> owl:intersectionOf (\n" + + " [ owl:onProperty <urn:HasRank> ; owl:someValuesFrom <urn:Ranks> ]\n" + + " [ owl:onProperty <urn:HasSuit> ; owl:someValuesFrom <urn:Suits> ]\n" + + " ) . \n" + + " <urn:HasRank> owl:range <urn:Ranks> . \n" + + " <urn:HasSuit> owl:range <urn:Suits> . \n" + + "}}"; + update = conn.prepareUpdate(QueryLanguage.SPARQL, ontology); + update.execute(); + log.info("Running Inference-dependent Query without refreshing InferenceEngine"); + resultHandler.resetCount(); + final String inferQuery = "select distinct ?card { GRAPH <http://updated/test> {\n" + + " ?card a <urn:Card> . \n" + + " ?suit a <urn:Suits> . \n" + + " ?card <urn:HasSuit> ?suit . \n" + + "}}"; + tupleQuery = conn.prepareTupleQuery(QueryLanguage.SPARQL, inferQuery); + tupleQuery.evaluate(resultHandler); + log.info("Result count : " + resultHandler.getCount()); + Validate.isTrue(resultHandler.getCount() == 0); + log.info("Refreshing InferenceEngine"); + ((RdfCloudTripleStore) sail).getInferenceEngine().refreshGraph(); + log.info("Re-running Inference-dependent Query"); + resultHandler.resetCount(); + resultHandler = new CountingResultHandler(); + tupleQuery = conn.prepareTupleQuery(QueryLanguage.SPARQL, inferQuery); + tupleQuery.evaluate(resultHandler); + log.info("Result count : " + resultHandler.getCount()); + Validate.isTrue(resultHandler.getCount() == 5); + } + public static void testInfer(final SailRepositoryConnection conn, final Sail sail) throws MalformedQueryException, RepositoryException, UpdateExecutionException, QueryEvaluationException, TupleQueryResultHandlerException, InferenceEngineException { http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/8431dfb7/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 a64c0ec..dee5c8d 100644 --- a/sail/src/main/java/org/apache/rya/rdftriplestore/RdfCloudTripleStoreConnection.java +++ b/sail/src/main/java/org/apache/rya/rdftriplestore/RdfCloudTripleStoreConnection.java @@ -57,6 +57,7 @@ import org.apache.rya.rdftriplestore.inference.HasValueVisitor; import org.apache.rya.rdftriplestore.inference.InferenceEngine; import org.apache.rya.rdftriplestore.inference.IntersectionOfVisitor; 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.SubClassOfVisitor; @@ -362,6 +363,7 @@ public class RdfCloudTripleStoreConnection extends SailConnectionBase { tupleExpr.visit(new SubPropertyOfVisitor(queryConf, inferenceEngine)); tupleExpr.visit(new SubClassOfVisitor(queryConf, inferenceEngine)); tupleExpr.visit(new SameAsVisitor(queryConf, inferenceEngine)); + tupleExpr.visit(new OneOfVisitor(queryConf, inferenceEngine)); } catch (final Exception e) { logger.error("Error encountered while visiting query node.", e); } http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/8431dfb7/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 a2f8d63..936fd41 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 @@ -85,6 +85,7 @@ public class InferenceEngine { private Map<URI, Map<Resource, Value>> hasValueByProperty; 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<>(); private RyaDAO<?> ryaDAO; private RdfCloudTripleStoreConfiguration conf; @@ -203,6 +204,8 @@ public class InferenceEngine { refreshIntersectionOf(); + refreshOneOf(); + iter = RyaDAOHelper.query(ryaDAO, null, RDF.TYPE, OWL.SYMMETRICPROPERTY, conf); final Set<URI> symProp = new HashSet<>(); try { @@ -761,6 +764,46 @@ public class InferenceEngine { } } + private void refreshOneOf() throws QueryEvaluationException { + final Map<Resource, Set<Resource>> enumTypes = new HashMap<>(); + + // First query for all the owl:oneOf's. + // If we have the following oneOf: + // :A owl:oneOf (:B, :C) + // It will be represented by triples following a pattern similar to: + // <:A> owl:oneOf _:bnode1 . + // _:bnode1 rdf:first <:B> . + // _:bnode1 rdf:rest _:bnode2 . + // _:bnode2 rdf:first <:C> . + // _:bnode2 rdf:rest rdf:nil . + ryaDaoQueryWrapper.queryAll(null, OWL.ONEOF, null, new RDFHandlerBase() { + @Override + public void handleStatement(final Statement statement) throws RDFHandlerException { + final Resource enumType = statement.getSubject(); + // listHead will point to a type class of the enumeration. + final URI listHead = (URI) statement.getObject(); + if (!enumTypes.containsKey(enumType)) { + enumTypes.put(enumType, new LinkedHashSet<Resource>()); + } + + // listHead should point to a list of items that forms the + // enumeration. + try { + final Set<Resource> enumeration = new LinkedHashSet<>(getList(listHead)); + if (!enumeration.isEmpty()) { + // Add this enumeration for this type. + enumTypes.get(enumType).addAll(enumeration); + } + } catch (final QueryEvaluationException e) { + throw new RDFHandlerException("Error getting enumeration list.", e); + } + } + }); + + enumerations.clear(); + enumerations.putAll(enumTypes); + } + /** * Queries for all items that are in a list of the form: * <pre> @@ -1281,4 +1324,37 @@ public class InferenceEngine { } return null; } + + /** + * For a given type, return all sets of types such that owl:oneOf + * restrictions on those properties could imply this type. A enumeration + * of all the types that are part of the specified class type. + * @param type The type (URI or bnode) to check against the known oneOf + * sets. + * @return A {@link Set} of {@link Resource} types that represents the + * enumeration of resources that belong to the class type. + * An empty set is returned if no enumerations were found for the specified + * type. + */ + public Set<Resource> getEnumeration(final Resource type) { + if (enumerations != null) { + final Set<Resource> oneOfSet = enumerations.get(type); + if (oneOfSet != null) { + return oneOfSet; + } + } + return new LinkedHashSet<>(); + + } + + /** + * Checks if the specified type is an enumerated type. + * @param type The type (URI or bnode) to check against the known oneOf + * sets. + * @return {@code true} if the type is an enumerated type. {@code false} + * otherwise. + */ + public boolean isEnumeratedType(final Resource type) { + return enumerations != null && enumerations.containsKey(type); + } } http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/8431dfb7/sail/src/main/java/org/apache/rya/rdftriplestore/inference/OneOfVisitor.java ---------------------------------------------------------------------- diff --git a/sail/src/main/java/org/apache/rya/rdftriplestore/inference/OneOfVisitor.java b/sail/src/main/java/org/apache/rya/rdftriplestore/inference/OneOfVisitor.java new file mode 100644 index 0000000..004a4b0 --- /dev/null +++ b/sail/src/main/java/org/apache/rya/rdftriplestore/inference/OneOfVisitor.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.rya.rdftriplestore.inference; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.apache.log4j.Logger; +import org.apache.rya.api.RdfCloudTripleStoreConfiguration; +import org.openrdf.model.Resource; +import org.openrdf.model.vocabulary.RDF; +import org.openrdf.query.BindingSet; +import org.openrdf.query.algebra.BindingSetAssignment; +import org.openrdf.query.algebra.StatementPattern; +import org.openrdf.query.algebra.Var; +import org.openrdf.query.algebra.evaluation.QueryBindingSet; + +/** + * Visitor for handling owl:oneOf inferencing on a node. + */ +public class OneOfVisitor extends AbstractInferVisitor { + private static final Logger log = Logger.getLogger(OneOfVisitor.class); + + /** + * Creates a new instance of {@link OneOfVisitor}. + * @param conf the {@link RdfCloudeTripleStoreConfiguration}. + * @param inferenceEngine the {@link InferenceEngine}. + */ + public OneOfVisitor(final RdfCloudTripleStoreConfiguration conf, final InferenceEngine inferenceEngine) { + super(conf, inferenceEngine); + include = conf.isInferOneOf(); + } + + @Override + protected void meetSP(final StatementPattern node) throws Exception { + final Var subVar = node.getSubjectVar(); + final Var predVar = node.getPredicateVar(); + final Var objVar = node.getObjectVar(); + final Var conVar = node.getContextVar(); + if (predVar != null && objVar != null && objVar.getValue() != null && objVar.getValue() instanceof Resource && RDF.TYPE.equals(predVar.getValue()) && !EXPANDED.equals(conVar)) { + final Resource object = (Resource) objVar.getValue(); + if (inferenceEngine.isEnumeratedType(object)) { + final Set<BindingSet> solutions = new LinkedHashSet<>(); + final Set<Resource> enumeration = inferenceEngine.getEnumeration(object); + for (final Resource enumType : enumeration) { + final QueryBindingSet qbs = new QueryBindingSet(); + qbs.addBinding(subVar.getName(), enumType); + solutions.add(qbs); + } + + if (!solutions.isEmpty()) { + final BindingSetAssignment enumNode = new BindingSetAssignment(); + enumNode.setBindingSets(solutions); + + node.replaceWith(enumNode); + log.trace("Replacing node with inferred one of enumeration: " + enumNode); + } + } + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/8431dfb7/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 10edf49..f290324 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 @@ -438,4 +438,88 @@ public class InferenceEngineTest extends TestCase { Assert.assertTrue(momSuperClassUris.contains(mother)); Assert.assertTrue(momSuperClassUris.contains(mom)); } + + @Test + public void testOneOf() throws Exception { + final String ontology = "INSERT DATA { GRAPH <http://updated/test> {\n" + + " <urn:Suits> owl:oneOf _:bnodeS1 . \n" + + " _:bnodeS1 rdf:first <urn:Clubs> . \n" + + " _:bnodeS1 rdf:rest _:bnodeS2 . \n" + + " _:bnodeS2 rdf:first <urn:Diamonds> . \n" + + " _:bnodeS2 rdf:rest _:bnodeS3 . \n" + + " _:bnodeS3 rdf:first <urn:Hearts> . \n" + + " _:bnodeS3 rdf:rest _:bnodeS4 . \n" + + " _:bnodeS4 rdf:first <urn:Spades> . \n" + + " _:bnodeS4 rdf:rest rdf:nil . \n" + + " <urn:Ranks> owl:oneOf _:bnodeR1 . \n" + + " _:bnodeR1 rdf:first <urn:Ace> . \n" + + " _:bnodeR1 rdf:rest _:bnodeR2 . \n" + + " _:bnodeR2 rdf:first <urn:2> . \n" + + " _:bnodeR2 rdf:rest _:bnodeR3 . \n" + + " _:bnodeR3 rdf:first <urn:3> . \n" + + " _:bnodeR3 rdf:rest _:bnodeR4 . \n" + + " _:bnodeR4 rdf:first <urn:4> . \n" + + " _:bnodeR4 rdf:rest _:bnodeR5 . \n" + + " _:bnodeR5 rdf:first <urn:5> . \n" + + " _:bnodeR5 rdf:rest _:bnodeR6 . \n" + + " _:bnodeR6 rdf:first <urn:6> . \n" + + " _:bnodeR6 rdf:rest _:bnodeR7 . \n" + + " _:bnodeR7 rdf:first <urn:7> . \n" + + " _:bnodeR7 rdf:rest _:bnodeR8 . \n" + + " _:bnodeR8 rdf:first <urn:8> . \n" + + " _:bnodeR8 rdf:rest _:bnodeR9 . \n" + + " _:bnodeR9 rdf:first <urn:9> . \n" + + " _:bnodeR9 rdf:rest _:bnodeR10 . \n" + + " _:bnodeR10 rdf:first <urn:10> . \n" + + " _:bnodeR10 rdf:rest _:bnodeR11 . \n" + + " _:bnodeR11 rdf:first <urn:Jack> . \n" + + " _:bnodeR11 rdf:rest _:bnodeR12 . \n" + + " _:bnodeR12 rdf:first <urn:Queen> . \n" + + " _:bnodeR12 rdf:rest _:bnodeR13 . \n" + + " _:bnodeR13 rdf:first <urn:King> . \n" + + " _:bnodeR13 rdf:rest rdf:nil . \n" + + "}}"; + + conn.prepareUpdate(QueryLanguage.SPARQL, ontology).execute(); + inferenceEngine.refreshGraph(); + + final URI suits = vf.createURI("urn:Suits"); + final URI ranks = vf.createURI("urn:Ranks"); + + final URI clubs = vf.createURI("urn:Clubs"); + final URI diamonds = vf.createURI("urn:Diamonds"); + final URI hearts = vf.createURI("urn:Hearts"); + final URI spades = vf.createURI("urn:Spades"); + + final URI ace = vf.createURI("urn:Ace"); + final URI two = vf.createURI("urn:2"); + final URI three = vf.createURI("urn:3"); + final URI four = vf.createURI("urn:4"); + final URI five = vf.createURI("urn:5"); + final URI six = vf.createURI("urn:6"); + final URI seven = vf.createURI("urn:7"); + final URI eight = vf.createURI("urn:8"); + final URI nine = vf.createURI("urn:9"); + final URI ten = vf.createURI("urn:10"); + final URI jack = vf.createURI("urn:Jack"); + final URI queen = vf.createURI("urn:Queen"); + final URI king = vf.createURI("urn:King"); + + final URI joker = vf.createURI("urn:Joker"); + + final boolean isJokerEnumeratedType = inferenceEngine.isEnumeratedType(joker); + Assert.assertFalse(isJokerEnumeratedType); + + final boolean isSuitsEnumeratedType = inferenceEngine.isEnumeratedType(suits); + Assert.assertTrue(isSuitsEnumeratedType); + final Set<Resource> enumerationImplyingSuits = Sets.newHashSet(clubs, diamonds, hearts, spades); + final Set<Resource> actualCardSuits = inferenceEngine.getEnumeration(suits); + Assert.assertEquals(enumerationImplyingSuits, actualCardSuits); + + final boolean isRanksEnumeratedType = inferenceEngine.isEnumeratedType(ranks); + Assert.assertTrue(isRanksEnumeratedType); + final Set<Resource> enumerationImplyingRanks = Sets.newHashSet(ace, two, three, four, five, six, seven, eight, nine, ten, jack, queen, king); + final Set<Resource> actualCardRanks = inferenceEngine.getEnumeration(ranks); + Assert.assertEquals(enumerationImplyingRanks, actualCardRanks); + } } http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/8431dfb7/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 dcda8a9..1fcfa2c 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 @@ -212,8 +212,8 @@ public class InferenceIT extends TestCase { conn.prepareUpdate(QueryLanguage.SPARQL, instances).execute(); conn.prepareTupleQuery(QueryLanguage.SPARQL, "SELECT ?x { ?x a <urn:Dog> }").evaluate(resultHandler); Assert.assertEquals(2, solutions.size()); - Set<Value> answers = new HashSet<>(); - for (BindingSet solution : solutions) { + final Set<Value> answers = new HashSet<>(); + for (final BindingSet solution : solutions) { answers.add(solution.getBinding("x").getValue()); } Assert.assertTrue(answers.contains(vf.createURI("urn:Terry"))); @@ -400,4 +400,99 @@ public class InferenceIT extends TestCase { expectedMen.add(new ListBindingSet(varNames, vf.createURI("urn:Bob"))); Assert.assertEquals(expectedMen, new HashSet<>(solutions)); } + + @Test + public void testOneOfQuery() throws Exception { + final String ontology = "INSERT DATA { GRAPH <http://updated/test> {\n" + + " <urn:Suits> owl:oneOf _:bnodeS1 . \n" + + " _:bnodeS1 rdf:first <urn:Clubs> . \n" + + " _:bnodeS1 rdf:rest _:bnodeS2 . \n" + + " _:bnodeS2 rdf:first <urn:Diamonds> . \n" + + " _:bnodeS2 rdf:rest _:bnodeS3 . \n" + + " _:bnodeS3 rdf:first <urn:Hearts> . \n" + + " _:bnodeS3 rdf:rest _:bnodeS4 . \n" + + " _:bnodeS4 rdf:first <urn:Spades> . \n" + + " _:bnodeS4 rdf:rest rdf:nil . \n" + + " <urn:Ranks> owl:oneOf _:bnodeR1 . \n" + + " _:bnodeR1 rdf:first <urn:Ace> . \n" + + " _:bnodeR1 rdf:rest _:bnodeR2 . \n" + + " _:bnodeR2 rdf:first <urn:2> . \n" + + " _:bnodeR2 rdf:rest _:bnodeR3 . \n" + + " _:bnodeR3 rdf:first <urn:3> . \n" + + " _:bnodeR3 rdf:rest _:bnodeR4 . \n" + + " _:bnodeR4 rdf:first <urn:4> . \n" + + " _:bnodeR4 rdf:rest _:bnodeR5 . \n" + + " _:bnodeR5 rdf:first <urn:5> . \n" + + " _:bnodeR5 rdf:rest _:bnodeR6 . \n" + + " _:bnodeR6 rdf:first <urn:6> . \n" + + " _:bnodeR6 rdf:rest _:bnodeR7 . \n" + + " _:bnodeR7 rdf:first <urn:7> . \n" + + " _:bnodeR7 rdf:rest _:bnodeR8 . \n" + + " _:bnodeR8 rdf:first <urn:8> . \n" + + " _:bnodeR8 rdf:rest _:bnodeR9 . \n" + + " _:bnodeR9 rdf:first <urn:9> . \n" + + " _:bnodeR9 rdf:rest _:bnodeR10 . \n" + + " _:bnodeR10 rdf:first <urn:10> . \n" + + " _:bnodeR10 rdf:rest _:bnodeR11 . \n" + + " _:bnodeR11 rdf:first <urn:Jack> . \n" + + " _:bnodeR11 rdf:rest _:bnodeR12 . \n" + + " _:bnodeR12 rdf:first <urn:Queen> . \n" + + " _:bnodeR12 rdf:rest _:bnodeR13 . \n" + + " _:bnodeR13 rdf:first <urn:King> . \n" + + " _:bnodeR13 rdf:rest rdf:nil . \n" + + " <urn:Card> owl:intersectionOf (\n" + + " [ owl:onProperty <urn:HasRank> ; owl:someValuesFrom <urn:Ranks> ]\n" + + " [ owl:onProperty <urn:HasSuit> ; owl:someValuesFrom <urn:Suits> ]\n" + + " ) . \n" + + " <urn:HasRank> owl:range <urn:Ranks> . \n" + + " <urn:HasSuit> owl:range <urn:Suits> . \n" + + "}}"; + final String instances = "INSERT DATA { GRAPH <http://updated/test> {\n" + + " <urn:FlopCard1> a <urn:Card> . \n" + + " <urn:FlopCard1> <urn:HasRank> <urn:Ace> . \n" + + " <urn:FlopCard1> <urn:HasSuit> <urn:Diamonds> . \n" + + " <urn:FlopCard2> a <urn:Card> . \n" + + " <urn:FlopCard2> <urn:HasRank> <urn:Ace> . \n" + + " <urn:FlopCard2> <urn:HasSuit> <urn:Hearts> . \n" + + " <urn:FlopCard3> a <urn:Card> . \n" + + " <urn:FlopCard3> <urn:HasRank> <urn:King> . \n" + + " <urn:FlopCard3> <urn:HasSuit> <urn:Spades> . \n" + + " <urn:TurnCard> a <urn:Card> . \n" + + " <urn:TurnCard> <urn:HasRank> <urn:10> . \n" + + " <urn:TurnCard> <urn:HasSuit> <urn:Clubs> . \n" + + " <urn:RiverCard> a <urn:Card> . \n" + + " <urn:RiverCard> <urn:HasRank> <urn:Queen> . \n" + + " <urn:RiverCard> <urn:HasSuit> <urn:Hearts> . \n" + + "}}"; + conn.prepareUpdate(QueryLanguage.SPARQL, ontology).execute(); + conn.prepareUpdate(QueryLanguage.SPARQL, instances).execute(); + inferenceEngine.refreshGraph(); + + final List<String> varNames = new LinkedList<>(); + varNames.add("card"); + + // Find all cards with a <urn:Suits> type (expect 5 results) + final String cardSuitQuery = "SELECT ?card { GRAPH <http://updated/test> { ?card a <urn:Card> . ?suit a <urn:Suits> . ?card <urn:HasSuit> ?suit} } \n"; + conn.prepareTupleQuery(QueryLanguage.SPARQL, cardSuitQuery).evaluate(resultHandler); + final Set<BindingSet> expectedCardSuits = new HashSet<>(); + expectedCardSuits.add(new ListBindingSet(varNames, vf.createURI("urn:FlopCard1"))); + expectedCardSuits.add(new ListBindingSet(varNames, vf.createURI("urn:FlopCard2"))); + expectedCardSuits.add(new ListBindingSet(varNames, vf.createURI("urn:FlopCard3"))); + expectedCardSuits.add(new ListBindingSet(varNames, vf.createURI("urn:TurnCard"))); + expectedCardSuits.add(new ListBindingSet(varNames, vf.createURI("urn:RiverCard"))); + Assert.assertEquals(expectedCardSuits.size(), solutions.size()); + Assert.assertEquals(expectedCardSuits, new HashSet<>(solutions)); + + // Find all cards with a <urn:Ranks> type (expect 5 results) + final String cardRankQuery = "SELECT ?card { GRAPH <http://updated/test> { ?card a <urn:Card> . ?rank a <urn:Ranks> . ?card <urn:HasRank> ?rank} } \n"; + conn.prepareTupleQuery(QueryLanguage.SPARQL, cardRankQuery).evaluate(resultHandler); + final Set<BindingSet> expectedCardRanks = new HashSet<>(); + expectedCardRanks.add(new ListBindingSet(varNames, vf.createURI("urn:FlopCard1"))); + expectedCardRanks.add(new ListBindingSet(varNames, vf.createURI("urn:FlopCard2"))); + expectedCardRanks.add(new ListBindingSet(varNames, vf.createURI("urn:FlopCard3"))); + expectedCardRanks.add(new ListBindingSet(varNames, vf.createURI("urn:TurnCard"))); + expectedCardRanks.add(new ListBindingSet(varNames, vf.createURI("urn:RiverCard"))); + Assert.assertEquals(expectedCardRanks.size(), solutions.size()); + Assert.assertEquals(expectedCardRanks, new HashSet<>(solutions)); + } } http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/8431dfb7/sail/src/test/java/org/apache/rya/rdftriplestore/inference/OneOfVisitorTest.java ---------------------------------------------------------------------- diff --git a/sail/src/test/java/org/apache/rya/rdftriplestore/inference/OneOfVisitorTest.java b/sail/src/test/java/org/apache/rya/rdftriplestore/inference/OneOfVisitorTest.java new file mode 100644 index 0000000..484c8bc --- /dev/null +++ b/sail/src/test/java/org/apache/rya/rdftriplestore/inference/OneOfVisitorTest.java @@ -0,0 +1,172 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.rya.rdftriplestore.inference; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Iterator; +import java.util.Set; + +import org.apache.rya.accumulo.AccumuloRdfConfiguration; +import org.junit.Test; +import org.openrdf.model.Resource; +import org.openrdf.model.URI; +import org.openrdf.model.Value; +import org.openrdf.model.ValueFactory; +import org.openrdf.model.impl.ValueFactoryImpl; +import org.openrdf.model.vocabulary.RDF; +import org.openrdf.query.Binding; +import org.openrdf.query.BindingSet; +import org.openrdf.query.algebra.BindingSetAssignment; +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.Var; +import org.openrdf.query.algebra.evaluation.QueryBindingSet; + +import com.beust.jcommander.internal.Lists; +import com.google.common.collect.Sets; + +/** + * Tests the methods of {@link OneOfVisitor}. + */ +public class OneOfVisitorTest { + private final AccumuloRdfConfiguration conf = new AccumuloRdfConfiguration(); + private static final ValueFactory VF = new ValueFactoryImpl(); + + private static final URI SUITS = VF.createURI("urn:Suits"); + private static final URI RANKS = VF.createURI("urn:Ranks"); + + // Definition #1: :Suits owl:oneOf(:Clubs, :Diamonds, :Hearts, :Spades) + private static final URI CLUBS = VF.createURI("urn:Clubs"); + private static final URI DIAMONDS = VF.createURI("urn:Diamonds"); + private static final URI HEARTS = VF.createURI("urn:Hearts"); + private static final URI SPADES = VF.createURI("urn:Spades"); + + // Definition #2: :Ranks owl:oneOf(:Ace, :2, :3, :4, :5, :6, :7, :8, :9, :10, :Jack, :Queen, :King) + private static final URI ACE = VF.createURI("urn:Ace"); + private static final URI TWO = VF.createURI("urn:2"); + private static final URI THREE = VF.createURI("urn:3"); + private static final URI FOUR = VF.createURI("urn:4"); + private static final URI FIVE = VF.createURI("urn:5"); + private static final URI SIX = VF.createURI("urn:6"); + private static final URI SEVEN = VF.createURI("urn:7"); + private static final URI EIGHT = VF.createURI("urn:8"); + private static final URI NINE = VF.createURI("urn:9"); + private static final URI TEN = VF.createURI("urn:10"); + private static final URI JACK = VF.createURI("urn:Jack"); + private static final URI QUEEN = VF.createURI("urn:Queen"); + private static final URI KING = VF.createURI("urn:King"); + + private static final Set<Resource> CARD_SUIT_ENUMERATION = + Sets.newLinkedHashSet( + Lists.newArrayList(CLUBS, DIAMONDS, HEARTS, SPADES) + ); + private static final Set<Resource> CARD_RANK_ENUMERATION = + Sets.newLinkedHashSet( + Lists.newArrayList( + ACE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, + JACK, QUEEN, KING + ) + ); + + @Test + public void testOneOf() throws Exception { + // Configure a mock instance engine with an ontology: + final InferenceEngine inferenceEngine = mock(InferenceEngine.class); + when(inferenceEngine.isEnumeratedType(SUITS)).thenReturn(true); + when(inferenceEngine.getEnumeration(SUITS)).thenReturn(CARD_SUIT_ENUMERATION); + when(inferenceEngine.isEnumeratedType(RANKS)).thenReturn(true); + when(inferenceEngine.getEnumeration(RANKS)).thenReturn(CARD_RANK_ENUMERATION); + // Query for a Suits and rewrite using the visitor: + final Projection query = new Projection( + new StatementPattern(new Var("s"), new Var("p", RDF.TYPE), new Var("o", SUITS)), + new ProjectionElemList(new ProjectionElem("s", "subject"))); + query.visit(new OneOfVisitor(conf, inferenceEngine)); + // Expected structure: BindingSetAssignment containing the enumeration: + // BindingSetAssignment(CLUBS, DIAMONDS, HEARTS, SPADES) + // Collect the arguments to the BindingSetAssignment: + assertTrue(query.getArg() instanceof BindingSetAssignment); + final BindingSetAssignment bsa = (BindingSetAssignment) query.getArg(); + final Iterable<BindingSet> iterable = bsa.getBindingSets(); + final Iterator<BindingSet> iter = iterable.iterator(); + + assertBindingSet(iter, CARD_SUIT_ENUMERATION.iterator()); + + // Query for a Ranks and rewrite using the visitor: + final Projection query2 = new Projection( + new StatementPattern(new Var("s"), new Var("p", RDF.TYPE), new Var("o", RANKS)), + new ProjectionElemList(new ProjectionElem("s", "subject"))); + query2.visit(new OneOfVisitor(conf, inferenceEngine)); + // Expected structure: BindingSetAssignment containing the enumeration: + // BindingSetAssignment(ACE, 2, 3, 4, 5, 6, 7, 8, 9, 10, JACK, QUEEN, KING) + // Collect the arguments to the BindingSetAssignment: + assertTrue(query2.getArg() instanceof BindingSetAssignment); + final BindingSetAssignment bsa2 = (BindingSetAssignment) query2.getArg(); + final Iterable<BindingSet> iterable2 = bsa2.getBindingSets(); + final Iterator<BindingSet> iter2 = iterable2.iterator(); + + assertBindingSet(iter2, CARD_RANK_ENUMERATION.iterator()); + } + + @Test + public void testOneOfDisabled() throws Exception { + // Configure a mock instance engine with an ontology: + final InferenceEngine inferenceEngine = mock(InferenceEngine.class); + when(inferenceEngine.isEnumeratedType(SUITS)).thenReturn(true); + when(inferenceEngine.getEnumeration(SUITS)).thenReturn(CARD_SUIT_ENUMERATION); + when(inferenceEngine.isEnumeratedType(RANKS)).thenReturn(true); + when(inferenceEngine.getEnumeration(RANKS)).thenReturn(CARD_RANK_ENUMERATION); + + // Query for a Suits and rewrite using the visitor: + final Projection query = new Projection( + new StatementPattern(new Var("s"), new Var("p", RDF.TYPE), new Var("o", SUITS)), + new ProjectionElemList(new ProjectionElem("s", "subject"))); + + final AccumuloRdfConfiguration disabledConf = conf.clone(); + disabledConf.setInferOneOf(false); + + query.visit(new OneOfVisitor(disabledConf, inferenceEngine)); + + // Expected structure: the original statement: + assertTrue(query.getArg() instanceof StatementPattern); + final StatementPattern actualCardSuitSp = (StatementPattern) query.getArg(); + final StatementPattern expectedCardSuitSp = new StatementPattern(new Var("s"), new Var("p", RDF.TYPE), new Var("o", SUITS)); + assertEquals(expectedCardSuitSp, actualCardSuitSp); + } + + private static void assertBindingSet(final Iterator<BindingSet> bindingSetIter, final Iterator<Resource> expectedValues) { + while (expectedValues.hasNext()) { + final Resource expectedValue = expectedValues.next(); + assertTrue(bindingSetIter.hasNext()); + final BindingSet bindingSet = bindingSetIter.next(); + assertTrue(bindingSet instanceof QueryBindingSet); + assertEquals(1, bindingSet.getBindingNames().size()); + final Binding binding = bindingSet.getBinding("s"); + assertNotNull(binding); + final Value actualValue = binding.getValue(); + assertEquals(expectedValue, actualValue); + } + } +} \ No newline at end of file
