You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@rya.apache.org by ca...@apache.org on 2018/01/11 14:52:04 UTC
[5/6] incubator-rya git commit: RYA-417 Batch forward-chaining rules
engine. Closes #255.
http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/9f611019/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/strategy/AbstractRuleExecutionStrategy.java
----------------------------------------------------------------------
diff --git a/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/strategy/AbstractRuleExecutionStrategy.java b/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/strategy/AbstractRuleExecutionStrategy.java
new file mode 100644
index 0000000..24c8de9
--- /dev/null
+++ b/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/strategy/AbstractRuleExecutionStrategy.java
@@ -0,0 +1,108 @@
+/*
+ * 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.forwardchain.strategy;
+
+import org.apache.rya.api.domain.StatementMetadata;
+import org.apache.rya.forwardchain.ForwardChainException;
+
+import org.apache.rya.forwardchain.rule.AbstractConstructRule;
+import org.apache.rya.forwardchain.rule.AbstractInconsistencyRule;
+import org.apache.rya.forwardchain.rule.AbstractUpdateRule;
+
+/**
+ * Base class for rule application strategies, which can execute a single
+ * forward chaining (materialization) rule at a time. Subclasses may provide
+ * implementations of methods to execute whichever they support of construct
+ * rules, update rules, and inconsistency rules. The default behavior for all
+ * kinds is to throw an {@link UnsupportedOperationException}.
+ */
+public abstract class AbstractRuleExecutionStrategy {
+ protected int requiredLevel = 0;
+
+ /**
+ * Execute a rule corresponding to a "CONSTRUCT" query. Throws an
+ * UnsupportedOperationException if not explicitly overridden.
+ * @param rule The construct rule to apply; assumed not null.
+ * @param metadata Additional metadata to add to any inferred triples;
+ * assumed not null.
+ * @return The number of inferred triples. Higher-level forward chaining
+ * strategies may rely on the accuracy of this number.
+ * @throws ForwardChainException if execution failed.
+ */
+ public long executeConstructRule(AbstractConstructRule rule,
+ StatementMetadata metadata) throws ForwardChainException {
+ throw new UnsupportedOperationException("Rule execution strategy does not support construct rules.");
+ };
+
+ /**
+ * Execute a rule corresponding to an update query. Throws an
+ * UnsupportedOperationException if not explicitly overridden.
+ * @param rule The update rule to apply; assumed not null.
+ * @param metadata Additional metadata to add to any updated triples;
+ * assumed not null.
+ * @return The number of inferences made. Higher-level forward chaining
+ * strategies may rely on the accuracy of this number.
+ * @throws ForwardChainException if execution failed.
+ */
+ public long executeUpdateRule(AbstractUpdateRule rule,
+ StatementMetadata metadata) throws ForwardChainException {
+ throw new UnsupportedOperationException("Rule execution strategy does not support update rules.");
+ };
+
+ /**
+ * Execute a rule capable of detecting inconsistencies. Throws an
+ * UnsupportedOperationException if not explicitly overridden.
+ * @param rule The inconsistency rule to apply; assumed not null.
+ * @param metadata Additional metadata associated with inconsistencies;
+ * assumed not null.
+ * @return The number of inconsistencies found.
+ * @throws ForwardChainException if execution failed.
+ */
+ public long executeInconsistencyRule(AbstractInconsistencyRule rule,
+ StatementMetadata metadata) throws ForwardChainException {
+ throw new UnsupportedOperationException("Rule execution strategy does not perform inconsistency detection.");
+ }
+
+ /**
+ * Initialize the strategy and make any preparations for executing rules.
+ * Does nothing by default; subclasses should override if necessary.
+ * @throws ForwardChainException
+ */
+ public void initialize() throws ForwardChainException { };
+
+ /**
+ * Shut down the strategy and perform any appropriate cleanup. Does nothing
+ * by default; subclasses should override if necessary.
+ * @throws ForwardChainException
+ */
+ public void shutDown() throws ForwardChainException { }
+
+ /**
+ * Indicate that a rule need only be applied if one of the source statements
+ * is is at least this derivation level, i.e. took this many steps to derive
+ * itself. Subclasses may use this for optimization, but are not guaranteed
+ * to.
+ * @param derivationLevel Forward chaining level of statements that should
+ * be used to trigger rules. If not set, defaults to zero which should have
+ * no effect.
+ */
+ public void setRequiredLevel(int derivationLevel) {
+ this.requiredLevel = derivationLevel;
+ };
+}
http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/9f611019/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/strategy/MongoPipelineStrategy.java
----------------------------------------------------------------------
diff --git a/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/strategy/MongoPipelineStrategy.java b/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/strategy/MongoPipelineStrategy.java
new file mode 100644
index 0000000..c095122
--- /dev/null
+++ b/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/strategy/MongoPipelineStrategy.java
@@ -0,0 +1,276 @@
+/*
+ * 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.forwardchain.strategy;
+
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.LongAdder;
+
+import org.apache.log4j.Logger;
+import org.apache.rya.api.domain.RyaStatement;
+import org.apache.rya.api.domain.StatementMetadata;
+import org.apache.rya.api.persist.RyaDAOException;
+import org.apache.rya.api.persist.query.RyaQuery;
+import org.apache.rya.api.persist.query.RyaQueryEngine;
+import org.apache.rya.forwardchain.ForwardChainException;
+import org.apache.rya.forwardchain.rule.AbstractConstructRule;
+import org.apache.rya.forwardchain.rule.Rule;
+import org.apache.rya.mongodb.MongoDBRdfConfiguration;
+import org.apache.rya.mongodb.MongoDBRyaDAO;
+import org.apache.rya.mongodb.StatefulMongoDBRdfConfiguration;
+import org.apache.rya.mongodb.aggregation.AggregationPipelineQueryNode;
+import org.apache.rya.mongodb.aggregation.SparqlToPipelineTransformVisitor;
+import org.apache.rya.mongodb.batch.MongoDbBatchWriter;
+import org.apache.rya.mongodb.batch.MongoDbBatchWriterConfig;
+import org.apache.rya.mongodb.batch.MongoDbBatchWriterException;
+import org.apache.rya.mongodb.batch.MongoDbBatchWriterUtils;
+import org.apache.rya.mongodb.batch.collection.CollectionType;
+import org.apache.rya.mongodb.batch.collection.MongoCollectionType;
+import org.apache.rya.mongodb.dao.SimpleMongoDBStorageStrategy;
+import org.apache.rya.sail.config.RyaSailFactory;
+import org.bson.Document;
+import org.bson.conversions.Bson;
+import org.openrdf.query.algebra.QueryRoot;
+import org.openrdf.query.algebra.TupleExpr;
+
+import com.google.common.base.Preconditions;
+import com.mongodb.Block;
+import com.mongodb.DBObject;
+import com.mongodb.MongoClient;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoDatabase;
+import com.mongodb.util.JSON;
+
+/**
+ * A rule execution strategy for MongoDB Rya that converts a single rule into an
+ * aggregation pipeline whenever possible. Falls back on an internal
+ * {@link SailExecutionStrategy} to handle any rules that can't be converted.
+ */
+public class MongoPipelineStrategy extends AbstractRuleExecutionStrategy {
+ private static final Logger logger = Logger.getLogger(MongoPipelineStrategy.class);
+
+ private static final int PIPELINE_BATCH_SIZE = 1000;
+
+ private final SparqlToPipelineTransformVisitor pipelineVisitor;
+ private final MongoCollection<Document> baseCollection;
+ private final MongoDbBatchWriter<Document> batchWriter;
+ private final MongoDBRyaDAO dao;
+ private final SimpleMongoDBStorageStrategy storageStrategy = new SimpleMongoDBStorageStrategy();
+ private final ConcurrentHashMap<Rule, Long> executionTimes = new ConcurrentHashMap<>();
+ private final AbstractRuleExecutionStrategy backup;
+ private final RyaQueryEngine<StatefulMongoDBRdfConfiguration> engine;
+ private boolean usedBackup = false;
+
+ /**
+ * Initialize based on a configuration.
+ * @param mongoConf Should contain database information; cannot be null. If
+ * passed a stateful configuration, uses the existing mongo client,
+ * otherwise creates one.
+ */
+ public MongoPipelineStrategy(MongoDBRdfConfiguration mongoConf) throws ForwardChainException {
+ Preconditions.checkNotNull(mongoConf);
+ final String mongoDBName = mongoConf.getMongoDBName();
+ final String collectionName = mongoConf.getTriplesCollectionName();
+ mongoConf.setFlush(false);
+ final StatefulMongoDBRdfConfiguration statefulConf;
+ try {
+ if (mongoConf instanceof StatefulMongoDBRdfConfiguration) {
+ statefulConf = (StatefulMongoDBRdfConfiguration) mongoConf;
+ this.dao = new MongoDBRyaDAO();
+ this.dao.setConf(statefulConf);
+ this.dao.init();
+ }
+ else {
+ this.dao = RyaSailFactory.getMongoDAO(mongoConf);
+ statefulConf = this.dao.getConf();
+ }
+ } catch (RyaDAOException e) {
+ throw new ForwardChainException("Can't connect to Rya.", e);
+ }
+ final MongoClient mongoClient = statefulConf.getMongoClient();
+ final MongoDatabase mongoDB = mongoClient.getDatabase(mongoDBName);
+ this.baseCollection = mongoDB.getCollection(collectionName);
+ this.pipelineVisitor = new SparqlToPipelineTransformVisitor(this.baseCollection);
+ this.engine = this.dao.getQueryEngine();
+ this.backup = new SailExecutionStrategy(statefulConf);
+ final MongoDbBatchWriterConfig writerConfig = MongoDbBatchWriterUtils.getMongoDbBatchWriterConfig(statefulConf);
+ final CollectionType<Document> ct = new MongoCollectionType(baseCollection);
+ this.batchWriter = new MongoDbBatchWriter<>(ct, writerConfig);
+ try {
+ this.batchWriter.start();
+ } catch (final MongoDbBatchWriterException e) {
+ throw new ForwardChainException("Error starting MongoDB batch writer", e);
+ }
+ }
+
+ /**
+ * Execute a CONSTRUCT rule by converting it into a pipeline, iterating
+ * through the resulting documents, and inserting them back to the data
+ * store as new triples. If pipeline conversion fails, falls back on
+ * default execution strategy.
+ * @param rule A construct query rule; not null.
+ * @param metadata StatementMetadata to attach to new triples; not null.
+ * @return The number of new triples inferred.
+ * @throws ForwardChainException if execution fails.
+ */
+ @Override
+ public long executeConstructRule(AbstractConstructRule rule,
+ StatementMetadata metadata) throws ForwardChainException {
+ Preconditions.checkNotNull(rule);
+ logger.info("Applying inference rule " + rule + "...");
+ long timestamp = System.currentTimeMillis();
+ // Get a pipeline that turns individual matches into triples
+ List<Bson> pipeline = null;
+ try {
+ int requireSourceLevel = 0;
+ if (!usedBackup) {
+ // If we can assume derivation levels are set properly, we can optimize by
+ // pruning any derived fact whose sources are all old information. (i.e. we can
+ // infer that the pruned fact would have already been derived in a previous
+ // step.) But if the backup strategy has ever been used, the source triples aren't
+ // guaranteed to have derivation level set.
+ requireSourceLevel = requiredLevel;
+ }
+ pipeline = toPipeline(rule, requireSourceLevel, timestamp);
+ }
+ catch (ForwardChainException e) {
+ logger.error(e);
+ }
+ if (pipeline == null) {
+ if (backup == null) {
+ logger.error("Couldn't convert " + rule + " to pipeline:");
+ for (String line : rule.getQuery().toString().split("\n")) {
+ logger.error("\t" + line);
+ }
+ throw new UnsupportedOperationException("Couldn't convert query to pipeline.");
+ }
+ else {
+ logger.debug("Couldn't convert " + rule + " to pipeline:");
+ for (String line : rule.getQuery().toString().split("\n")) {
+ logger.debug("\t" + line);
+ }
+ logger.debug("Using fallback strategy.");
+ usedBackup = true;
+ return backup.executeConstructRule(rule, metadata);
+ }
+ }
+ // Execute the pipeline
+ for (Bson step : pipeline) {
+ logger.debug("\t" + step.toString());
+ }
+ LongAdder count = new LongAdder();
+ baseCollection.aggregate(pipeline)
+ .allowDiskUse(true)
+ .batchSize(PIPELINE_BATCH_SIZE)
+ .forEach(new Block<Document>() {
+ @Override
+ public void apply(Document doc) {
+ final DBObject dbo = (DBObject) JSON.parse(doc.toJson());
+ RyaStatement rstmt = storageStrategy.deserializeDBObject(dbo);
+ if (!statementExists(rstmt)) {
+ count.increment();
+ doc.replace(SimpleMongoDBStorageStrategy.STATEMENT_METADATA, metadata.toString());
+ try {
+ batchWriter.addObjectToQueue(doc);
+ } catch (MongoDbBatchWriterException e) {
+ logger.error("Couldn't insert " + rstmt, e);
+ }
+ }
+ }
+ });
+ try {
+ batchWriter.flush();
+ } catch (MongoDbBatchWriterException e) {
+ throw new ForwardChainException("Error writing to Mongo", e);
+ }
+ logger.info("Added " + count + " new statements.");
+ executionTimes.compute(rule, (r, previous) -> {
+ if (previous != null && previous > timestamp) {
+ return previous;
+ }
+ else {
+ return timestamp;
+ }
+ });
+ return count.longValue();
+ }
+
+ private boolean statementExists(RyaStatement rstmt) {
+ try {
+ return engine.query(new RyaQuery(rstmt)).iterator().hasNext();
+ } catch (RyaDAOException e) {
+ logger.error("Error querying for " + rstmt, e);
+ return false;
+ }
+ }
+
+ /**
+ * Flush and close the batch writer, and shut down the backup
+ * SailExecutionStrategy.
+ * @throws ForwardChainException if the batch writer or backup strategy
+ * throw any errors.
+ */
+ @Override
+ public void shutDown() throws ForwardChainException {
+ backup.shutDown();
+ try {
+ batchWriter.shutdown();
+ } catch (MongoDbBatchWriterException e) {
+ throw new ForwardChainException("Error shutting down batch writer", e);
+ }
+ }
+
+ /**
+ * Converts a construct rule into a series of documents representing
+ * aggregation pipeline steps.
+ * @param rule A construct query rule.
+ * @param sourceLevel Only make derivations whose source triples have this
+ * derivation level or higher, i.e. took some number of forward chaining
+ * steps to infer. Set to zero to skip this check.
+ * @param timestamp Timestamp to be set for all inferred triples.
+ * @return An aggregation pipeline.
+ * @throws ForwardChainException if pipeline construction fails.
+ */
+ private List<Bson> toPipeline(AbstractConstructRule rule, int sourceLevel,
+ long timestamp) throws ForwardChainException {
+ TupleExpr tupleExpr = rule.getQuery().getTupleExpr();
+ if (!(tupleExpr instanceof QueryRoot)) {
+ tupleExpr = new QueryRoot(tupleExpr);
+ }
+ try {
+ tupleExpr.visit(pipelineVisitor);
+ } catch (Exception e) {
+ throw new ForwardChainException("Error converting construct rule to an aggregation pipeline", e);
+ }
+ if (tupleExpr instanceof QueryRoot) {
+ QueryRoot root = (QueryRoot) tupleExpr;
+ if (root.getArg() instanceof AggregationPipelineQueryNode) {
+ AggregationPipelineQueryNode pipelineNode = (AggregationPipelineQueryNode) root.getArg();
+ pipelineNode.distinct(); // require distinct triples
+ pipelineNode.requireSourceDerivationDepth(sourceLevel);
+ long latestTime = executionTimes.getOrDefault(rule, 0L);
+ if (latestTime > 0) {
+ pipelineNode.requireSourceTimestamp(latestTime);
+ }
+ return pipelineNode.getTriplePipeline(timestamp, false);
+ }
+ }
+ return null;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/9f611019/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/strategy/RoundRobinStrategy.java
----------------------------------------------------------------------
diff --git a/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/strategy/RoundRobinStrategy.java b/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/strategy/RoundRobinStrategy.java
new file mode 100644
index 0000000..eb044fc
--- /dev/null
+++ b/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/strategy/RoundRobinStrategy.java
@@ -0,0 +1,212 @@
+/*
+ * 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.forwardchain.strategy;
+
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.log4j.Logger;
+import org.apache.rya.api.domain.RyaType;
+import org.apache.rya.api.domain.StatementMetadata;
+import org.apache.rya.forwardchain.ForwardChainConstants;
+import org.apache.rya.forwardchain.ForwardChainException;
+import org.apache.rya.forwardchain.rule.Rule;
+import org.apache.rya.forwardchain.rule.Ruleset;
+import org.openrdf.model.vocabulary.XMLSchema;
+
+import com.google.common.base.Preconditions;
+
+/**
+ * A simple {@link AbstractForwardChainStrategy} that iterates over every
+ * relevant rule once, and repeats until no rules are relevant.
+ * <p>
+ * Initially, all rules are considered relevant. Iteration 1 executes each rule
+ * once.
+ * <p>
+ * When a rule produces inferences, all rules that are marked as that rule's
+ * successors according to the {@link Ruleset} are triggered as potentially
+ * relevant for future execution. If a triggered rule is scheduled to be
+ * executed during the current iteration, nothing changes. If a triggered rule
+ * has already been executed once during the current iteration, or was not
+ * activated for the current iteration at all, it is flagged to be executed
+ * during the next iteration.
+ * <p>
+ * When an iteration concludes, a new iteration begins with the relevant set of
+ * rules having been determined during the previous iteration. If there are no
+ * such rules, forward chaining ends.
+ * <p>
+ * Within each iteration, rules are processed such that a rule which may trigger
+ * many other rules is given priority over a rule that may be triggered by many
+ * other rules.
+ * <p>
+ * The observation that one rule may trigger another is based on the
+ * relationships between triple patterns produced and consumed by the rules in
+ * general, not based on any triples that were actually generated. Therefore,
+ * there may be false positives but not false negatives: Rules triggered by the
+ * current rule may or may not produce more triples in response, but any rule
+ * that could produce triples in response will be triggered.
+ * <p>
+ * The procedure for executing the individual rules is governed by the
+ * {@link RuleExecutionStrategy}. This class uses the strategy's reported counts
+ * to determine whether or not a rule has produced inferences.
+ */
+public class RoundRobinStrategy extends AbstractForwardChainStrategy {
+ private static final Logger logger = Logger.getLogger(RoundRobinStrategy.class);
+
+ private final AbstractRuleExecutionStrategy ruleStrategy;
+ private int iteration;
+ private Ruleset ruleset;
+ private Set<Rule> activeNow;
+ private Set<Rule> activeNextIteration;
+ private long inferencesThisIteration;
+ private AtomicBoolean initialized = new AtomicBoolean(false);
+
+ /**
+ * Instantiate a RoundRobinStrategy by providing the RuleExecutionStrategy.
+ * @param ruleStrategy Defines how to execute individual rules; not null.
+ */
+ public RoundRobinStrategy(AbstractRuleExecutionStrategy ruleStrategy) {
+ Preconditions.checkNotNull(ruleStrategy);
+ this.ruleStrategy = ruleStrategy;
+ }
+
+ @Override
+ public void initialize(Ruleset withRuleset) throws ForwardChainException {
+ Preconditions.checkNotNull(withRuleset);
+ iteration = 0;
+ ruleset = withRuleset;
+ activeNow = new HashSet<>();
+ activeNextIteration = new HashSet<>(ruleset.getRules());
+ logger.info("Initializing round robin forward chaining, with " +
+ activeNextIteration.size() + " rules.");
+ initialized.set(true);
+ prepareQueue();
+ }
+
+ private void prepareQueue() throws ForwardChainException {
+ if (initialized.get()) {
+ if (activeNow.isEmpty()) {
+ if (iteration > 0) {
+ logger.info("Finished iteration " + iteration + "; made " +
+ inferencesThisIteration + " inferences.");
+ }
+ if (activeNextIteration.isEmpty()) {
+ logger.info("Finished forward chaining after " + iteration + " iterations.");
+ setDone();
+ }
+ else {
+ ruleStrategy.setRequiredLevel(iteration);
+ iteration++;
+ inferencesThisIteration = 0;
+ activeNow.addAll(activeNextIteration);
+ activeNextIteration.clear();
+ logger.info("Beginning iteration " + iteration + ", with " +
+ activeNow.size() + " rules to execute...");
+ }
+ }
+ }
+ }
+
+ private void setDone() throws ForwardChainException {
+ initialized.set(false);
+ if (ruleStrategy != null) {
+ ruleStrategy.shutDown();
+ }
+ }
+
+ @Override
+ public boolean isActive() {
+ return initialized.get();
+ }
+
+ @Override
+ public long executeNext() throws ForwardChainException {
+ if (!initialized.get()) {
+ return 0;
+ }
+ Rule rule = getNextRule();
+ if (rule == null) {
+ return 0;
+ }
+ StatementMetadata metadata = new StatementMetadata();
+ metadata.addMetadata(ForwardChainConstants.RYA_DERIVATION_TIME,
+ new RyaType(XMLSchema.INT, Integer.toString(iteration)));
+ long inferences = rule.execute(ruleStrategy, metadata);
+ inferencesThisIteration += inferences;
+ if (inferences > 0) {
+ for (Rule successor : ruleset.getSuccessorsOf(rule)) {
+ // If we'll handle the triggered rule in the current iteration,
+ // it may not need to be checked in the next one.
+ if (!activeNow.contains(successor)) {
+ activeNextIteration.add(successor);
+ }
+ }
+ }
+ prepareQueue();
+ return inferences;
+ }
+
+ private Rule getNextRule() {
+ if (activeNow.isEmpty()) {
+ return null;
+ }
+ Ruleset subset = new Ruleset(activeNow);
+ SortedSet<Rule> sorted = new TreeSet<>(new Comparator<Rule>() {
+ @Override
+ public int compare(Rule r1, Rule r2) {
+ // If one rule triggers the other (directly or indirectly) but
+ // not the other way around, the one that triggers the other
+ // should come first.
+ boolean forwardPath = subset.pathExists(r1, r2);
+ boolean backwardPath = subset.pathExists(r2, r1);
+ if (forwardPath && !backwardPath) {
+ return -1;
+ }
+ if (backwardPath && !forwardPath) {
+ return 1;
+ }
+ return 0;
+ }
+ }.thenComparingInt(rule -> {
+ // Otherwise, prioritize rules that trigger many remaining rules,
+ // and defer rules that can be triggered by many remaining rules.
+ return remainingPredecessors(rule).size() - remainingSuccessors(rule).size();
+ }).thenComparing(Rule::toString)); // Fall back on string comparison
+ sorted.addAll(activeNow);
+ Rule next = sorted.first();
+ activeNow.remove(next);
+ return next;
+ }
+
+ private Set<Rule> remainingSuccessors(Rule rule) {
+ Set<Rule> successors = new HashSet<>(ruleset.getSuccessorsOf(rule));
+ successors.retainAll(activeNow);
+ return successors;
+ }
+
+ private Set<Rule> remainingPredecessors(Rule rule) {
+ Set<Rule> predecessors = new HashSet<>(ruleset.getPredecessorsOf(rule));
+ predecessors.retainAll(activeNow);
+ return predecessors;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/9f611019/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/strategy/SailExecutionStrategy.java
----------------------------------------------------------------------
diff --git a/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/strategy/SailExecutionStrategy.java b/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/strategy/SailExecutionStrategy.java
new file mode 100644
index 0000000..d09c50c
--- /dev/null
+++ b/extras/rya.forwardchain/src/main/java/org/apache/rya/forwardchain/strategy/SailExecutionStrategy.java
@@ -0,0 +1,223 @@
+/*
+ * 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.forwardchain.strategy;
+
+import org.apache.accumulo.core.client.AccumuloException;
+import org.apache.accumulo.core.client.AccumuloSecurityException;
+import org.apache.log4j.Logger;
+import org.apache.rya.accumulo.AccumuloRdfConfiguration;
+import org.apache.rya.api.RdfCloudTripleStoreConfiguration;
+import org.apache.rya.api.domain.RyaStatement;
+import org.apache.rya.api.domain.StatementMetadata;
+import org.apache.rya.api.persist.RyaDAO;
+import org.apache.rya.api.persist.RyaDAOException;
+import org.apache.rya.api.persist.query.RyaQuery;
+import org.apache.rya.api.persist.query.RyaQueryEngine;
+import org.apache.rya.api.resolver.RdfToRyaConversions;
+import org.apache.rya.forwardchain.ForwardChainException;
+import org.apache.rya.forwardchain.rule.AbstractConstructRule;
+import org.apache.rya.indexing.accumulo.ConfigUtils;
+import org.apache.rya.mongodb.MongoDBRdfConfiguration;
+import org.apache.rya.sail.config.RyaSailFactory;
+import org.calrissian.mango.collect.CloseableIterable;
+import org.openrdf.model.Statement;
+import org.openrdf.query.GraphQuery;
+import org.openrdf.query.QueryEvaluationException;
+import org.openrdf.query.parser.ParsedGraphQuery;
+import org.openrdf.repository.RepositoryException;
+import org.openrdf.repository.sail.SailGraphQuery;
+import org.openrdf.repository.sail.SailRepository;
+import org.openrdf.repository.sail.SailRepositoryConnection;
+import org.openrdf.rio.RDFHandlerException;
+import org.openrdf.rio.helpers.RDFHandlerBase;
+
+import com.google.common.base.Preconditions;
+
+/**
+ * A naive but back-end-agnostic rule execution strategy that applies a
+ * construct rule by submitting the associated query to a Rya SAIL, then
+ * converting the resulting bindings (expecting variables "subject",
+ * "predicate", and "object") into triples and inserting them into a Rya DAO.
+ */
+public class SailExecutionStrategy extends AbstractRuleExecutionStrategy {
+ private static final Logger logger = Logger.getLogger(SailExecutionStrategy.class);
+
+ private final RdfCloudTripleStoreConfiguration conf;
+
+ private SailRepository repo = null;
+ private SailRepositoryConnection conn = null;
+ private RyaDAO<?> dao = null;
+ private boolean initialized = false;
+
+ /**
+ * Initialize a SailExecutionStrategy with the given configuration.
+ * @param conf Defines Rya connection and query parameters; not null.
+ */
+ public SailExecutionStrategy(RdfCloudTripleStoreConfiguration conf) {
+ Preconditions.checkNotNull(conf);
+ this.conf = conf;
+ }
+
+ /**
+ * Executes a CONSTRUCT query through the SAIL and inserts the results into
+ * the DAO.
+ * @param rule A construct query; not null.
+ * @param metadata Metadata to add to any inferred triples; not null.
+ * @return The number of inferred triples.
+ * @throws ForwardChainException if query execution or data insert fails.
+ */
+ @Override
+ public long executeConstructRule(AbstractConstructRule rule,
+ StatementMetadata metadata) throws ForwardChainException {
+ Preconditions.checkNotNull(rule);
+ Preconditions.checkNotNull(metadata);
+ if (!initialized) {
+ initialize();
+ }
+ ParsedGraphQuery graphQuery = rule.getQuery();
+ long statementsAdded = 0;
+ logger.info("Applying inference rule " + rule + "...");
+ for (String line : graphQuery.getTupleExpr().toString().split("\n")) {
+ logger.debug("\t" + line);
+ }
+ InferredStatementHandler<?> handler = new InferredStatementHandler<>(dao, metadata);
+ try {
+ GraphQuery executableQuery = new SailGraphQuery(graphQuery, conn) { };
+ executableQuery.evaluate(handler);
+ statementsAdded = handler.getNumStatementsAdded();
+ logger.info("Added " + statementsAdded + " inferred statements.");
+ return statementsAdded;
+ } catch (QueryEvaluationException e) {
+ throw new ForwardChainException("Error evaluating query portion of construct rule", e);
+ } catch (RDFHandlerException e) {
+ throw new ForwardChainException("Error processing results of construct rule", e);
+ }
+ }
+
+ /**
+ * Connect to the Rya SAIL. If a DAO wasn't provided, instantiate one from
+ * the configuration.
+ * @throws ForwardChainException if connecting fails.
+ */
+ @Override
+ public void initialize() throws ForwardChainException {
+ try {
+ if (dao == null) {
+ dao = getDAO();
+ }
+ repo = new SailRepository(RyaSailFactory.getInstance(conf));
+ conn = repo.getConnection();
+ initialized = true;
+ } catch (Exception e) {
+ shutDown();
+ throw new ForwardChainException("Error connecting to SAIL", e);
+ }
+ }
+
+ private RyaDAO<?> getDAO() throws RyaDAOException, ForwardChainException {
+ if (ConfigUtils.getUseMongo(conf)) {
+ MongoDBRdfConfiguration mongoConf;
+ if (conf instanceof MongoDBRdfConfiguration) {
+ mongoConf = (MongoDBRdfConfiguration) conf;
+ }
+ else {
+ mongoConf = new MongoDBRdfConfiguration(conf);
+ }
+ return RyaSailFactory.getMongoDAO(mongoConf);
+ }
+ else {
+ AccumuloRdfConfiguration accumuloConf;
+ if (conf instanceof AccumuloRdfConfiguration) {
+ accumuloConf = (AccumuloRdfConfiguration) conf;
+ }
+ else {
+ accumuloConf = new AccumuloRdfConfiguration(conf);
+ }
+ try {
+ return RyaSailFactory.getAccumuloDAO(accumuloConf);
+ } catch (AccumuloException | AccumuloSecurityException e) {
+ throw new ForwardChainException(e);
+ }
+ }
+ }
+
+ /**
+ * Shut down the SAIL connection objects.
+ */
+ @Override
+ public void shutDown() {
+ initialized = false;
+ if (conn != null) {
+ try {
+ conn.close();
+ } catch (RepositoryException e) {
+ logger.warn("Error closing SailRepositoryConnection", e);
+ }
+ }
+ if (repo != null && repo.isInitialized()) {
+ try {
+ repo.shutDown();
+ } catch (RepositoryException e) {
+ logger.warn("Error shutting down SailRepository", e);
+ }
+ }
+ try {
+ if (dao != null && dao.isInitialized()) {
+ dao.flush();
+ }
+ } catch (RyaDAOException e) {
+ logger.warn("Error flushing DAO", e);
+ }
+ }
+
+ private static class InferredStatementHandler<T extends RdfCloudTripleStoreConfiguration> extends RDFHandlerBase {
+ private RyaDAO<T> dao;
+ private RyaQueryEngine<T> engine;
+ private long numStatementsAdded = 0;
+ private StatementMetadata metadata;
+
+ InferredStatementHandler(RyaDAO<T> dao, StatementMetadata metadata) {
+ this.dao = dao;
+ this.engine = dao.getQueryEngine();
+ this.metadata = metadata;
+ this.engine.setConf(dao.getConf());
+ }
+
+ @Override
+ public void handleStatement(Statement statement) {
+ RyaStatement ryaStatement = RdfToRyaConversions.convertStatement(statement);
+ ryaStatement.setStatementMetadata(metadata);
+ try {
+ // Need to check whether the statement already exists, because
+ // we need an accurate count of newly added statements.
+ CloseableIterable<RyaStatement> iter = engine.query(new RyaQuery(ryaStatement));
+ if (!iter.iterator().hasNext()) {
+ dao.add(ryaStatement);
+ numStatementsAdded++;
+ }
+ } catch (RyaDAOException e) {
+ logger.error("Error handling inferred statement", e);
+ }
+ }
+
+ public long getNumStatementsAdded() {
+ return numStatementsAdded;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/9f611019/extras/rya.forwardchain/src/test/java/org/apache/rya/forwardchain/batch/MongoSpinIT.java
----------------------------------------------------------------------
diff --git a/extras/rya.forwardchain/src/test/java/org/apache/rya/forwardchain/batch/MongoSpinIT.java b/extras/rya.forwardchain/src/test/java/org/apache/rya/forwardchain/batch/MongoSpinIT.java
new file mode 100644
index 0000000..c70a025
--- /dev/null
+++ b/extras/rya.forwardchain/src/test/java/org/apache/rya/forwardchain/batch/MongoSpinIT.java
@@ -0,0 +1,169 @@
+/*
+ * 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.forwardchain.batch;
+
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.apache.hadoop.util.ToolRunner;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.apache.rya.indexing.mongodb.MongoIndexingConfiguration;
+import org.apache.rya.indexing.mongodb.MongoIndexingConfiguration.MongoDBIndexingConfigBuilder;
+import org.apache.rya.mongodb.EmbeddedMongoFactory;
+import org.apache.rya.mongodb.MongoDBRdfConfiguration;
+import org.apache.rya.sail.config.RyaSailFactory;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.openrdf.model.ValueFactory;
+import org.openrdf.model.impl.ValueFactoryImpl;
+import org.openrdf.query.BindingSet;
+import org.openrdf.query.QueryLanguage;
+import org.openrdf.query.TupleQuery;
+import org.openrdf.query.TupleQueryResult;
+import org.openrdf.query.impl.ListBindingSet;
+import org.openrdf.repository.RepositoryException;
+import org.openrdf.repository.sail.SailRepository;
+import org.openrdf.repository.sail.SailRepositoryConnection;
+import org.openrdf.rio.RDFFormat;
+import org.openrdf.rio.Rio;
+
+import com.google.common.io.Resources;
+import com.mongodb.MongoClient;
+import com.mongodb.ServerAddress;
+
+public class MongoSpinIT {
+ private static final ValueFactory VF = ValueFactoryImpl.getInstance();
+ private static final String EX = "http://example.org/";
+
+ private MongoDBRdfConfiguration conf;
+ private SailRepository repository;
+
+ @Before
+ public void setup() throws Exception {
+ Logger.getLogger("org.apache.rya.mongodb").setLevel(Level.WARN);
+ Logger.getLogger("org.apache.rya.forwardchain").setLevel(Level.INFO);
+ conf = getConf();
+ repository = new SailRepository(RyaSailFactory.getInstance(conf));
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (repository != null) {
+ try {
+ repository.shutDown();
+ } catch (final RepositoryException e) {
+ // quietly absorb this exception
+ }
+ }
+ }
+
+ @Test
+ public void testSailStrategy() throws Exception {
+ insertDataFile(Resources.getResource("data.ttl"), "http://example.org#");
+ insertDataFile(Resources.getResource("university.ttl"), "http://example.org#");
+ insertDataFile(Resources.getResource("owlrl.ttl"), "http://example.org#");
+ Set<BindingSet> solutions = executeQuery(Resources.getResource("query.sparql"));
+ Set<BindingSet> expected = new HashSet<>();
+ Assert.assertEquals(expected, solutions);
+ conf.setUseAggregationPipeline(false);
+ ForwardChainSpinTool tool = new ForwardChainSpinTool();
+ ToolRunner.run(conf, tool, new String[] {});
+ solutions = executeQuery(Resources.getResource("query.sparql"));
+ expected.add(new ListBindingSet(Arrays.asList("X", "Y"),
+ VF.createURI(EX, "Alice"), VF.createURI(EX, "Department1")));
+ Assert.assertEquals(expected, solutions);
+ Assert.assertEquals(24, tool.getNumInferences());
+ }
+
+ @Test
+ public void testPipelineStrategy() throws Exception {
+ insertDataFile(Resources.getResource("data.ttl"), "http://example.org#");
+ insertDataFile(Resources.getResource("university.ttl"), "http://example.org#");
+ insertDataFile(Resources.getResource("owlrl.ttl"), "http://example.org#");
+ Set<BindingSet> solutions = executeQuery(Resources.getResource("query.sparql"));
+ Set<BindingSet> expected = new HashSet<>();
+ Assert.assertEquals(expected, solutions);
+ conf.setUseAggregationPipeline(true);
+ ForwardChainSpinTool tool = new ForwardChainSpinTool();
+ ToolRunner.run(conf, tool, new String[] {});
+ solutions = executeQuery(Resources.getResource("query.sparql"));
+ expected.add(new ListBindingSet(Arrays.asList("X", "Y"),
+ VF.createURI(EX, "Alice"), VF.createURI(EX, "Department1")));
+ Assert.assertEquals(expected, solutions);
+ Assert.assertEquals(24, tool.getNumInferences());
+ }
+
+ private void insertDataFile(URL dataFile, String defaultNamespace) throws Exception {
+ RDFFormat format = Rio.getParserFormatForFileName(dataFile.getFile());
+ SailRepositoryConnection conn = repository.getConnection();
+ try {
+ conn.add(dataFile, defaultNamespace, format);
+ } finally {
+ closeQuietly(conn);
+ }
+ }
+
+ Set<BindingSet> executeQuery(URL queryFile) throws Exception {
+ SailRepositoryConnection conn = repository.getConnection();
+ try {
+ InputStream queryIS = queryFile.openStream();
+ BufferedReader br = new BufferedReader(new java.io.InputStreamReader(queryIS, "UTF-8"));
+ String query = br.lines().collect(Collectors.joining("\n"));
+ br.close();
+ TupleQuery tupleQuery = conn.prepareTupleQuery(QueryLanguage.SPARQL, query);
+ TupleQueryResult result = tupleQuery.evaluate();
+ Set<BindingSet> solutions = new HashSet<>();
+ while (result.hasNext()) {
+ solutions.add(result.next());
+ }
+ return solutions;
+ } finally {
+ closeQuietly(conn);
+ }
+ }
+
+ private static MongoDBRdfConfiguration getConf() throws Exception {
+ MongoDBIndexingConfigBuilder builder = MongoIndexingConfiguration.builder().setUseMockMongo(true);
+ final MongoClient c = EmbeddedMongoFactory.newFactory().newMongoClient();
+ final ServerAddress address = c.getAddress();
+ builder.setMongoHost(address.getHost());
+ builder.setMongoPort(Integer.toString(address.getPort()));
+ builder.setUseInference(false);
+ c.close();
+ return builder.build();
+ }
+
+ private static void closeQuietly(final SailRepositoryConnection conn) {
+ if (conn != null) {
+ try {
+ conn.close();
+ } catch (final RepositoryException e) {
+ // quietly absorb this exception
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/9f611019/extras/rya.forwardchain/src/test/java/org/apache/rya/forwardchain/rule/AntecedentVisitorTest.java
----------------------------------------------------------------------
diff --git a/extras/rya.forwardchain/src/test/java/org/apache/rya/forwardchain/rule/AntecedentVisitorTest.java b/extras/rya.forwardchain/src/test/java/org/apache/rya/forwardchain/rule/AntecedentVisitorTest.java
new file mode 100644
index 0000000..7761a1a
--- /dev/null
+++ b/extras/rya.forwardchain/src/test/java/org/apache/rya/forwardchain/rule/AntecedentVisitorTest.java
@@ -0,0 +1,156 @@
+/*
+ * 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.forwardchain.rule;
+
+import java.util.Set;
+
+import org.junit.Assert;
+import org.junit.Test;
+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.FOAF;
+import org.openrdf.model.vocabulary.RDF;
+import org.openrdf.model.vocabulary.RDFS;
+import org.openrdf.query.algebra.StatementPattern;
+import org.openrdf.query.algebra.Var;
+import org.openrdf.query.algebra.StatementPattern.Scope;
+import org.openrdf.query.parser.ParsedQuery;
+import org.openrdf.query.parser.sparql.SPARQLParser;
+
+import com.google.common.collect.Sets;
+
+public class AntecedentVisitorTest {
+ private static Var c(Value val) {
+ Var v = new Var("-const-" + val.stringValue(), val);
+ v.setAnonymous(true);
+ return v;
+ }
+
+ private static ValueFactory VF = ValueFactoryImpl.getInstance();
+ private static String EX = "http://example.org/";
+ private static URI G1 = VF.createURI(EX, "Graph1");
+ private static URI G2 = VF.createURI(EX, "Graph2");
+
+ @Test
+ public void testSelectQuery() throws Exception {
+ String text = "PREFIX foaf: <" + FOAF.NAMESPACE + ">\n"
+ + "SELECT * WHERE {\n"
+ + " ?x a foaf:Person .\n"
+ + " ?y a foaf:Person .\n"
+ + " ?x foaf:knows ?y .\n"
+ + "}";
+ ParsedQuery query = new SPARQLParser().parseQuery(text, null);
+ AntecedentVisitor visitor = new AntecedentVisitor();
+ query.getTupleExpr().visit(visitor);
+ Set<StatementPattern> expected = Sets.newHashSet(
+ new StatementPattern(new Var("x"), c(RDF.TYPE), c(FOAF.PERSON)),
+ new StatementPattern(new Var("y"), c(RDF.TYPE), c(FOAF.PERSON)),
+ new StatementPattern(new Var("x"), c(FOAF.KNOWS), new Var("y")));
+ Assert.assertEquals(expected, visitor.getAntecedents());
+ }
+
+ @Test
+ public void testConstructQuery() throws Exception {
+ String text = "PREFIX foaf: <" + FOAF.NAMESPACE + ">\n"
+ + "CONSTRUCT {\n"
+ + " ?y foaf:knows ?x .\n"
+ + " ?y <urn:knows> ?x .\n"
+ + " ?x <urn:knows> ?y .\n"
+ + "} WHERE {\n"
+ + " ?x a foaf:Person .\n"
+ + " ?y a foaf:Person .\n"
+ + " ?x foaf:knows ?y .\n"
+ + "}";
+ ParsedQuery query = new SPARQLParser().parseQuery(text, null);
+ AntecedentVisitor visitor = new AntecedentVisitor();
+ query.getTupleExpr().visit(visitor);
+ Set<StatementPattern> expected = Sets.newHashSet(
+ new StatementPattern(new Var("x"), c(RDF.TYPE), c(FOAF.PERSON)),
+ new StatementPattern(new Var("y"), c(RDF.TYPE), c(FOAF.PERSON)),
+ new StatementPattern(new Var("x"), c(FOAF.KNOWS), new Var("y")));
+ Assert.assertEquals(expected, visitor.getAntecedents());
+ }
+
+ @Test
+ public void testComplexQuery() throws Exception {
+ String text = "PREFIX foaf: <" + FOAF.NAMESPACE + ">\n"
+ + "PREFIX ex: <" + EX + ">\n"
+ + "SELECT * WHERE {\n"
+ + " { ?x a foaf:Person } UNION {\n"
+ + " GRAPH ex:Graph1 { ?y a foaf:Person }\n"
+ + " } .\n"
+ + " GRAPH ex:Graph2 {\n"
+ + " ?x foaf:knows ?y .\n"
+ + " }\n ."
+ + " OPTIONAL { ?x foaf:mbox ?m } .\n"
+ + " FILTER (?x != ?y) .\n"
+ + "}";
+ ParsedQuery query = new SPARQLParser().parseQuery(text, null);
+ AntecedentVisitor visitor = new AntecedentVisitor();
+ query.getTupleExpr().visit(visitor);
+ Set<StatementPattern> expected = Sets.newHashSet(
+ new StatementPattern(Scope.NAMED_CONTEXTS, new Var("y"), c(RDF.TYPE), c(FOAF.PERSON), c(G1)),
+ new StatementPattern(new Var("x"), c(RDF.TYPE), c(FOAF.PERSON)),
+ new StatementPattern(Scope.NAMED_CONTEXTS, new Var("x"), c(FOAF.KNOWS), new Var("y"), c(G2)),
+ new StatementPattern(new Var("x"), c(FOAF.MBOX), new Var("m")));
+ Assert.assertEquals(expected, visitor.getAntecedents());
+ }
+
+ @Test
+ public void testBNodeQuery() throws Exception {
+ String text = "PREFIX foaf: <" + FOAF.NAMESPACE + ">\n"
+ + "SELECT * WHERE {\n"
+ + " ?x a [ rdfs:subClassOf foaf:Person ] .\n"
+ + " ?x foaf:knows ?y .\n"
+ + "}";
+ ParsedQuery query = new SPARQLParser().parseQuery(text, null);
+ AntecedentVisitor visitor = new AntecedentVisitor();
+ query.getTupleExpr().visit(visitor);
+ Set<StatementPattern> actual = visitor.getAntecedents();
+ Assert.assertEquals(3, actual.size());
+ StatementPattern knows = new StatementPattern(new Var("x"), c(FOAF.KNOWS), new Var("y"));
+ Assert.assertTrue(actual.remove(knows));
+ Assert.assertTrue(actual.removeIf(sp -> {
+ return sp.getSubjectVar().equals(new Var("x"))
+ && RDF.TYPE.equals(sp.getPredicateVar().getValue())
+ && sp.getObjectVar().getValue() == null;
+ }));
+ Assert.assertTrue(actual.removeIf(sp -> {
+ return sp.getSubjectVar().getValue() == null
+ && RDFS.SUBCLASSOF.equals(sp.getPredicateVar().getValue())
+ && FOAF.PERSON.equals(sp.getObjectVar().getValue());
+ }));
+ }
+
+ @Test
+ public void testNoSP() throws Exception {
+ String text = "CONSTRUCT {\n"
+ + " owl:Thing a owl:Class ."
+ + " owl:Nothing a owl:Class ."
+ + " owl:Nothing rdfs:subClassOf owl:Thing ."
+ + "} WHERE { }";
+ ParsedQuery query = new SPARQLParser().parseQuery(text, null);
+ AntecedentVisitor visitor = new AntecedentVisitor();
+ query.getTupleExpr().visit(visitor);
+ Set<StatementPattern> expected = Sets.newHashSet();
+ Assert.assertEquals(expected, visitor.getAntecedents());
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/9f611019/extras/rya.forwardchain/src/test/java/org/apache/rya/forwardchain/rule/ConstructConsequentVisitorTest.java
----------------------------------------------------------------------
diff --git a/extras/rya.forwardchain/src/test/java/org/apache/rya/forwardchain/rule/ConstructConsequentVisitorTest.java b/extras/rya.forwardchain/src/test/java/org/apache/rya/forwardchain/rule/ConstructConsequentVisitorTest.java
new file mode 100644
index 0000000..0865ef8
--- /dev/null
+++ b/extras/rya.forwardchain/src/test/java/org/apache/rya/forwardchain/rule/ConstructConsequentVisitorTest.java
@@ -0,0 +1,164 @@
+/*
+ * 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.forwardchain.rule;
+
+import java.util.Arrays;
+import java.util.Set;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.openrdf.model.Value;
+import org.openrdf.model.vocabulary.FOAF;
+import org.openrdf.model.vocabulary.OWL;
+import org.openrdf.model.vocabulary.RDF;
+import org.openrdf.query.algebra.BNodeGenerator;
+import org.openrdf.query.algebra.Extension;
+import org.openrdf.query.algebra.ExtensionElem;
+import org.openrdf.query.algebra.MultiProjection;
+import org.openrdf.query.algebra.Projection;
+import org.openrdf.query.algebra.ProjectionElem;
+import org.openrdf.query.algebra.ProjectionElemList;
+import org.openrdf.query.algebra.SingletonSet;
+import org.openrdf.query.algebra.StatementPattern;
+import org.openrdf.query.algebra.ValueConstant;
+import org.openrdf.query.algebra.Var;
+
+import com.google.common.collect.Sets;
+
+public class ConstructConsequentVisitorTest {
+ private static Var s(Value val) {
+ return new Var("subject", val);
+ }
+ private static Var p(Value val) {
+ return new Var("predicate", val);
+ }
+ private static Var o(Value val) {
+ return new Var("object", val);
+ }
+ private static Var anon(Var var) {
+ var.setAnonymous(true);
+ return var;
+ }
+
+ @Test
+ public void testGenericSP() {
+ Extension extension = new Extension(new SingletonSet(),
+ new ExtensionElem(new Var("z"), "z"));
+ Projection projection = new Projection(extension, new ProjectionElemList(
+ new ProjectionElem("x", "subject"),
+ new ProjectionElem("y", "predicate"),
+ new ProjectionElem("z", "object")));
+ ConstructConsequentVisitor visitor = new ConstructConsequentVisitor();
+ projection.visit(visitor);
+ Set<StatementPattern> expected = Sets.newHashSet(
+ new StatementPattern(s(null), p(null), o(null)));
+ Assert.assertEquals(expected, visitor.getConsequents());
+ }
+
+ @Test
+ public void testConcreteSP() {
+ Extension extension = new Extension(new SingletonSet(),
+ new ExtensionElem(new ValueConstant(FOAF.PERSON), "x"),
+ new ExtensionElem(new ValueConstant(RDF.TYPE), "y"),
+ new ExtensionElem(new ValueConstant(OWL.CLASS), "z"));
+ Projection projection = new Projection(extension, new ProjectionElemList(
+ new ProjectionElem("x", "subject"),
+ new ProjectionElem("y", "predicate"),
+ new ProjectionElem("z", "object")));
+ ConstructConsequentVisitor visitor = new ConstructConsequentVisitor();
+ projection.visit(visitor);
+ Set<StatementPattern> expected = Sets.newHashSet(
+ new StatementPattern(s(FOAF.PERSON), p(RDF.TYPE), o(OWL.CLASS)));
+ Assert.assertEquals(expected, visitor.getConsequents());
+ }
+
+ @Test
+ public void testMissingVariables() {
+ Extension extension = new Extension(new SingletonSet(),
+ new ExtensionElem(new ValueConstant(FOAF.PERSON), "x"),
+ new ExtensionElem(new ValueConstant(RDF.TYPE), "y"));
+ Projection projection = new Projection(extension, new ProjectionElemList(
+ new ProjectionElem("x", "s"),
+ new ProjectionElem("y", "predicate"),
+ new ProjectionElem("z", "object")));
+ ConstructConsequentVisitor visitor = new ConstructConsequentVisitor();
+ projection.visit(visitor);
+ Set<StatementPattern> expected = Sets.newHashSet(
+ new StatementPattern(s(null), p(RDF.TYPE), o(null)));
+ Assert.assertEquals(expected, visitor.getConsequents());
+ }
+
+ @Test
+ public void testMultiProjection() {
+ Extension extension = new Extension(new SingletonSet(),
+ new ExtensionElem(new ValueConstant(RDF.TYPE), "rdftype"),
+ new ExtensionElem(new ValueConstant(OWL.OBJECTPROPERTY), "owlprop"),
+ new ExtensionElem(new ValueConstant(OWL.EQUIVALENTCLASS), "owleqcls"),
+ new ExtensionElem(new ValueConstant(OWL.CLASS), "owlclass"));
+ MultiProjection projection = new MultiProjection(extension, Arrays.asList(
+ new ProjectionElemList(
+ new ProjectionElem("cls", "subject"),
+ new ProjectionElem("rdftype", "predicate"),
+ new ProjectionElem("owlclass", "object")),
+ new ProjectionElemList(
+ new ProjectionElem("prop", "subject"),
+ new ProjectionElem("rdftype", "predicate"),
+ new ProjectionElem("owlprop", "object")),
+ new ProjectionElemList(
+ new ProjectionElem("owleqcls", "predicate"),
+ new ProjectionElem("cls", "object"))));
+ ConstructConsequentVisitor visitor = new ConstructConsequentVisitor();
+ projection.visit(visitor);
+ Set<StatementPattern> expected = Sets.newHashSet(
+ new StatementPattern(s(null), p(RDF.TYPE), o(OWL.CLASS)),
+ new StatementPattern(s(null), p(RDF.TYPE), o(OWL.OBJECTPROPERTY)),
+ new StatementPattern(s(null), p(OWL.EQUIVALENTCLASS), o(null)));
+ Assert.assertEquals(expected, visitor.getConsequents());
+ }
+
+ @Test
+ public void testNoExtension() {
+ StatementPattern sp = new StatementPattern(new Var("x"), new Var("y"), new Var("z"));
+ Projection projection = new Projection(sp, new ProjectionElemList(
+ new ProjectionElem("x", "subject"),
+ new ProjectionElem("y", "predicate"),
+ new ProjectionElem("z", "object")));
+ ConstructConsequentVisitor visitor = new ConstructConsequentVisitor();
+ projection.visit(visitor);
+ Set<StatementPattern> expected = Sets.newHashSet(
+ new StatementPattern(s(null), p(null), o(null)));
+ Assert.assertEquals(expected, visitor.getConsequents());
+ }
+
+ @Test
+ public void testBNode() {
+ Extension extension = new Extension(new SingletonSet(),
+ new ExtensionElem(new Var("x"), "x"),
+ new ExtensionElem(new BNodeGenerator(), "z"));
+ Projection projection = new Projection(extension, new ProjectionElemList(
+ new ProjectionElem("x", "subject"),
+ new ProjectionElem("y", "predicate"),
+ new ProjectionElem("z", "object")));
+ ConstructConsequentVisitor visitor = new ConstructConsequentVisitor();
+ projection.visit(visitor);
+ Set<StatementPattern> expected = Sets.newHashSet(
+ new StatementPattern(s(null), p(null), anon(o(null))));
+ Assert.assertEquals(expected, visitor.getConsequents());
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/9f611019/extras/rya.forwardchain/src/test/java/org/apache/rya/forwardchain/rule/RulesetTest.java
----------------------------------------------------------------------
diff --git a/extras/rya.forwardchain/src/test/java/org/apache/rya/forwardchain/rule/RulesetTest.java b/extras/rya.forwardchain/src/test/java/org/apache/rya/forwardchain/rule/RulesetTest.java
new file mode 100644
index 0000000..adb851b
--- /dev/null
+++ b/extras/rya.forwardchain/src/test/java/org/apache/rya/forwardchain/rule/RulesetTest.java
@@ -0,0 +1,137 @@
+/*
+ * 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.forwardchain.rule;
+
+import java.util.Collection;
+import java.util.Set;
+
+import org.apache.rya.api.domain.StatementMetadata;
+import org.apache.rya.forwardchain.ForwardChainException;
+import org.apache.rya.forwardchain.strategy.AbstractRuleExecutionStrategy;
+import org.junit.Assert;
+import org.junit.Test;
+import org.openrdf.model.Value;
+import org.openrdf.model.vocabulary.RDF;
+import org.openrdf.model.vocabulary.RDFS;
+import org.openrdf.query.algebra.StatementPattern;
+import org.openrdf.query.algebra.Var;
+
+import com.google.common.collect.Sets;
+
+public class RulesetTest {
+ private static Var c(Value val) {
+ Var v = new Var("-const-" + val.stringValue(), val);
+ v.setAnonymous(true);
+ return v;
+ }
+
+ private static class TestRule implements Rule {
+ private final Collection<StatementPattern> consume;
+ private final Collection<StatementPattern> produce;
+ TestRule(Collection<StatementPattern> consume, Collection<StatementPattern> produce) {
+ this.consume = consume;
+ this.produce = produce;
+ }
+ @Override
+ public boolean canConclude(StatementPattern sp) {
+ return produce.contains(sp);
+ }
+ @Override
+ public Collection<StatementPattern> getAntecedentPatterns() {
+ return consume;
+ }
+ @Override
+ public Collection<StatementPattern> getConsequentPatterns() {
+ return produce;
+ }
+ @Override
+ public long execute(AbstractRuleExecutionStrategy strategy,
+ StatementMetadata metadata) throws ForwardChainException {
+ return 0;
+ }
+ }
+
+ @Test
+ public void testDependencies() {
+ StatementPattern genericSP = new StatementPattern(new Var("a"), new Var("b"), new Var("c"));
+ StatementPattern typeSP = new StatementPattern(new Var("x"), c(RDF.TYPE), new Var("t"));
+ StatementPattern scoSP = new StatementPattern(new Var("x"), c(RDFS.SUBCLASSOF), new Var("y"));
+ Rule typeTriggersAny = new TestRule(
+ Sets.newHashSet(typeSP),
+ Sets.newHashSet(genericSP, typeSP, scoSP));
+ Rule subclassTriggersType = new TestRule(
+ Sets.newHashSet(scoSP),
+ Sets.newHashSet(genericSP, typeSP));
+ Rule anyTriggersNothing = new TestRule(
+ Sets.newHashSet(genericSP),
+ Sets.newHashSet());
+ Set<Rule> allRules = Sets.newHashSet(anyTriggersNothing, subclassTriggersType, typeTriggersAny);
+ Set<Rule> noRules = Sets.newHashSet();
+ Set<Rule> produceType = Sets.newHashSet(subclassTriggersType, typeTriggersAny);
+ Set<Rule> produceSubclass = Sets.newHashSet(typeTriggersAny);
+ Set<Rule> produceAny = Sets.newHashSet(subclassTriggersType, typeTriggersAny);
+ Set<Rule> consumeType = Sets.newHashSet(anyTriggersNothing, typeTriggersAny);
+ Ruleset ruleset = new Ruleset(allRules);
+ Assert.assertEquals(produceType, ruleset.getPredecessorsOf(typeTriggersAny));
+ Assert.assertEquals(allRules, ruleset.getSuccessorsOf(typeTriggersAny));
+ Assert.assertEquals(produceSubclass, ruleset.getPredecessorsOf(subclassTriggersType));
+ Assert.assertEquals(consumeType, ruleset.getSuccessorsOf(subclassTriggersType));
+ Assert.assertEquals(produceAny, ruleset.getPredecessorsOf(anyTriggersNothing));
+ Assert.assertEquals(noRules, ruleset.getSuccessorsOf(anyTriggersNothing));
+ }
+
+ @Test
+ public void testIndirectDependencies() {
+ StatementPattern genericSP = new StatementPattern(new Var("a"), new Var("b"), new Var("c"));
+ StatementPattern typeSP = new StatementPattern(new Var("x"), c(RDF.TYPE), new Var("t"));
+ StatementPattern scoSP = new StatementPattern(new Var("x"), c(RDFS.SUBCLASSOF), new Var("y"));
+ StatementPattern spoSP = new StatementPattern(new Var("x"), c(RDFS.SUBPROPERTYOF), new Var("y"));
+ Rule typeTriggersAny = new TestRule(
+ Sets.newHashSet(typeSP),
+ Sets.newHashSet(genericSP, typeSP, scoSP));
+ Rule subclassTriggersType = new TestRule(
+ Sets.newHashSet(scoSP),
+ Sets.newHashSet(genericSP, typeSP));
+ Rule anyTriggersNothing = new TestRule(
+ Sets.newHashSet(genericSP),
+ Sets.newHashSet());
+ Rule typeTriggersSubprop = new TestRule(
+ Sets.newHashSet(typeSP),
+ Sets.newHashSet(genericSP, spoSP));
+ Set<Rule> allRules = Sets.newHashSet(anyTriggersNothing, subclassTriggersType,
+ typeTriggersAny, typeTriggersSubprop);
+ Ruleset ruleset = new Ruleset(allRules);
+ Assert.assertTrue(ruleset.pathExists(typeTriggersAny, typeTriggersAny));
+ Assert.assertTrue(ruleset.pathExists(typeTriggersAny, subclassTriggersType));
+ Assert.assertTrue(ruleset.pathExists(typeTriggersAny, anyTriggersNothing));
+ Assert.assertTrue(ruleset.pathExists(typeTriggersAny, typeTriggersSubprop));
+ Assert.assertTrue(ruleset.pathExists(subclassTriggersType, typeTriggersAny));
+ Assert.assertTrue(ruleset.pathExists(subclassTriggersType, subclassTriggersType));
+ Assert.assertTrue(ruleset.pathExists(subclassTriggersType, anyTriggersNothing));
+ Assert.assertTrue(ruleset.pathExists(subclassTriggersType, typeTriggersSubprop));
+ Assert.assertFalse(ruleset.pathExists(anyTriggersNothing, typeTriggersAny));
+ Assert.assertFalse(ruleset.pathExists(anyTriggersNothing, subclassTriggersType));
+ Assert.assertFalse(ruleset.pathExists(anyTriggersNothing, anyTriggersNothing));
+ Assert.assertFalse(ruleset.pathExists(anyTriggersNothing, typeTriggersSubprop));
+ Assert.assertFalse(ruleset.pathExists(typeTriggersSubprop, typeTriggersAny));
+ Assert.assertFalse(ruleset.pathExists(typeTriggersSubprop, subclassTriggersType));
+ Assert.assertTrue(ruleset.pathExists(typeTriggersSubprop, anyTriggersNothing));
+ Assert.assertFalse(ruleset.pathExists(typeTriggersSubprop, typeTriggersSubprop));
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/9f611019/extras/rya.forwardchain/src/test/java/org/apache/rya/forwardchain/rule/SpinConstructRuleTest.java
----------------------------------------------------------------------
diff --git a/extras/rya.forwardchain/src/test/java/org/apache/rya/forwardchain/rule/SpinConstructRuleTest.java b/extras/rya.forwardchain/src/test/java/org/apache/rya/forwardchain/rule/SpinConstructRuleTest.java
new file mode 100644
index 0000000..9bbcce0
--- /dev/null
+++ b/extras/rya.forwardchain/src/test/java/org/apache/rya/forwardchain/rule/SpinConstructRuleTest.java
@@ -0,0 +1,213 @@
+/*
+ * 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.forwardchain.rule;
+
+import java.util.Arrays;
+
+import org.junit.Assert;
+import org.junit.Test;
+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.FOAF;
+import org.openrdf.model.vocabulary.OWL;
+import org.openrdf.model.vocabulary.RDF;
+import org.openrdf.model.vocabulary.RDFS;
+import org.openrdf.query.algebra.StatementPattern;
+import org.openrdf.query.algebra.Var;
+import org.openrdf.query.parser.ParsedGraphQuery;
+import org.openrdf.query.parser.sparql.SPARQLParser;
+
+import com.google.common.collect.HashMultiset;
+import com.google.common.collect.Multiset;
+
+public class SpinConstructRuleTest {
+ private static ValueFactory VF = ValueFactoryImpl.getInstance();
+ private static SPARQLParser PARSER = new SPARQLParser();
+
+ private static URI RL_CAX_SCO = VF.createURI("http://example.org/rl/cax-sco");
+ private static URI RL_SCM_CLS = VF.createURI("http://example.org/rl/scm-cls");
+ private static URI RL_PRP_SPO1 = VF.createURI("http://example.org/rl/prp-spo");
+ private static URI LIVING_THING = VF.createURI("http://example.org/LivingThing");
+
+ private static Var c(Value val) {
+ return new Var("-const-" + val.stringValue(), val);
+ }
+ private static Var ac(Value val) {
+ Var v = c(val);
+ v.setAnonymous(true);
+ return v;
+ }
+
+ @Test
+ public void testEmptyWhere() throws Exception {
+ String text = "CONSTRUCT {\n"
+ + " ?this a <" + LIVING_THING.stringValue() + "> .\n"
+ + "} WHERE { }";
+ ParsedGraphQuery query = (ParsedGraphQuery) PARSER.parseQuery(text, null);
+ SpinConstructRule rule = new SpinConstructRule(FOAF.PERSON, VF.createURI("urn:person-is-living"), query);
+ Multiset<StatementPattern> expectedAntecedents = HashMultiset.create(Arrays.asList(
+ new StatementPattern(new Var("this"), c(RDF.TYPE), c(FOAF.PERSON))));
+ Multiset<StatementPattern> expectedConsequents = HashMultiset.create(Arrays.asList(
+ new StatementPattern(new Var("subject"), new Var("predicate", RDF.TYPE), new Var("object", LIVING_THING))));
+ Assert.assertEquals(expectedAntecedents, HashMultiset.create(rule.getAntecedentPatterns()));
+ Assert.assertEquals(expectedConsequents, HashMultiset.create(rule.getConsequentPatterns()));
+ Assert.assertFalse(rule.hasAnonymousConsequent());
+ // Basic pattern matches
+ Assert.assertTrue(rule.canConclude(new StatementPattern(new Var("x"), c(RDF.TYPE), c(LIVING_THING))));
+ // Broader patterns match (variables in place of constants)
+ Assert.assertTrue(rule.canConclude(new StatementPattern(new Var("x"), c(RDF.TYPE), new Var("y"))));
+ Assert.assertTrue(rule.canConclude(new StatementPattern(new Var("x"), new Var("y"), c(LIVING_THING))));
+ Assert.assertTrue(rule.canConclude(new StatementPattern(new Var("a"), new Var("b"), new Var("c"))));
+ // Narrower patterns match (constants in place of variables)
+ Assert.assertTrue(rule.canConclude(new StatementPattern(c(RDF.TYPE), c(RDF.TYPE), c(LIVING_THING))));
+ Assert.assertTrue(rule.canConclude(new StatementPattern(c(FOAF.MBOX), c(RDF.TYPE), new Var("y"))));
+ Assert.assertTrue(rule.canConclude(new StatementPattern(c(RDF.ALT), new Var("y"), c(LIVING_THING))));
+ Assert.assertTrue(rule.canConclude(new StatementPattern(c(RDF.BAG), new Var("b"), new Var("c"))));
+ // Incompatible patterns don't match (different constants)
+ Assert.assertFalse(rule.canConclude(new StatementPattern(new Var("x"), c(RDFS.SUBCLASSOF), new Var("y"))));
+ Assert.assertFalse(rule.canConclude(new StatementPattern(new Var("x"), new Var("y"), c(FOAF.PERSON))));
+ Assert.assertFalse(rule.canConclude(new StatementPattern(c(RDF.TYPE), c(RDF.TYPE), c(RDF.TYPE))));
+ }
+
+ @Test
+ public void testThisUnbound() throws Exception {
+ String text = "CONSTRUCT {\n"
+ + " ?ind a ?superclass .\n"
+ + "} WHERE {\n"
+ + " ?ind a ?subclass .\n"
+ + " ?subclass rdfs:subClassOf ?superclass .\n"
+ + "}";
+ ParsedGraphQuery query = (ParsedGraphQuery) PARSER.parseQuery(text, null);
+ SpinConstructRule rule = new SpinConstructRule(OWL.THING, RL_CAX_SCO, query);
+ Multiset<StatementPattern> expectedAntecedents = HashMultiset.create(Arrays.asList(
+ new StatementPattern(new Var("subclass"), ac(RDFS.SUBCLASSOF), new Var("superclass")),
+ new StatementPattern(new Var("ind"), ac(RDF.TYPE), new Var("subclass"))));
+ Multiset<StatementPattern> expectedConsequents = HashMultiset.create(Arrays.asList(
+ new StatementPattern(new Var("subject"), new Var("predicate", RDF.TYPE), new Var("object"))));
+ Assert.assertEquals(expectedAntecedents, HashMultiset.create(rule.getAntecedentPatterns()));
+ Assert.assertEquals(expectedConsequents, HashMultiset.create(rule.getConsequentPatterns()));
+ Assert.assertFalse(rule.hasAnonymousConsequent());
+ // Basic pattern matches
+ Assert.assertTrue(rule.canConclude(new StatementPattern(new Var("x"), c(RDF.TYPE), new Var("y"))));
+ // Broader patterns match (variables in place of constants)
+ Assert.assertTrue(rule.canConclude(new StatementPattern(new Var("a"), new Var("b"), new Var("c"))));
+ // Narrower patterns match (constants in place of variables)
+ Assert.assertTrue(rule.canConclude(new StatementPattern(c(RDF.TYPE), c(RDF.TYPE), c(RDF.TYPE))));
+ // Incompatible patterns don't match (different constants)
+ Assert.assertFalse(rule.canConclude(new StatementPattern(new Var("x"), c(RDFS.SUBCLASSOF), new Var("y"))));
+ }
+
+ @Test
+ public void testMultipleConsequents() throws Exception {
+ String text = "CONSTRUCT {\n"
+ // actual rule is "?this subClassOf ?this", but reflexive construct patterns produce
+ // bnodes due to an openrdf bug, resulting in incorrect matches
+ + " ?this rdfs:subClassOf ?something .\n"
+ + " ?this owl:equivalentClass ?something .\n"
+ + " ?this rdfs:subClassOf owl:Thing .\n"
+ + " owl:Nothing rdfs:subClassOf ?this .\n"
+ + "} WHERE { }";
+ ParsedGraphQuery query = (ParsedGraphQuery) PARSER.parseQuery(text, null);
+ SpinConstructRule rule = new SpinConstructRule(OWL.CLASS, RL_SCM_CLS, query);
+ Multiset<StatementPattern> expectedAntecedents = HashMultiset.create(Arrays.asList(
+ new StatementPattern(new Var("this"), c(RDF.TYPE), c(OWL.CLASS))));
+ Multiset<StatementPattern> expectedConsequents = HashMultiset.create(Arrays.asList(
+ new StatementPattern(new Var("subject"), new Var("predicate", RDFS.SUBCLASSOF), new Var("object")),
+ new StatementPattern(new Var("subject"), new Var("predicate", OWL.EQUIVALENTCLASS), new Var("object")),
+ new StatementPattern(new Var("subject"), new Var("predicate", RDFS.SUBCLASSOF), new Var("object", OWL.THING)),
+ new StatementPattern(new Var("subject", OWL.NOTHING), new Var("predicate", RDFS.SUBCLASSOF), new Var("object"))));
+ Assert.assertEquals(expectedAntecedents, HashMultiset.create(rule.getAntecedentPatterns()));
+ Assert.assertEquals(expectedConsequents, HashMultiset.create(rule.getConsequentPatterns()));
+ // Basic pattern matches
+ Assert.assertTrue(rule.canConclude(new StatementPattern(new Var("x"), c(RDFS.SUBCLASSOF), new Var("y"))));
+ Assert.assertTrue(rule.canConclude(new StatementPattern(new Var("x"), c(OWL.EQUIVALENTCLASS), new Var("y"))));
+ Assert.assertTrue(rule.canConclude(new StatementPattern(new Var("x"), c(RDFS.SUBCLASSOF), c(OWL.THING))));
+ Assert.assertTrue(rule.canConclude(new StatementPattern(c(OWL.NOTHING), c(RDFS.SUBCLASSOF), new Var("y"))));
+ // Broader patterns match (variables in place of constants)
+ Assert.assertTrue(rule.canConclude(new StatementPattern(new Var("a"), new Var("b"), new Var("c"))));
+ Assert.assertTrue(rule.canConclude(new StatementPattern(new Var("a"), new Var("b"), c(OWL.THING))));
+ Assert.assertTrue(rule.canConclude(new StatementPattern(c(OWL.NOTHING), new Var("b"), new Var("c"))));
+ // Narrower patterns match (constants in place of variables)
+ Assert.assertTrue(rule.canConclude(new StatementPattern(c(FOAF.PERSON), c(RDFS.SUBCLASSOF), new Var("x"))));
+ Assert.assertTrue(rule.canConclude(new StatementPattern(c(FOAF.PERSON), c(OWL.EQUIVALENTCLASS), c(FOAF.PERSON))));
+ Assert.assertTrue(rule.canConclude(new StatementPattern(c(OWL.NOTHING), c(RDFS.SUBCLASSOF), c(FOAF.PERSON))));
+ Assert.assertTrue(rule.canConclude(new StatementPattern(c(OWL.NOTHING), c(OWL.EQUIVALENTCLASS), c(FOAF.PERSON))));
+ Assert.assertTrue(rule.canConclude(new StatementPattern(c(OWL.NOTHING), c(OWL.EQUIVALENTCLASS), c(OWL.THING))));
+ // Incompatible patterns don't match (different constants)
+ Assert.assertFalse(rule.canConclude(new StatementPattern(new Var("x"), c(RDFS.SUBPROPERTYOF), c(OWL.THING))));
+ }
+
+ @Test
+ public void testGeneralConsequent() throws Exception {
+ String text = "CONSTRUCT {\n"
+ + " ?x ?p2 ?y"
+ + "} WHERE {\n"
+ + " ?x ?p1 ?y .\n"
+ + " ?p1 rdfs:subPropertyOf ?p2 .\n"
+ + "}";
+ ParsedGraphQuery query = (ParsedGraphQuery) PARSER.parseQuery(text, null);
+ SpinConstructRule rule = new SpinConstructRule(OWL.THING, RL_PRP_SPO1, query);
+ Multiset<StatementPattern> expectedAntecedents = HashMultiset.create(Arrays.asList(
+ new StatementPattern(new Var("p1"), ac(RDFS.SUBPROPERTYOF), new Var("p2")),
+ new StatementPattern(new Var("x"), new Var("p1"), new Var("y"))));
+ Multiset<StatementPattern> expectedConsequents = HashMultiset.create(Arrays.asList(
+ new StatementPattern(new Var("subject"), new Var("predicate"), new Var("object"))));
+ Assert.assertEquals(expectedAntecedents, HashMultiset.create(rule.getAntecedentPatterns()));
+ Assert.assertEquals(expectedConsequents, HashMultiset.create(rule.getConsequentPatterns()));
+ Assert.assertFalse(rule.hasAnonymousConsequent());
+ // Basic pattern matches
+ Assert.assertTrue(rule.canConclude(new StatementPattern(new Var("a"), new Var("b"), new Var("c"))));
+ // Narrower patterns match (constants in place of variables)
+ Assert.assertTrue(rule.canConclude(new StatementPattern(new Var("x"), c(RDFS.SUBPROPERTYOF), c(OWL.THING))));
+ Assert.assertTrue(rule.canConclude(new StatementPattern(c(OWL.NOTHING), new Var("prop"), c(OWL.THING))));
+ Assert.assertTrue(rule.canConclude(new StatementPattern(c(FOAF.PERSON), c(RDFS.SUBCLASSOF), new Var("x"))));
+ Assert.assertTrue(rule.canConclude(new StatementPattern(c(OWL.NOTHING), c(RDFS.SUBCLASSOF), c(FOAF.PERSON))));
+ }
+
+ @Test
+ public void testAnonymousConsequent() throws Exception {
+ String text = "CONSTRUCT {\n"
+ + " ?x ?p2 _:something"
+ + "} WHERE {\n"
+ + " ?x ?p1 ?y .\n"
+ + " ?p1 rdfs:subPropertyOf ?p2 .\n"
+ + "}";
+ ParsedGraphQuery query = (ParsedGraphQuery) PARSER.parseQuery(text, null);
+ SpinConstructRule rule = new SpinConstructRule(OWL.THING, RL_PRP_SPO1, query);
+ Multiset<StatementPattern> expectedAntecedents = HashMultiset.create(Arrays.asList(
+ new StatementPattern(new Var("p1"), ac(RDFS.SUBPROPERTYOF), new Var("p2")),
+ new StatementPattern(new Var("x"), new Var("p1"), new Var("y"))));
+ Assert.assertEquals(expectedAntecedents, HashMultiset.create(rule.getAntecedentPatterns()));
+ // should have detected anonymous node
+ Assert.assertTrue(rule.hasAnonymousConsequent());
+ Var anonymousObject = new Var("object");
+ anonymousObject.setAnonymous(true);
+ Multiset<StatementPattern> expectedConsequents = HashMultiset.create(Arrays.asList(
+ new StatementPattern(new Var("subject"), new Var("predicate"), anonymousObject)));
+ Assert.assertEquals(expectedConsequents, HashMultiset.create(rule.getConsequentPatterns()));
+ // Pattern matches should be unaffected by anonymous node status
+ Assert.assertTrue(rule.canConclude(new StatementPattern(new Var("a"), new Var("b"), new Var("c"))));
+ Assert.assertTrue(rule.canConclude(new StatementPattern(new Var("x"), c(RDFS.SUBPROPERTYOF), c(OWL.THING))));
+ Assert.assertTrue(rule.canConclude(new StatementPattern(c(OWL.NOTHING), new Var("prop"), c(OWL.THING))));
+ Assert.assertTrue(rule.canConclude(new StatementPattern(c(FOAF.PERSON), c(RDFS.SUBCLASSOF), new Var("x"))));
+ Assert.assertTrue(rule.canConclude(new StatementPattern(c(OWL.NOTHING), c(RDFS.SUBCLASSOF), c(FOAF.PERSON))));
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/9f611019/extras/rya.forwardchain/src/test/resources/data.ttl
----------------------------------------------------------------------
diff --git a/extras/rya.forwardchain/src/test/resources/data.ttl b/extras/rya.forwardchain/src/test/resources/data.ttl
new file mode 100644
index 0000000..f026409
--- /dev/null
+++ b/extras/rya.forwardchain/src/test/resources/data.ttl
@@ -0,0 +1,56 @@
+# 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.
+
+# Sample data similar to LUBM
+
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix owl: <http://www.w3.org/2002/07/owl#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+@prefix lubm: <http://swat.cse.lehigh.edu/onto/univ-bench.owl#> .
+@prefix ex: <http://example.org/> .
+
+ex:Department0 lubm:subOrganizationOf ex:College0 .
+ex:Department1 lubm:subOrganizationOf ex:College1 .
+ex:Department2 lubm:subOrganizationOf ex:College2 .
+ex:Department3 lubm:subOrganizationOf ex:College2 .
+
+ex:College0 a lubm:Organization ; lubm:subOrganizationOf ex:University0 .
+ex:College1 a lubm:Organization ; lubm:subOrganizationOf ex:University0 .
+ex:College2 lubm:subOrganizationOf ex:University1 .
+
+ex:Department0 a lubm:Department .
+ex:Department1 a lubm:Department .
+ex:Department2 a lubm:Department .
+ex:Department3 a lubm:Department .
+
+# Professors -- infer Faculty and therefore Person
+ex:Alice a lubm:Professor .
+ex:Bob a lubm:Professor .
+ex:Carol a lubm:Professor .
+ex:Dan a lubm:Professor .
+ex:Eve a lubm:Professor .
+
+# Can infer Organization via rdfs:range
+ex:Alice lubm:worksFor ex:Department2 .
+ex:Carol lubm:worksFor ex:Department0 .
+ex:Dan lubm:worksFor ex:Department2 .
+ex:Eve lubm:worksFor ex:Department1 .
+
+ex:Alice lubm:headOf ex:Department1 . # infer Chair and worksFor
+ex:Dan lubm:headOf ex:Department2 . # infer Chair, already have worksFor
+ex:Eve lubm:headOf ex:ResearchGroup3 . # infer worksFor, therefore Organization, but not Chair because not a Department
\ No newline at end of file