/*
* Copyright 2009 Revelytix.
*
* Licensed 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.mulgara.swrl;
import java.net.URI;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.log4j.Logger;
import org.jrdf.graph.BlankNode;
import org.jrdf.graph.Literal;
import org.jrdf.graph.Node;
import org.jrdf.graph.ObjectNode;
import org.jrdf.graph.PredicateNode;
import org.jrdf.graph.SubjectNode;
import org.jrdf.graph.Triple;
import org.jrdf.graph.URIReference;
import org.jrdf.vocabulary.OWL;
import org.jrdf.vocabulary.RDF;
import org.mulgara.itql.VariableFactoryImpl;
import org.mulgara.krule.ConsistencyCheck;
import org.mulgara.krule.KruleStructureException;
import org.mulgara.krule.QueryStruct;
import org.mulgara.krule.Rule;
import org.mulgara.krule.RuleStructure;
import org.mulgara.query.Answer;
import org.mulgara.query.ConstantValue;
import org.mulgara.query.ConstraintConjunction;
import org.mulgara.query.ConstraintDisjunction;
import org.mulgara.query.ConstraintElement;
import org.mulgara.query.ConstraintExpression;
import org.mulgara.query.ConstraintFilter;
import org.mulgara.query.ConstraintImpl;
import org.mulgara.query.ConstraintIs;
import org.mulgara.query.ConstraintOperation;
import org.mulgara.query.GraphExpression;
import org.mulgara.query.GraphResource;
import org.mulgara.query.Order;
import org.mulgara.query.Query;
import org.mulgara.query.QueryException;
import org.mulgara.query.SelectElement;
import org.mulgara.query.SingleTransitiveConstraint;
import org.mulgara.query.TuplesException;
import org.mulgara.query.UnconstrainedAnswer;
import org.mulgara.query.Value;
import org.mulgara.query.Variable;
import org.mulgara.query.VariableFactory;
import org.mulgara.query.filter.And;
import org.mulgara.query.filter.Equals;
import org.mulgara.query.filter.Filter;
import org.mulgara.query.filter.RDFTerm;
import org.mulgara.query.filter.value.DataTypeFn;
import org.mulgara.query.filter.value.IRI;
import org.mulgara.query.filter.value.TypedLiteral;
import org.mulgara.query.filter.value.Var;
import org.mulgara.query.rdf.LiteralImpl;
import org.mulgara.query.rdf.SWRL;
import org.mulgara.query.rdf.TripleImpl;
import org.mulgara.query.rdf.URIReferenceImpl;
import org.mulgara.query.rdf.VariableNodeImpl;
import org.mulgara.resolver.OperationContext;
import org.mulgara.rules.InitializerException;
import org.mulgara.rules.RuleLoader;
import org.mulgara.rules.Rules;
import org.mulgara.util.functional.Pair;
/**
* Implementation of a rule loader which parses rule definitions from an RDF
* graph according to the SWRL schema.
*
* @created Jun 4, 2009
* @author Alex Hall
* @copyright © 2009 <a href="http://www.revelytix.com">Revelytix, Inc.</a>
* @licence <a href="{@docRoot}/../../LICENCE.txt">Open Software License v3.0</a>
*/
public class SWRLLoader implements RuleLoader {
private static final Logger logger = Logger.getLogger(SWRLLoader.class.getName());
/** A field used in queries to indicate no prior constraints on the answer. */
private static final UnconstrainedAnswer UNCONSTRAINED = new UnconstrainedAnswer();
/** RDF reference for rdf:type. */
public static final URIReferenceImpl RDF_TYPE = new URIReferenceImpl(RDF.TYPE);
/** The graph from which rule definitions are being read. */
private final GraphResource ruleGraph;
/** The graph expression containing the base statements. */
private final GraphExpression baseGraph;
/** The graph where entailed statements will be inserted. */
private final URI destGraph;
/** The database session for querying. */
private OperationContext operationContext;
/** Factory for creating unique variables in the rule queries. */
private VariableFactory varFactory = new VariableFactoryImpl();
/**
* Factory method.
* @param ruleGraph The graph URI that contains the SWRL rule definitions.
* @param baseGraph The graph expression that contains the base statements.
* @param destGraph The graph URI that will receive the entailed statements.
* @return The rule loader which will process the rule definitions.
*/
public static RuleLoader newInstance(URI ruleGraph, GraphExpression baseGraph, URI destGraph) {
return new SWRLLoader(ruleGraph, baseGraph, destGraph);
}
/**
* Initialize the rule loader with the rule, base, and destination graphs.
* @param ruleGraph The graph containing the rules to load.
* @param baseGraph The graph expression containing the base statements.
* @param destGraph The destination graph for entailed statements.
*/
SWRLLoader(URI ruleGraph, GraphExpression baseGraph, URI destGraph) {
this.ruleGraph = new GraphResource(ruleGraph);
this.baseGraph = baseGraph;
this.destGraph = destGraph;
}
/* (non-Javadoc)
* @see org.mulgara.rules.RuleLoader#readRules(java.lang.Object)
*/
public Rules readRules(Object session) throws InitializerException, RemoteException {
this.operationContext = (OperationContext)session;
RuleStructure rules = new RuleStructure();
rules.setTargetModel(destGraph);
try {
List<Node> ruleNodes = findRules();
if (ruleNodes.isEmpty()) {
logger.debug("No SWRL data.");
return null;
}
if (logger.isDebugEnabled()) logger.debug("Found rules: " + ruleNodes);
Map<URIReference,Variable> vars = findVariables();
if (logger.isDebugEnabled()) logger.debug("Found variables: " + vars);
Map<Node,Pair<URI,ConstraintImpl>> constraintAtoms = new HashMap<Node,Pair<URI,ConstraintImpl>>();
Map<Node,Pair<URI,Filter>> filterAtoms = new HashMap<Node,Pair<URI,Filter>>();
findAtoms(constraintAtoms, filterAtoms, vars);
if (logger.isDebugEnabled()) {
logger.debug("Found constraint atoms: " + constraintAtoms);
logger.debug("Found filter atoms: " + filterAtoms);
}
for (Node ruleNode : ruleNodes) {
buildRule(ruleNode, rules, constraintAtoms, filterAtoms);
}
processTriggers(rules);
} catch (TuplesException te) {
logger.error("Exception while accessing rule data.", te);
throw new InitializerException("Problem accessing rule data", te);
} catch (QueryException qe) {
logger.error("Exception while reading rules.", qe);
throw new InitializerException("Problem reading rules", qe);
} catch (SWRLStructureException se) {
logger.error("Error in rule RDF data:" + se.getMessage(), se);
throw new InitializerException("Problem in rules RDF", se);
}
return rules;
}
/**
* Find all variable declarations in the rule graph. Variable references are
* identified using an <tt>rdf:type</tt> of <tt>swrl:Variable</tt>.
* @return A mapping of variable URI reference to a variable object which will
* represent that reference in all subsequent queries.
*/
private Map<URIReference,Variable> findVariables() throws QueryException, TuplesException {
// select $var from <ruleGraph> where $var <rdf:type> <swrl:Variable>
Variable varV = new Variable("var");
ConstraintExpression where = new ConstraintImpl(varV, RDF_TYPE, SWRL.VARIABLE);
Query query = createQuery(where, varV);
if (logger.isDebugEnabled()) logger.debug("Variable query: " + query);
Answer answer = doQuery(query);
Map<URIReference,Variable> variables = new HashMap<URIReference,Variable>();
try {
answer.beforeFirst();
while (answer.next()) {
Object obj = answer.getObject(varV.getName());
if (logger.isDebugEnabled()) logger.debug("Found variable: " + obj);
if (obj instanceof URIReference) {
Variable var = varFactory.newVariable();
variables.put((URIReference)obj, var);
}
}
} finally {
answer.close();
}
return variables;
}
/**
* Find all resources in the rule graph that represent SWRL rules (a.k.a. implications).
* A rule is identified by an <tt>rdf:type</tt> of <tt>swrl:Imp</tt>.
* @return A list of RDF nodes that represent rule resources.
*/
private List<Node> findRules() throws QueryException, TuplesException {
// select $rule from <ruleGraph> where $rule <rdf:type> <swrl:Imp>
Variable ruleVar = new Variable("rule");
ConstraintExpression where = new ConstraintImpl(ruleVar, RDF_TYPE, SWRL.IMP);
Query query = createQuery(where, ruleVar);
if (logger.isDebugEnabled()) logger.debug("Rule query: " + query);
Answer answer = doQuery(query);
List<Node> rules = new ArrayList<Node>();
try {
answer.beforeFirst();
while (answer.next()) {
rules.add((Node)answer.getObject(ruleVar.getName()));
}
} finally {
answer.close();
}
return rules;
}
/**
* Finds all SWRL atoms in the rule graph, and convert them into query triple patterns
* or filters as appropriate. The triple patterns and filters are inserted into the supplied
* maps, and variable mappings are performed when the patterns and filters are constructed.
* @param constraintAtoms This object will be populated with SWRL atoms that convert to RDF
* triple patterns. The mapping is from the atom's RDF node to a [atom type URI, triple pattern] pair.
* @param filterAtoms This object will be populated with SWRL atoms that convert to SPARQL
* filters. The mapping is from the atom's RDF node to a [atom type URI, filter] pair.
* @param varMap A map of variable URI references to variable objects to use when constructing
* triple patterns and filters.
*/
private void findAtoms(Map<Node,Pair<URI,ConstraintImpl>> constraintAtoms, Map<Node,Pair<URI,Filter>> filterAtoms, Map<URIReference,Variable> varMap)
throws QueryException, TuplesException, SWRLStructureException {
findClassAtoms(constraintAtoms, varMap);
findIndividualAtoms(constraintAtoms, varMap);
findDataAtoms(constraintAtoms, varMap);
findIdentityAtoms(constraintAtoms, varMap);
findDataRangeAtoms(filterAtoms, varMap);
findBuiltinAtoms(filterAtoms, varMap);
}
/**
* Find all atoms in the rule graph of type <tt>swrl:ClassAtom</tt> and insert the resulting
* triple patterns into the supplied map. Note that only named classes are supported.
* A class atom has the form:
* <pre>
* _:x rdf:type swrl:ClassAtom .
* _:x swrl:classPredicate ex:MyClass .
* _:x swrl:argument1 rule:varX .
* </pre>
* and will be translated into the triple pattern: <tt>[$varX rdf:type ex:MyClass]</tt>
* @param constraintAtoms The map into which the triple patterns are inserted.
* @param varMap The variable reference map which is used to construct the triple patterns.
*/
private void findClassAtoms(Map<Node,Pair<URI,ConstraintImpl>> constraintAtoms, Map<URIReference,Variable> varMap)
throws QueryException, TuplesException, SWRLStructureException {
// select $atom $class $arg from <ruleGraph>
// where $atom <rdf:type> <swrl:ClassAtom> and $atom <swrl:classPredicate> $class and $atom <swrl:argument1> $arg
Variable atomVar = new Variable("atom");
Variable classVar = new Variable("class");
Variable argVar = new Variable("arg");
ConstraintExpression where = new ConstraintConjunction(
new ConstraintImpl(atomVar, RDF_TYPE, SWRL.CLASS_ATOM),
new ConstraintImpl(atomVar, SWRL.CLASS_PREDICATE, classVar),
new ConstraintImpl(atomVar, SWRL.ARG_1, argVar)
);
Query query = createQuery(where, atomVar, classVar, argVar);
Answer answer = doQuery(query);
try {
answer.beforeFirst();
while (answer.next()) {
Node atom = (Node)answer.getObject(atomVar.getName());
Object classObj = answer.getObject(classVar.getName());
checkClass(classObj, URIReference.class, "Only named classes may be used with class atoms.");
Object argObj = answer.getObject(argVar.getName());
checkClass(argObj, URIReference.class, "Argument of a class atom may only be a URI or variable reference");
constraintAtoms.put(atom, new Pair<URI,ConstraintImpl>(SWRL.CLASS_ATOM.getURI(),
toConstraint((URIReference)argObj, RDF_TYPE, (URIReference)classObj, varMap)));
}
} finally {
answer.close();
}
}
/**
* Find all atoms in the rule graph of type <tt>swrl:IndividualPropertyAtom</tt> and insert
* the resulting triple patterns into the supplied map. An individual property atom has the form:
* <pre>
* _:x rdf:type swrl:IndividualPropertyAtom .
* _:x swrl:propertyPredicate ex:myObjectProperty .
* _:x swrl:argument1 ex:myIndividual .
* _:x swrl:argument2 rule:varX .
* </pre>
* and will be translated to the triple pattern: <tt>[ex:myIndividual ex:myObjectProperty $varX]</tt>.
* @param constraintAtoms The map into which the triple patterns are inserted.
* @param varMap The variable reference map which is used to construct the triple patterns.
*/
private void findIndividualAtoms(Map<Node,Pair<URI,ConstraintImpl>> constraintAtoms, Map<URIReference,Variable> varMap)
throws QueryException, TuplesException, SWRLStructureException {
// select $atom $property $arg1 $arg2 from <ruleGraph>
// where $atom <rdf:type> <swrl:IndividualPropertyAtom> and $atom <swrl:propertyPredicate> $property
// and $atom <swrl:argument1> $arg1 and $atom <swrl:argument2> $arg2;
Variable atomVar = new Variable("atom");
Variable propertyVar = new Variable("property");
Variable arg1Var = new Variable("arg1");
Variable arg2Var = new Variable("arg2");
ConstraintExpression where = new ConstraintConjunction(
new ConstraintImpl(atomVar, RDF_TYPE, SWRL.INDIVIDUAL_ATOM),
new ConstraintImpl(atomVar, SWRL.PROPERTY_PREDICATE, propertyVar),
new ConstraintImpl(atomVar, SWRL.ARG_1, arg1Var),
new ConstraintImpl(atomVar, SWRL.ARG_2, arg2Var)
);
Query query = createQuery(where, atomVar, propertyVar, arg1Var, arg2Var);
Answer answer = doQuery(query);
try {
answer.beforeFirst();
while (answer.next()) {
Node atom = (Node)answer.getObject(atomVar.getName());
Object subjectObj = answer.getObject(arg1Var.getName());
checkClass(subjectObj, URIReference.class, "Subject of an individual property atom must be a URI or variable reference");
Object propertyObj = answer.getObject(propertyVar.getName());
checkClass(propertyObj, URIReference.class, "Predicate of an individual property atom must be a URI");
Node object = (Node)answer.getObject(arg2Var.getName());
constraintAtoms.put(atom, new Pair<URI,ConstraintImpl>(SWRL.INDIVIDUAL_ATOM.getURI(),
toConstraint((URIReference)subjectObj, (URIReference)propertyObj, object, varMap)));
}
} finally {
answer.close();
}
}
/**
* Find all atoms in the rule graph of type <tt>swrl:DatavaluedPropertyAtom</tt> and insert
* the resulting triple patterns into the supplied map. An individual property atom has the form:
* <pre>
* _:x rdf:type swrl:DatavaluedPropertyAtom .
* _:x swrl:propertyPredicate ex:myDataProperty .
* _:x swrl:argument1 ex:myIndividual .
* _:x swrl:argument2 rule:varX .
* </pre>
* and will be translated to the triple pattern: <tt>[ex:myIndividual ex:myDataProperty $varX]</tt>.
* @param constraintAtoms The map into which the triple patterns are inserted.
* @param varMap The variable reference map which is used to construct the triple patterns.
*/
private void findDataAtoms(Map<Node,Pair<URI,ConstraintImpl>> constraintAtoms, Map<URIReference,Variable> varMap)
throws QueryException, TuplesException, SWRLStructureException {
// select $atom $property $arg1 $arg2 from <ruleGraph>
// where $atom <rdf:type> <swrl:DatavaluedPropertyAtom> and $atom <swrl:propertyPredicate> $property
// and $atom <swrl:argument1> $arg1 and $atom <swrl:argument2> $arg2;
Variable atomVar = new Variable("atom");
Variable propertyVar = new Variable("property");
Variable arg1Var = new Variable("arg1");
Variable arg2Var = new Variable("arg2");
ConstraintExpression where = new ConstraintConjunction(
new ConstraintImpl(atomVar, RDF_TYPE, SWRL.DATA_ATOM),
new ConstraintImpl(atomVar, SWRL.PROPERTY_PREDICATE, propertyVar),
new ConstraintImpl(atomVar, SWRL.ARG_1, arg1Var),
new ConstraintImpl(atomVar, SWRL.ARG_2, arg2Var)
);
Query query = createQuery(where, atomVar, propertyVar, arg1Var, arg2Var);
Answer answer = doQuery(query);
try {
answer.beforeFirst();
while (answer.next()) {
Node atom = (Node)answer.getObject(atomVar.getName());
Object subjectObj = answer.getObject(arg1Var.getName());
checkClass(subjectObj, URIReference.class, "Subject of a data-valued property atom must be a URI or variable reference");
Object propertyObj = answer.getObject(propertyVar.getName());
checkClass(propertyObj, URIReference.class, "Predicate of a data-valued property atom must be a URI");
Node object = (Node)answer.getObject(arg2Var.getName());
constraintAtoms.put(atom, new Pair<URI,ConstraintImpl>(SWRL.DATA_ATOM.getURI(),
toConstraint((URIReference)subjectObj, (URIReference)propertyObj, object, varMap)));
}
} finally {
answer.close();
}
}
/**
* Find all identity atoms in the rule graph (i.e. atoms of type <tt>swrl:SameIndividualsAtom</tt>
* and <tt>swrl:DifferentIndividualsAtom</tt>) an insert the resulting triple patterns into
* the supplied map. Identity atoms take the following form:
* <pre>
* _:x rdf:type swrl:SameIndividualsAtom .
* _:x swrl:argument1 ex:individualA .
* _:x swrl:argument2 rule:varX .
*
* _:y rdf:type swrl:DifferentIndividualsAtom .
* _:x swrl:argument1 ex:individualB .
* _:x swrl:argument2 rule:varY .
* </pre>
* and are translated into the triple patterns: <tt>[ex:individualA owl:sameAs $varX]</tt>
* and <tt>[ex:individualB owl:differentFrom $varY]</tt>.
* @param constraintAtoms The map into which the triple patterns are inserted.
* @param varMap The variable reference map which is used to construct the triple patterns.
*/
private void findIdentityAtoms(Map<Node,Pair<URI,ConstraintImpl>> constraintAtoms, Map<URIReference,Variable> varMap)
throws QueryException, TuplesException, SWRLStructureException {
// select $atom $type $arg1 $arg2 from <ruleGraph>
// where $atom <rdf:type> $type and $atom <swrl:argument1> $arg1 and $atom <swrl:argument2> $arg2
// and ($type <mulgara:is> <swrl:SameIndividualsAtom> or $type <mulgara:is> <swrl:DifferentIndividualsAtom>)
Variable atomVar = new Variable("atom");
Variable typeVar = new Variable("type");
Variable arg1Var = new Variable("arg1");
Variable arg2Var = new Variable("arg2");
ConstraintExpression where = new ConstraintConjunction(
new ConstraintImpl(atomVar, RDF_TYPE, typeVar),
new ConstraintImpl(atomVar, SWRL.ARG_1, arg1Var),
new ConstraintImpl(atomVar, SWRL.ARG_2, arg2Var),
new ConstraintDisjunction(
new ConstraintIs(typeVar, SWRL.SAME_INDIVIDUALS_ATOM),
new ConstraintIs(typeVar, SWRL.DIFFERENT_INDIVIDUALS_ATOM))
);
Query query = createQuery(where, atomVar, typeVar, arg1Var, arg2Var);
Answer answer = doQuery(query);
try {
answer.beforeFirst();
while (answer.next()) {
Node atom = (Node)answer.getObject(atomVar.getName());
URIReference type = (URIReference)answer.getObject(typeVar.getName());
Object arg1Obj = answer.getObject(arg1Var.getName());
checkClass(arg1Obj, URIReference.class, "Arguments to identity atoms must be URI or variable references");
Object arg2Obj = answer.getObject(arg2Var.getName());
checkClass(arg2Obj, URIReference.class, "Arguments to identity atoms must be URI or variable references");
URIReference pred = null;
if (SWRL.SAME_INDIVIDUALS_ATOM.equals(type)) {
pred = new URIReferenceImpl(OWL.SAME_AS);
} else if (SWRL.DIFFERENT_INDIVIDUALS_ATOM.equals(type)) {
pred = new URIReferenceImpl(OWL.DIFFERENT_FROM);
} else {
throw new IllegalStateException("Unexpected type result for identiy atom: " + type);
}
constraintAtoms.put(atom, new Pair<URI,ConstraintImpl>(type.getURI(),
toConstraint((URIReference)arg1Obj, pred, (URIReference)arg2Obj, varMap)));
}
} finally {
answer.close();
}
}
/**
* Find all atoms in the rule graph of type <tt>swrl:DataRangeAtom</tt> and insert the
* resulting filters into the supplied map. Note that only named datatypes are supported.
* A data range atom has the form:
* <pre>
* _:x rdf:type swrl:DataRangeAtom .
* _:x swrl:dataRange xsd:integer .
* _:x swrl:argument1 rule:varX .
* </pre>
* and is mapped to the SPARQL filter clause: <tt>FILTER( datatype(?varX) = xsd:integer )</tt>.
* @param filterAtoms The map into which the filters are inserted.
* @param varMap The variable reference map which is used to construct the triple patterns.
*/
private void findDataRangeAtoms(Map<Node,Pair<URI,Filter>> filterAtoms, Map<URIReference,Variable> varMap)
throws QueryException, TuplesException, SWRLStructureException {
// select $atom $range $arg from <ruleGraph>
// where $atom <rdf:type> <swrl:DataRangeAtom> and $atom <swrl:dataRange> $range and $atom <swrl:argument1> $arg
Variable atomVar = new Variable("atom");
Variable rangeVar = new Variable("range");
Variable argVar = new Variable("arg");
ConstraintExpression where = new ConstraintConjunction(
new ConstraintImpl(atomVar, RDF_TYPE, SWRL.DATA_RANGE_ATOM),
new ConstraintImpl(atomVar, SWRL.DATA_RANGE, rangeVar),
new ConstraintImpl(atomVar, SWRL.ARG_1, argVar)
);
Query query = createQuery(where, atomVar, rangeVar, argVar);
Answer answer = doQuery(query);
try {
answer.beforeFirst();
while (answer.next()) {
Node atom = (Node)answer.getObject(atomVar.getName());
Object rangeObj = answer.getObject(rangeVar.getName());
checkClass(rangeObj, URIReference.class, "Data range atom must specify a named datatype or variable reference");
Node arg = (Node)answer.getObject(argVar.getName());
Filter filter = new Equals(toRdfTerm((URIReference)rangeObj, varMap), new DataTypeFn(toRdfTerm(arg, varMap)));
filterAtoms.put(atom, new Pair<URI,Filter>(SWRL.DATA_RANGE_ATOM.getURI(), filter));
}
} finally {
answer.close();
}
}
/**
* Find all atoms in the rule graph of type <tt>swrl:BuiltinAtom</tt> and insert the
* resulting filters into the supplied map. Currently unimplemented.
* @param filterAtoms The map into which the filters are inserted.
* @param varMap The variable reference map which is used to construct the triple patterns.
*/
private void findBuiltinAtoms(Map<Node,Pair<URI,Filter>> filterAtoms, Map<URIReference,Variable> varMap)
throws QueryException, TuplesException, SWRLStructureException {
// TODO Implement me.
}
/**
* Builds the rule identified by the given RDF node, and inserts it into the supplied
* rule structure. Rules handled by this method may be ordinary rules, consistency checks,
* or axioms, depending on the number of atoms in the body and head of the SWRL implication.
* @param ruleNode The RDF node that identifies the rule description to build.
* @param rules The rule structure to hold the new rule.
* @param constraintAtoms A pre-loaded map containing all triple-pattern atoms in the rule graph.
* @param filterAtoms A pre-loaded map containing all filter atoms in the rule graph.
*/
private void buildRule(Node ruleNode, RuleStructure rules,
Map<Node,Pair<URI,ConstraintImpl>> constraintAtoms, Map<Node,Pair<URI,Filter>> filterAtoms)
throws QueryException, TuplesException, SWRLStructureException {
if (logger.isDebugEnabled()) logger.debug("Building structure for rule: " + ruleNode);
Set<Node> bodyAtoms = getListMembers(ruleNode, SWRL.BODY);
if (logger.isDebugEnabled()) logger.debug("Found body atoms: " + bodyAtoms);
Set<Node> headAtoms = getListMembers(ruleNode, SWRL.HEAD);
if (logger.isDebugEnabled()) logger.debug("Found head atoms: " + headAtoms);
if (bodyAtoms.isEmpty() && headAtoms.isEmpty()) throw new SWRLStructureException("Rule must have at least one atom: " + ruleNode);
if (bodyAtoms.isEmpty()) {
// Empty body means it's an axiom
for (Node headAtom : headAtoms) {
rules.addAxiom(toAxiom(getHeadAtom(headAtom, constraintAtoms, filterAtoms)));
}
} else {
// Either a regular rule or a consistency check; both have queries.
ConstraintExpression whereClause = buildWhereClause(bodyAtoms, constraintAtoms, filterAtoms);
Rule rule = null;
List<SelectElement> selectVars = null;
if (headAtoms.isEmpty()) {
rule = new ConsistencyCheck(ruleNode.toString());
selectVars = new ArrayList<SelectElement>(whereClause.getVariables());
} else {
rule = new Rule(ruleNode.toString());
selectVars = getSelectElements(headAtoms, constraintAtoms, filterAtoms);
}
QueryStruct queryStruct = new QueryStruct(selectVars);
queryStruct.setWhereClause(whereClause);
queryStruct.setGraphExpression(baseGraph, destGraph);
try {
rule.setQueryStruct(queryStruct);
} catch (KruleStructureException e) {
throw new SWRLStructureException("Illegal query structure", e);
}
rules.add(rule);
}
}
/**
* Gets the triple pattern identified by the given atom node, or throws an exception if
* the given atom does not map to a triple pattern.
* @param node The RDF node representing an atom appearing in a rule head.
* @param constraintAtoms All triple-pattern atoms in the rule graph.
* @param filterAtoms All filter atoms in the rule graph.
* @return The triple pattern mapped to the given RDF node.
* @throws SWRLStructureException if the RDF node does not represent a triple pattern atom.
*/
private ConstraintImpl getHeadAtom(Node node, Map<Node,Pair<URI,ConstraintImpl>> constraintAtoms,
Map<Node,Pair<URI,Filter>> filterAtoms) throws SWRLStructureException {
if (constraintAtoms.containsKey(node)) {
return constraintAtoms.get(node).second();
} else if (filterAtoms.containsKey(node)) {
throw new SWRLStructureException("Atoms in rule head must map to triple patterns; found: " + filterAtoms.get(node).first());
} else {
throw new SWRLStructureException("Unable to map entry in rule head to an atom: " + node);
}
}
/**
* Converts a query triple pattern to an axiomatic statement.
* @param pattern A query triple pattern.
* @return The equivalent axiomatic statement.
* @throws SWRLStructureException if any position in the triple pattern contains a variable.
*/
private Triple toAxiom(ConstraintImpl pattern) throws SWRLStructureException {
ConstraintElement c = pattern.getElement(0);
if (c instanceof Variable) throw new SWRLStructureException("Axioms may not contain variables.");
SubjectNode s = (SubjectNode)c;
c = pattern.getElement(1);
if (c instanceof Variable) throw new SWRLStructureException("Axioms may not contain variables.");
PredicateNode p = (PredicateNode)c;
c = pattern.getElement(2);
if (c instanceof Variable) throw new SWRLStructureException("Axioms may not contain variables.");
ObjectNode o = (ObjectNode)c;
return new TripleImpl(s, p, o);
}
/**
* Builds the where clause for a query given a collection of atoms in a rule body.
* The atoms from the rule body may map to triple patterns or filters, but at least one
* atom must map to a triple pattern.
* @param bodyAtoms A collection of RDF nodes that are atoms in the body of a SWRL implication.
* @param constraintAtoms All triple-pattern atoms in the rule graph.
* @param filterAtoms All filter atoms in the rule graph.
* @return The where clause for a query that represents the rule body.
*/
private ConstraintExpression buildWhereClause(Set<Node> bodyAtoms, Map<Node,Pair<URI,ConstraintImpl>> constraintAtoms,
Map<Node,Pair<URI,Filter>> filterAtoms) throws SWRLStructureException {
List<ConstraintExpression> triplePatterns = new ArrayList<ConstraintExpression>();
List<Filter> filters = new ArrayList<Filter>();
for (Node bodyAtom : bodyAtoms) {
if (constraintAtoms.containsKey(bodyAtom)) {
triplePatterns.add(constraintAtoms.get(bodyAtom).second());
} else if (filterAtoms.containsKey(bodyAtom)) {
filters.add(filterAtoms.get(bodyAtom).second());
} else {
throw new SWRLStructureException("Unable to map entry in rule head to an atom: " + bodyAtom);
}
}
if (triplePatterns.isEmpty()) {
throw new SWRLStructureException("Rule body must contain at least one triple pattern.");
}
ConstraintExpression whereClause = new ConstraintConjunction(triplePatterns);
if (!filters.isEmpty()) {
Filter filter = (filters.size() == 1) ? filters.get(0) : new And(filters.toArray(new Filter[filters.size()]));
whereClause = new ConstraintFilter(whereClause, filter);
}
return whereClause;
}
/**
* Construct a list of select elements that are specified by triple patterns in
* the head of a SWRL rule. The select elements may be either variables or constant values.
* @param headNodes A collection of RDF nodes that comprise the atoms in the head of a SWRL rule.
* @param constraintAtoms All triple-pattern atoms in the rule graph.
* @param filterAtoms All filter atoms in the rule graph.
* @return The list of elements that represent statements to be entailed by the rule.
*/
private List<SelectElement> getSelectElements(Set<Node> headNodes, Map<Node,Pair<URI,ConstraintImpl>> constraintAtoms,
Map<Node,Pair<URI,Filter>> filterAtoms) throws SWRLStructureException {
List<SelectElement> selectElements = new ArrayList<SelectElement>();
for (Node headNode : headNodes) {
ConstraintImpl pattern = getHeadAtom(headNode, constraintAtoms, filterAtoms);
for (int i = 0; i < 3; i++) {
ConstraintElement e = pattern.getElement(i);
selectElements.add(e instanceof Variable ? (Variable)e : new ConstantValue(varFactory.newVariable(), (Value)e));
}
}
return selectElements;
}
/**
* Utility method to get all members of the RDF list whose head is the object of
* an RDF statement with the given subject and predicate.
* @param subject The subject of a statement which identifies the list.
* @param predicate The predicate of a statement which identifies the list.
* @return A collection of RDF nodes which comprise the given list.
*/
private Set<Node> getListMembers(Node subject, URIReferenceImpl predicate) throws QueryException, TuplesException {
// select $value from <ruleGraph>
// where <parent> <predicate> $head
// and (trans($head <rdf:rest> $node) or $head <rdf:rest> $node)
// and ($head <rdf:first> $value or $node <rdf:first> $value)
Variable valueVar = new Variable("value");
Variable headVar = new Variable("head");
Variable nodeVar = new Variable("node");
URIReferenceImpl rdfRest = new URIReferenceImpl(RDF.REST);
URIReferenceImpl rdfFirst = new URIReferenceImpl(RDF.FIRST);
ConstraintExpression where = new ConstraintConjunction(
new ConstraintImpl(toElement(subject), predicate, headVar),
new ConstraintDisjunction(
new SingleTransitiveConstraint(new ConstraintImpl(headVar, rdfRest, nodeVar)),
new ConstraintImpl(headVar, rdfRest, nodeVar)),
new ConstraintDisjunction(
new ConstraintImpl(headVar, rdfFirst, valueVar),
new ConstraintImpl(nodeVar, rdfFirst, valueVar))
);
Query query = createQuery(where, valueVar);
Answer answer = doQuery(query);
Set<Node> values = new HashSet<Node>();
try {
answer.beforeFirst();
while (answer.next()) values.add((Node)answer.getObject(valueVar.getName()));
} finally {
answer.close();
}
return values;
}
/**
* Detect and add all trigger dependencies between rules to the given rule structure.
* @param rules The rule structure for which to process triggers.
*/
private void processTriggers(RuleStructure rules) {
for (Iterator<Rule> it1 = rules.getRuleIterator(); it1.hasNext(); ) {
Rule trigger = it1.next();
for (Iterator<Rule> it2 = rules.getRuleIterator(); it2.hasNext(); ) {
Rule potentialTarget = it2.next();
if (triggersRule(trigger, potentialTarget)) trigger.addTriggerTarget(potentialTarget);
}
}
}
/**
* Determine if a rule triggers a potential terget.
* @param trigger The rule which may potentially trigger another rule.
* @param potentialTarget The potential target of the rule.
* @return <tt>true</tt> iff the given rule has a head atom which matches one of
* the body atoms of the potential target.
*/
private boolean triggersRule(Rule trigger, Rule potentialTarget) {
// Consistency checks never trigger other rules (they throw exceptions instead).
if (trigger instanceof ConsistencyCheck) return false;
List<SelectElement> products = trigger.getQuery().getVariableList();
if (products.size() % 3 != 0) throw new IllegalStateException("Invalid number of select elements for rule query: " + trigger.getQuery());
for (int index = 0; index < products.size(); index += 3) {
List<SelectElement> triggerProduct = products.subList(index, index + 3);
if (matchesExpression(triggerProduct, potentialTarget.getQuery().getConstraintExpression())) return true;
}
return false;
}
/**
* Determine whether the head atom represented by the product list matches any part of
* the given constraint expression.
* @param product A triple pattern appearing as the product of a rule.
* @param expr The body of a potential target rule.
* @return <tt>true</tt> iff the triple pattern matches any part of the potential target.
*/
private boolean matchesExpression(List<SelectElement> product, ConstraintExpression expr) {
assert product.size() == 3;
// Only triple patterns, conjunctions, and filters are created by the rule loader; ignore other
// constraint types.
if (expr instanceof ConstraintImpl) return matchesPattern(product, (ConstraintImpl)expr);
else if (expr instanceof ConstraintOperation) {
// An operation matches if any of the operands matches.
boolean matches = false;
for (ConstraintExpression operand : ((ConstraintOperation)expr).getElements()) {
matches = matches || matchesExpression(product, operand);
}
return matches;
} else if (expr instanceof ConstraintFilter) {
// Only the unfiltered part of a constraint can trigger a match (filters restrict the results from the unfiltered part).
return matchesExpression(product, ((ConstraintFilter)expr).getUnfilteredConstraint());
}
return false;
}
/**
* Determine whether a triple product in the head of one rule matches the triple pattern
* in the body of another rule.
* @param product A triple product.
* @param pattern A triple pattern.
* @return <tt>true</tt> if the product and pattern match at every position. A match is
* defined as both constants with the same value, or both variables (regardless of
* variable name).
*/
private boolean matchesPattern(List<SelectElement> product, ConstraintImpl pattern) {
assert product.size() == 3;
for (int i = 0; i < product.size(); i++) {
SelectElement productTerm = product.get(i);
if ((productTerm instanceof ConstantValue) && !(((ConstantValue)productTerm).getValue().equals(pattern.getElement(i)))) return false;
}
return true;
}
/**
* Utility method to create a query.
* @param constraintExpression The constraint expression making up the WHERE clause of the query.
* @param selection The variables to select in the query.
* @return The new query.
*/
@SuppressWarnings("unchecked")
private Query createQuery(ConstraintExpression constraintExpression, Variable... selection) {
List<Variable> selectList = Arrays.asList(selection);
return new Query(
selectList, // SELECT
ruleGraph, // FROM
constraintExpression, // WHERE
null, // HAVING
(List<Order>)Collections.EMPTY_LIST, // ORDER BY
null, // LIMIT
0, // OFFSET
true, // DISTINCT
UNCONSTRAINED // GIVEN
);
}
/**
* Local wrapper for querying on an OperationContext. Since {@link OperationContext#doQuery(Query)}
* throws an {@link Exception}, this is captured and wrapped in or cast to a {@link QueryException}.
* @param q The query to execute.
* @return The Answer to the query.
* @throws QueryException If the query fails.
*/
private Answer doQuery(Query q) throws QueryException {
try {
if (operationContext != null) return operationContext.doQuery(q);
throw new IllegalStateException("No environment to query the database in");
} catch (Exception e) {
if (e instanceof QueryException) throw (QueryException)e;
throw new QueryException("Unable to execute query", e);
}
}
/**
* Converts an RDF node into an RDF term used in a filter. Variable mappings from the
* rule graph are applied as part of this conversion.
* @param node A node from an RDF graph.
* @param varMap The variable mappings defined by the rule graph.
* @return The corresponding RDF term.
*/
private RDFTerm toRdfTerm(Node node, Map<URIReference,Variable> varMap) throws QueryException, SWRLStructureException {
if (varMap.containsKey(node)) return new Var(varMap.get(node).getName());
if (node instanceof URIReference) return new IRI(((URIReference)node).getURI());
if (node instanceof Literal) {
Literal l = (Literal)node;
return TypedLiteral.newLiteral(l.getLexicalForm(), l.getDatatypeURI(), l.getLanguage());
}
throw new SWRLStructureException("RDF term in an atom must be a URI, literal, or variable reference");
}
/**
* Constructs a query triple pattern from a subject, predicate, and object RDF node.
* Variable mappings from the rule graph are applied as part of this operation.
* @param s The subject URI reference.
* @param p The predicate URI reference.
* @param o The object node.
* @param varMap The variable mappings defined by the rule graph.
* @return The corresponding triple pattern.
*/
private ConstraintImpl toConstraint(URIReference s, URIReference p, Node o, Map<URIReference,Variable> varMap) throws SWRLStructureException {
ConstraintElement subject = varMap.containsKey(s) ? varMap.get(s): (s instanceof URIReferenceImpl ? (URIReferenceImpl)s : new URIReferenceImpl(s.getURI()));
ConstraintElement predicate = varMap.containsKey(p) ? varMap.get(p): (p instanceof URIReferenceImpl ? (URIReferenceImpl)p : new URIReferenceImpl(p.getURI()));
ConstraintElement object = null;
if (o instanceof URIReference) {
URIReference oUri = (URIReference)o;
object = varMap.containsKey(oUri) ? varMap.get(oUri) : (oUri instanceof URIReferenceImpl ? (URIReferenceImpl)oUri : new URIReferenceImpl(oUri.getURI()));
} else if (o instanceof Literal) {
object = toLiteralElement((Literal)o);
} else {
throw new SWRLStructureException("Object of a triple pattern must be a URI, literal, or variable reference; found: " + o);
}
return new ConstraintImpl(subject, predicate, object);
}
/**
* Converts a JRDF node to a query constraint element.
* @param node The JRDF node.
* @return The query constraint element.
*/
private ConstraintElement toElement(Node node) {
if (node instanceof ConstraintElement) return (ConstraintElement)node;
if (node instanceof URIReference) {
return new URIReferenceImpl(((URIReference)node).getURI());
} else if (node instanceof Literal) {
return toLiteralElement((Literal)node);
} else if (node instanceof BlankNode) {
return new VariableNodeImpl(((BlankNode)node).getID());
}
throw new IllegalArgumentException("Unable to convert to constraint element: " + node);
}
/**
* Converts a JRDF literal node to a query constraint element.
* @param lit The JRDF literal node.
* @return The query constraint element.
*/
private LiteralImpl toLiteralElement(Literal lit) {
if (lit instanceof LiteralImpl) return (LiteralImpl)lit;
else if (lit.getDatatypeURI() != null) return new LiteralImpl(lit.getLexicalForm(), lit.getDatatypeURI());
else if (lit.getLanguage() != null && lit.getLanguage().length() > 0) return new LiteralImpl(lit.getLexicalForm(), lit.getLanguage());
else return new LiteralImpl(lit.getLexicalForm());
}
/**
* Utility method to do type checking on an object extracted from query results.
* @param <T> The expected class.
* @param obj The object that was extracted from a query answer.
* @param expected The expected class of the object.
* @param msg The message to use when throwing an exception if the class doesn't match.
* @throws SWRLStructureException if the actual class of the object doesn't match the expected class.
*/
private <T> void checkClass(Object obj, Class<T> expected, String msg) throws SWRLStructureException {
if (!expected.isAssignableFrom(obj.getClass())) {
throw new SWRLStructureException(msg);
}
}
}