/* * 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.Iterator; import java.util.List; import org.apache.jena.graph.Factory ; import org.apache.jena.graph.Graph ; import org.apache.jena.graph.Node ; import org.apache.jena.graph.Triple ; import org.apache.jena.reasoner.* ; import org.apache.jena.reasoner.rulesys.impl.FRuleEngineI ; import org.apache.jena.reasoner.rulesys.impl.FRuleEngineIFactory ; import org.apache.jena.reasoner.rulesys.impl.SafeGraph ; import org.apache.jena.util.OneToManyMap ; import org.apache.jena.util.iterator.ExtendedIterator ; import org.apache.jena.util.iterator.NullIterator ; /** * An inference graph interface that runs a set of forward chaining * rules to conclusion on each added triple and stores the entire * result set. * <p> * This implementation has a horribly inefficient rule chainer built in. * Once we have this working generalize this to an interface than * can call out to a rule engine and build a real rule engine (e.g. Rete style). </p> */ public class BasicForwardRuleInfGraph extends BaseInfGraph implements ForwardRuleInfGraphI { /** Table of derivation records, maps from triple to RuleDerivation */ protected OneToManyMap<Triple, Derivation> derivations; /** The set of deduced triples, this is in addition to base triples in the fdata graph */ protected FGraph fdeductions; /** A safe wrapped version of the deductions graph used for reporting getDeductions */ protected Graph safeDeductions; /** Reference to any schema graph data bound into the parent reasoner */ protected Graph schemaGraph; /** The forward rule engine being used */ protected FRuleEngineI engine; /** The original rule set as supplied */ private List<Rule> rules; /** Flag, if true then find results will be filtered to remove functors and illegal RDF */ public boolean filterFunctors = true; /** Flag which, if true, enables tracing of rule actions to logger.info */ protected boolean traceOn = false; // private static Logger logger = LoggerFactory.getLogger(BasicForwardRuleInfGraph.class); //======================================================================= // Core methods /** * Constructor. Creates a new inference graph to which a (compiled) rule set * and a data graph can be attached. This separation of binding is useful to allow * any configuration parameters (such as logging) to be set before the data is added. * Note that until the data is added using {@link #rebind rebind} then any operations * like add, remove, find will result in errors. * * @param reasoner the parent reasoner * @param schema the (optional) schema data which is being processed */ public BasicForwardRuleInfGraph(Reasoner reasoner, Graph schema) { super(null, reasoner); instantiateRuleEngine(null); this.schemaGraph = schema; } /** * Constructor. Creates a new inference graph based on the given rule set. * No data graph is attached at this stage. This is to allow * any configuration parameters (such as logging) to be set before the data is added. * Note that until the data is added using {@link #rebind rebind} then any operations * like add, remove, find will result in errors. * * @param reasoner the parent reasoner * @param rules the list of rules to use this time * @param schema the (optional) schema or preload data which is being processed */ public BasicForwardRuleInfGraph(Reasoner reasoner, List<Rule> rules, Graph schema) { super( null, reasoner ); instantiateRuleEngine( rules ); this.rules = rules; this.schemaGraph = schema; } /** * Constructor. Creates a new inference graph based on the given rule set * then processes the initial data graph. No precomputed deductions are loaded. * * @param reasoner the parent reasoner * @param rules the list of rules to use this time * @param schema the (optional) schema or preload data which is being processed * @param data the data graph to be processed */ public BasicForwardRuleInfGraph(Reasoner reasoner, List<Rule> rules, Graph schema, Graph data) { this(reasoner, rules, schema); rebind(data); } /** * Instantiate the forward rule engine to use. * Subclasses can override this to switch to, say, a RETE imlementation. * @param rules the rule set or null if there are not rules bound in yet. */ protected void instantiateRuleEngine(List<Rule> rules) { engine = FRuleEngineIFactory.getInstance().createFRuleEngineI(this, rules, false); } /** * Attach a compiled rule set to this inference graph. * @param ruleStore a compiled set of rules (i.e. the result of an FRuleEngine.compile). */ public void setRuleStore(Object ruleStore) { engine.setRuleStore(ruleStore); } /** * Replace the underlying data graph for this inference graph and start any * inferences over again. This is primarily using in setting up ontology imports * processing to allow an imports multiunion graph to be inserted between the * inference graph and the raw data, before processing. * @param data the new raw data graph */ @Override public void rebind(Graph data) { fdata = new FGraph( data ); rebind(); } /** * Cause the inference graph to reconsult the underlying graph to take * into account changes. Normally changes are made through the InfGraph's add and * remove calls are will be handled appropriately. However, in some cases changes * are made "behind the InfGraph's back" and this forces a full reconsult of * the changed data. */ @Override public void rebind() { version++; this.setPreparedState(false); } /** * Return the schema graph, if any, bound into this inference graph. */ @Override public Graph getSchemaGraph() { return schemaGraph; } /** * Perform any initial processing and caching. This call is optional. Most * engines either have negligable set up work or will perform an implicit * "prepare" if necessary. The call is provided for those occasions where * substantial preparation work is possible (e.g. running a forward chaining * rule system) and where an application might wish greater control over when * this prepration is done. */ @Override public synchronized void prepare() { if (this.isPrepared()) return; this.setPreparedState(true); // initilize the deductions graph fdeductions = new FGraph( createDeductionsGraph() ); boolean rulesLoaded = false; if (schemaGraph != null) { rulesLoaded = preloadDeductions(schemaGraph); } if (rulesLoaded) { engine.fastInit(fdata); } else { engine.init(true, fdata); } } /** * Adds a set of precomputed triples to the deductions store. These do not, themselves, * fire any rules but provide additional axioms that might enable future rule * firing when real data is added. Used to implement bindSchema processing * in the parent Reasoner. * @return return true if the rule set has also been loaded */ protected boolean preloadDeductions(Graph preloadIn) { Graph d = fdeductions.getGraph(); BasicForwardRuleInfGraph preload = (BasicForwardRuleInfGraph) preloadIn; // If the rule set is the same we can reuse those as well if (preload.rules == rules) { // Load raw deductions for (Iterator<Triple> i = preload.find(null, null, null); i.hasNext(); ) { d.add(i.next()); } engine.setRuleStore(preload.engine.getRuleStore()); return true; } else { return false; } } /** * Add a new deduction to the deductions graph. */ @Override public void addDeduction(Triple t) { getDeductionsGraph().add(t); } /** * Set to true to cause functor-valued literals to be dropped from rule output. * Default is true. */ @Override public void setFunctorFiltering(boolean param) { filterFunctors = param; } /** * Extended find interface used in situations where the implementator * may or may not be able to answer the complete query. It will * attempt to answer the pattern but if its answers are not known * to be complete then it will also pass the request on to the nested * Finder to append more results. * @param pattern a TriplePattern to be matched against the data * @param continuation either a Finder or a normal Graph which * will be asked for additional match results if the implementor * may not have completely satisfied the query. */ @Override public ExtendedIterator<Triple> findWithContinuation(TriplePattern pattern, Finder continuation) { return findWithContinuation(pattern, continuation, true); } /** * Internals of findWithContinuation implementation which allows control * over functor filtering. */ private ExtendedIterator<Triple> findWithContinuation(TriplePattern pattern, Finder continuation, boolean filter) { checkOpen(); this.requirePrepared(); ExtendedIterator<Triple> result = null; if (fdata == null) { result = fdeductions.findWithContinuation(pattern, continuation); } else { if (continuation == null) { result = fdata.findWithContinuation(pattern, fdeductions); } else { result = fdata.findWithContinuation(pattern, FinderUtil.cascade(fdeductions, continuation) ); } } if (filter && filterFunctors) { return result.filterDrop(Functor.acceptFilter); } else { return result; } } /** * Returns an iterator over Triples. * This implementation assumes that the underlying findWithContinuation * will have also consulted the raw data. */ @Override protected ExtendedIterator<Triple> graphBaseFind(Node subject, Node property, Node object) { return findWithContinuation(new TriplePattern(subject, property, object), null); } /** * Basic pattern lookup interface. * This implementation assumes that the underlying findWithContinuation * will have also consulted the raw data. * @param pattern a TriplePattern to be matched against the data * @return a ExtendedIterator over all Triples in the data set * that match the pattern */ @Override public ExtendedIterator<Triple> find(TriplePattern pattern) { return findWithContinuation(pattern, null); } /** * Add one triple to the data graph, run any rules triggered by * the new data item, recursively adding any generated triples. */ @Override public synchronized void performAdd(Triple t) { version++; fdata.getGraph().add(t); if (this.isPrepared()) { engine.add(t); } } /** * Return the number of triples in the inferred graph */ @Override public int graphBaseSize() { checkOpen(); this.requirePrepared(); int baseSize = fdata.getGraph().size(); int dedSize = fdeductions.getGraph().size(); // System.err.println( ">> BasicForwardRuleInfGraph::size = " + baseSize + "(base) + " + dedSize + "(deductions)" ); return baseSize + dedSize; } /** * Removes the triple t (if possible) from the set belonging to this graph. */ @Override public void performDelete(Triple t) { version++; if (fdata != null) { Graph data = fdata.getGraph(); if (data != null) { data.delete(t); } } if (!this.isPrepared()) { fdeductions.getGraph().delete(t); } } /** * Free all resources, any further use of this Graph is an error. */ @Override public void close() { if (!closed) { engine = null; fdeductions = null; rules = null; schemaGraph = null; super.close(); } } // ======================================================================= // Implementation of ForwardRuleInfGraphI interface which is used by // the forward rule engine to invoke functions in this InfGraph /** * Adds a new Backward rule as a rules of a forward rule process. Only some * infgraphs support this. */ @Override public void addBRule(Rule brule) { throw new ReasonerException("Forward reasoner does not support hybrid rules - " + brule.toShortString()); } /** * Deletes a new Backward rule as a rules of a forward rule process. Only some * infgraphs support this. */ @Override public void deleteBRule(Rule brule) { throw new ReasonerException("Forward reasoner does not support hybrid rules - " + brule.toShortString()); } /** * Return the Graph containing all the static deductions available so far. * Will force a prepare. */ @Override public Graph getDeductionsGraph() { prepare(); return safeDeductions; } /** * Create the graph used to hold the deductions. Can be overridden * by subclasses that need special purpose graph implementations here. * Assumes the graph underlying fdeductions and associated SafeGraph * wrapper can be reused if present thus enabling preservation of * listeners. */ protected Graph createDeductionsGraph() { if (fdeductions != null) { Graph dg = fdeductions.getGraph(); if (dg != null) { // Reuse the old graph in order to preserve any listeners safeDeductions.clear(); return dg; } } Graph dg = Factory.createGraphMem( ); safeDeductions = new SafeGraph( dg ); return dg; } /** * Return the Graph containing all the static deductions available so far. * Does not trigger a prepare action. Returns a SafeWrapper and so * can be used for update (thus triggering listeners) but not * for access to generalized triples */ @Override public Graph getCurrentDeductionsGraph() { return safeDeductions; // return fdeductions.getGraph(); } /** * Search the combination of data and deductions graphs for the given triple pattern. * This may different from the normal find operation in the base of hybrid reasoners * where we are side-stepping the backward deduction step. */ @Override public ExtendedIterator<Triple> findDataMatches(Node subject, Node predicate, Node object) { return findWithContinuation(new TriplePattern(subject, predicate, object), null, false); } /** * Log a dervivation record against the given triple. */ @Override public void logDerivation(Triple t, Derivation derivation) { derivations.put(t, derivation); } /** * Assert a new triple in the deduction graph, bypassing any processing machinery. */ @Override public void silentAdd(Triple t) { fdeductions.getGraph().add(t); } //======================================================================= // support for proof traces /** * Set to true to enable derivation caching */ @Override public void setDerivationLogging(boolean recordDerivations) { this.recordDerivations = recordDerivations; engine.setDerivationLogging(recordDerivations); if (recordDerivations) { derivations = new OneToManyMap<>(); } else { derivations = null; } } /** * Return true if derivation logging is enabled. */ @Override public boolean shouldLogDerivations() { return recordDerivations; } /** * Return the derivation of at triple. * The derivation is a List of DerivationRecords */ @Override public Iterator<Derivation> getDerivation(Triple t) { if (derivations == null) { return new NullIterator<>(); } else { return derivations.getAll(t); } } /** * Set the state of the trace flag. If set to true then rule firings * are logged out to the Log at "INFO" level. */ public void setTraceOn(boolean state) { traceOn = state; } /** * Return true if tracing should be acted on - i.e. if traceOn is true * and we are past the bootstrap phase. */ @Override public boolean shouldTrace() { return traceOn && engine.shouldTrace(); } /** * Return the number of rules fired since this rule engine instance * was created and initialized */ public long getNRulesFired() { return engine.getNRulesFired(); } }