/*
* 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.jena.reasoner.rulesys;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import org.apache.jena.graph.Factory;
import org.apache.jena.graph.Graph;
import org.apache.jena.rdf.model.Property;
import org.apache.jena.rdf.model.Resource;
import org.apache.jena.rdf.model.Statement;
import org.apache.jena.reasoner.IllegalParameterException;
import org.apache.jena.reasoner.InfGraph;
import org.apache.jena.reasoner.Reasoner;
import org.apache.jena.reasoner.ReasonerException;
import org.apache.jena.reasoner.ReasonerFactory;
import org.apache.jena.reasoner.rulesys.impl.LPRuleStore;
import org.apache.jena.reasoner.rulesys.impl.OWLRuleTranslationHook;
import org.apache.jena.vocabulary.ReasonerVocabulary;
/**
* A reasoner interface that is able to invoke any of the useful
* rule engine combinations. The rule set can be set after the reasoner
* instance is created. The mode can be set to forward, backward or hybrid.
* The OWL-specific rule augmentation can be included. Each of these settings
* can be controlled using the configuration graph, specific methods calls or
* generic setParameter calls.
*/
public class GenericRuleReasoner extends FBRuleReasoner {
/** Prepared set of rules used for Backward-only mode */
protected LPRuleStore bRuleStore;
/** the current rule mode */
protected RuleMode mode = HYBRID;
/** Flag, if true we cache the closure of the pure rule set with its axioms */
@SuppressWarnings("hiding")
protected static final boolean cachePreload = true;
/** Flag, if true then subClass and subProperty lattices will be optimized using TGCs, only applicable to HYBRID reasoners */
protected boolean enableTGCCaching = false;
/** Flag, if true then rules will be augmented by OWL translations of the schema */
protected boolean enableOWLTranslation = false;
/** Optional set of preprocessing hooks to be run in sequence during preparation time, only applicable to HYBRID modes */
protected HashSet<RulePreprocessHook> preprocessorHooks;
/** Flag, if true then find results will be filtered to remove functors and illegal RDF */
public boolean filterFunctors = true;
/** A prebuilt copy of the OWL translation hook */
private static final OWLRuleTranslationHook owlTranslator = new OWLRuleTranslationHook();
/** Constant - the mode description for pure forward chaining */
public static final RuleMode FORWARD = new RuleMode("forward");
/** Constant - the mode description for pure forward chaining, using RETE engine */
public static final RuleMode FORWARD_RETE = new RuleMode("forwardRETE");
/** Constant - the mode description for pure backward chaining */
public static final RuleMode BACKWARD = new RuleMode("backward");
/** Constant - the mode description for mixed forward/backward, this is the default mode */
public static final RuleMode HYBRID = new RuleMode("hybrid");
// =======================================================================
// Constructors
/**
* Constructor. This is the raw version that does not reference a ReasonerFactory
* and so has no capabilities description.
* @param rules a list of Rule instances which defines the ruleset to process
*/
public GenericRuleReasoner(List<Rule> rules) {
super(rules);
}
/**
* Constructor
* @param factory the parent reasoner factory which is consulted to answer capability questions
* @param configuration RDF node to configure the rule set and mode, can be null
*/
public GenericRuleReasoner(ReasonerFactory factory, Resource configuration) {
super(factory);
this.configuration = configuration;
if (configuration != null) loadConfiguration( configuration );
}
/**
* Constructor
* @param rules a list of Rule instances which defines the ruleset to process
* @param factory the parent reasoner factory which is consulted to answer capability questions
*/
public GenericRuleReasoner(List<Rule> rules, ReasonerFactory factory) {
super(rules, factory);
}
/**
* Internal constructor, used to generated a partial binding of a schema
* to a rule reasoner instance.
*/
protected GenericRuleReasoner(List<Rule> rules, Graph schemaGraph, ReasonerFactory factory, RuleMode mode) {
this(rules, factory);
this.schemaGraph = schemaGraph;
this.mode = mode;
}
// =======================================================================
// Parameter control
/**
* Set the direction of rule inference desired.
* If set to a pure mode (FORWARD, BACKWARD) then the rules will be
* interpreted as operating in that direction which ever direction
* they were written in. In HYBRID mode then the direction of the rule
* itself which control whether it is used in forward or backward mode.
* In addition, HYBRID mode allows forward rules to generate addition
* backward rules.
*/
public void setMode(RuleMode mode) {
if (schemaGraph != null) {
throw new ReasonerException("Can't change mode of a reasoner bound to a schema");
}
this.mode = mode;
preload = null;
bRuleStore = null;
}
/**
* Set (or change) the rule set that this reasoner should execute.
* This will not affect inference models already created from this reasoner.
* @param rules a list of Rule objects
*/
@Override
public void setRules(List<Rule> rules) {
// Currently redunant but it will differ from the inherited
// version in the future
super.setRules(rules);
}
/**
* Set to true to enable translation of selected parts of an OWL schema
* to additional rules. At present only intersction statements are handled this way.
* The translation is only applicable in HYBRID mode.
*/
public void setOWLTranslation(boolean enableOWLTranslation) {
if (enableOWLTranslation && (mode != HYBRID)) {
throw new ReasonerException("Can only enable OWL rule translation in HYBRID mode");
}
this.enableOWLTranslation = enableOWLTranslation;
if (enableOWLTranslation) {
addPreprocessingHook(owlTranslator);
} else {
removePreprocessingHook(owlTranslator);
}
}
/**
* Set to true to enable caching of subclass/subproperty lattices in a
* specialized cache rather than using the rule systems. This has substantially
* higher performance but it is done as a separate initialization pass and so
* can only work correct with some rule sets. This is only guaranteed to be implemented
* for the HYBRID mode.
*/
public void setTransitiveClosureCaching(boolean enableTGCCaching) {
this.enableTGCCaching = enableTGCCaching;
}
/**
* Set to true to cause functor-valued literals to be dropped from rule output.
* Default is true.
*/
public void setFunctorFiltering(boolean param) {
filterFunctors = param;
}
/**
* Add a new preprocessing hook defining an operation that
* should be run when the inference graph is being prepared. This can be
* used to generate additional data-dependent rules or translations.
* This is only guaranted to be implemented for the HYBRID mode.
*/
public void addPreprocessingHook(RulePreprocessHook hook) {
if (preprocessorHooks == null) {
preprocessorHooks = new HashSet<>();
}
preprocessorHooks.add(hook);
}
/**
* Remove a preprocessing hook. defining an operation that
*/
public void removePreprocessingHook(RulePreprocessHook hook) {
if (preprocessorHooks != null) {
preprocessorHooks.remove(hook);
}
}
@Override
protected boolean doSetResourceParameter( Property parameter, Resource value )
{
if (isRuleSetURL( parameter ))
addRules( Rule.rulesFromURL( value.getURI() ) );
else if (isRuleSet( parameter ))
{
addRulesFromURLs( value );
addRulesFromStrings( value );
}
else
return false;
return true;
}
private void addRulesFromStrings( Resource value )
{
Iterator<Statement> it = getHasRuleStatements( value );
while (it.hasNext()) addRuleFromString( it.next().getString() );
}
private void addRuleFromString( String ruleString )
{ addRules( Rule.parseRules( ruleString ) ); }
private void addRulesFromURLs( Resource value )
{
Iterator<Statement> that = getRuleSetURLStatements( value );
while (that.hasNext()) addRules( Rule.rulesFromURL( that.next().getResource().getURI() ) );
}
private Iterator<Statement> getHasRuleStatements( Resource value )
{
return value.listProperties( ReasonerVocabulary.hasRule );
}
private Iterator<Statement> getRuleSetURLStatements( Resource value )
{
return value.listProperties( ReasonerVocabulary.ruleSetURL );
}
private boolean isHasRule( Property parameter )
{
return parameter.equals( ReasonerVocabulary.hasRule );
}
private boolean isRuleSet( Property parameter )
{
return parameter.equals( ReasonerVocabulary.ruleSet );
}
private boolean isRuleSetURL( Property parameter )
{
return parameter.equals( ReasonerVocabulary.ruleSetURL );
}
/**
* Internal version of setParameter that does not directly raise an
* exception on parameters it does not reconize.
* @return false if the parameter was not recognized
*/
@Override
protected boolean doSetParameter(Property parameter, Object value) {
if (parameter.equals(ReasonerVocabulary.PROPenableFunctorFiltering)) {
filterFunctors = Util.convertBooleanPredicateArg(parameter, value);
} else if (isHasRule( parameter )) {
addRuleFromString( value.toString() );
} else if (parameter.equals(ReasonerVocabulary.PROPenableOWLTranslation)) {
enableOWLTranslation = Util.convertBooleanPredicateArg(parameter, value);
if (enableOWLTranslation) {
addPreprocessingHook(owlTranslator);
} else {
removePreprocessingHook(owlTranslator);
}
} else if (parameter.equals(ReasonerVocabulary.PROPenableTGCCaching)) {
enableTGCCaching = Util.convertBooleanPredicateArg(parameter, value);
} else if (parameter.equals(ReasonerVocabulary.PROPruleMode)) {
if (value.equals(FORWARD.name)) {
mode = FORWARD;
} else if (value.equals(FORWARD_RETE.name)) {
mode = FORWARD_RETE;
} else if (value.equals(BACKWARD.name)) {
mode = BACKWARD;
} else if (value.equals(HYBRID.name)) {
mode = HYBRID;
} else {
throw new IllegalParameterException("PROPruleMode can only be 'forward'm 'forwardRETE', 'backward', 'hybrid', not " + value);
}
} else if (parameter.equals(ReasonerVocabulary.PROPruleSet)) {
if (value instanceof String) {
addRules( loadRules( (String)value ) );
} else {
throw new IllegalParameterException("PROPruleSet value should be a URI string. Was a " + value.getClass());
}
} else {
return super.doSetParameter( parameter, value );
}
return true;
}
// =======================================================================
// Implementation methods
/**
* Precompute the implications of a schema graph. The statements in the graph
* will be combined with the data when the final InfGraph is created.
*/
@Override
public Reasoner bindSchema(Graph tbox) throws ReasonerException {
if (schemaGraph != null) {
throw new ReasonerException("Can only bind one schema at a time to a GenericRuleReasoner");
}
Graph graph = null;
if (mode == FORWARD) {
graph = new BasicForwardRuleInfGraph(this, rules, null, tbox);
((InfGraph)graph).prepare();
} else if (mode == FORWARD_RETE) {
graph = new RETERuleInfGraph(this, rules, null, tbox);
((InfGraph)graph).prepare();
} else if (mode == BACKWARD) {
graph = tbox;
} else {
List<Rule> ruleSet = rules;
graph = new FBRuleInfGraph(this, ruleSet, getPreload(), tbox);
if (enableTGCCaching) ((FBRuleInfGraph)graph).setUseTGCCache();
((FBRuleInfGraph)graph).prepare();
}
GenericRuleReasoner grr = new GenericRuleReasoner(rules, graph, factory, mode);
grr.setDerivationLogging(recordDerivations);
grr.setTraceOn(traceOn);
grr.setTransitiveClosureCaching(enableTGCCaching);
grr.setFunctorFiltering(filterFunctors);
if (preprocessorHooks != null) {
for ( RulePreprocessHook preprocessorHook : preprocessorHooks )
{
grr.addPreprocessingHook( preprocessorHook );
}
}
return grr;
}
/**
* Attach the reasoner to a set of RDF data to process.
* The reasoner may already have been bound to specific rules or ontology
* axioms (encoded in RDF) through earlier bindRuleset calls.
*
* @param data the RDF data to be processed, some reasoners may restrict
* the range of RDF which is legal here (e.g. syntactic restrictions in OWL).
* @return an inference graph through which the data+reasoner can be queried.
* @throws ReasonerException if the data is ill-formed according to the
* constraints imposed by this reasoner.
*/
@Override
public InfGraph bind(Graph data) throws ReasonerException {
Graph schemaArg = schemaGraph == null ? getPreload() : schemaGraph;
InfGraph graph = null;
if (mode == FORWARD) {
graph = new BasicForwardRuleInfGraph(this, rules, schemaArg);
((BasicForwardRuleInfGraph)graph).setTraceOn(traceOn);
} else if (mode == FORWARD_RETE) {
graph = new RETERuleInfGraph(this, rules, schemaArg);
((BasicForwardRuleInfGraph)graph).setTraceOn(traceOn);
((BasicForwardRuleInfGraph)graph).setFunctorFiltering(filterFunctors);
} else if (mode == BACKWARD) {
graph = new LPBackwardRuleInfGraph(this, getBruleStore(), data, schemaArg);
((LPBackwardRuleInfGraph)graph).setTraceOn(traceOn);
} else {
List<Rule> ruleSet = ((FBRuleInfGraph)schemaArg).getRules();
FBRuleInfGraph fbgraph = new FBRuleInfGraph(this, ruleSet, schemaArg);
graph = fbgraph;
if (enableTGCCaching) fbgraph.setUseTGCCache();
fbgraph.setTraceOn(traceOn);
fbgraph.setFunctorFiltering(filterFunctors);
if (preprocessorHooks!= null) {
for ( RulePreprocessHook preprocessorHook : preprocessorHooks )
{
fbgraph.addPreprocessingHook( preprocessorHook );
}
}
}
graph.setDerivationLogging(recordDerivations);
graph.rebind(data);
return graph;
}
/**
* Get the single static precomputed rule closure.
*/
@Override
protected synchronized InfGraph getPreload() {
// We only support this in HYBRID mode
if (cachePreload && preload == null && mode == HYBRID) {
preload = new FBRuleInfGraph( this, rules, null, Factory.createDefaultGraph() );
if (enableTGCCaching) ((FBRuleInfGraph)preload).setUseTGCCache();
preload.prepare();
}
return preload;
}
/**
* Return the prepared backward only rules.
*/
protected LPRuleStore getBruleStore() {
if (bRuleStore == null) {
bRuleStore = new LPRuleStore(rules);
}
return bRuleStore;
}
// =======================================================================
// Inner classes
/**
* Class used as an enum for describing rule modes.
*/
public static class RuleMode {
/** Name for the mode */
String name;
/** Constructor */
protected RuleMode(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
}
}