/* * Copyright 2008 Fedora Commons, Inc. * * 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.krule; // Java 2 standard packages import java.net.URI; import java.io.Serializable; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import org.apache.log4j.Logger; import org.mulgara.query.rdf.URIReferenceImpl; import org.mulgara.query.QueryException; import org.mulgara.query.TuplesException; import org.mulgara.resolver.OperationContext; import org.mulgara.resolver.spi.LocalizeException; import org.mulgara.resolver.spi.Resolver; import org.mulgara.resolver.spi.ResolverException; import org.mulgara.resolver.spi.ResolverSession; import org.mulgara.resolver.spi.Statements; import org.mulgara.resolver.spi.SystemResolver; import org.mulgara.resolver.spi.TripleSetWrapperStatements; import org.mulgara.rules.InitializerException; import org.mulgara.rules.Rules; import org.mulgara.rules.RulesException; import org.mulgara.server.Session; /** * Represents a structure of rules. * * @created 2005-5-16 * @author <a href="mailto:pgearon@users.sourceforge.net">Paula Gearon</a> * @version $Revision: 1.3 $ * @modified $Date: 2005/07/03 12:53:41 $ * @maintenanceAuthor $Author: pgearon $ * @copyright © 2005 <a href="http://www.fedora-commons.org/">Fedora Commons</a> */ public class RuleStructure implements Rules, Serializable { private static final long serialVersionUID = 7891222973611830607L; /** Used to indicate that a gNode is not configured */ static final long UNINITIALIZED = -1; /** Logger. */ private static final Logger logger = Logger.getLogger(RuleStructure.class.getName()); /** The rules in the framework. */ private Set<Rule> rules; /** Map of rule names to the rule. */ private Map<String,Rule> ruleMap; /** The URI of the target graph to contain the entailments. */ private URI targetGraphURI; /** The target graph to contain the entailments. */ private long targetGraph = UNINITIALIZED; /** The current list of rules that have to be run. */ private LinkedHashSet<Rule> runQueue; /** The set of axioms pertinent to these rules. */ private Set<org.jrdf.graph.Triple> axioms; /** * Principle constructor. */ public RuleStructure() { rules = new HashSet<Rule>(); ruleMap = new HashMap<String,Rule>(); runQueue = new LinkedHashSet<Rule>(); axioms = null; } /** * Adds a new rule to the rule set. * * @param rule The rule to be added. */ public void add(Rule rule) { rules.add(rule); ruleMap.put(rule.getName(), rule); rule.setRuleStruct(this); } /** * Links a pair of rules for triggering. * * @param src The name of the trigger generating rule. * @param dest The name of the rule which is triggered. */ public void setTrigger(String src, String dest) throws InitializerException { // get the rules Rule srcRule = ruleMap.get(src); Rule destRule = ruleMap.get(dest); // check that the rules exist if (srcRule == null || destRule == null) { throw new InitializerException("Nonexistent rule: " + srcRule == null ? src : dest); } // set the link srcRule.addTriggerTarget(destRule); } /** * Sets a set of axioms for these rules. * * @param axioms A {@link java.util.Set} of {@link org.jrdf.graph.Triple}s * comprising axiomatic statements. */ public void setAxioms(Set<org.jrdf.graph.Triple> axioms) { this.axioms = axioms; } /** * Adds a single axiom to these rules. * * @param axiom A triple comprising an axiomatic statement. */ public void addAxiom(org.jrdf.graph.Triple axiom) { if (axioms == null) axioms = new HashSet<org.jrdf.graph.Triple>(); axioms.add(axiom); } /** * Gets the number of axioms for these rules. * * @return The number of axiomatic statements. */ public int getAxiomCount() { return axioms == null ? 0 : axioms.size(); } /** * Returns the number of rules. * * @return The number of rules. */ public int getRuleCount() { return rules.size(); } /** * Returns an iterator for the rules. * * @return An iterator for the rules. */ public Iterator<Rule> getRuleIterator() { return rules.iterator(); } /** * Debug method to view the contents of a rule structure. * * @return A string representation of this structure. */ public String toString() { String result = "Rules = {\n"; for (Rule r: rules) result += r.getName() + "\n"; result += "}"; return result; } /** * Set the target model for the rules. This will get localized when the rules are run. * * @param target The URI of the target graph to insert inferences into. */ public void setTargetModel(URI target) { targetGraphURI = target; } /** * Starts the rules engine. This is a breadth first evaluation engine. * This means that any triggered rules are scheduled for evaluation, * and are not run immediately. * * @param params An array containing the transactionally controlled {@link OperationContext} * and {@link SystemResolver} to use. This is not a structured parameter as it will * eventually drop back to a single {@link Session} parameter like it used to be. */ public void run(Object params) throws RulesException { logger.debug("Run called"); validateParams(params); // set up the operating parameters OperationContext context = (OperationContext)((Object[])params)[0]; SystemResolver systemResolver = (SystemResolver)((Object[])params)[1]; // determine the graph to insert into, and the resolver for that graph localizeRuleTarget(context, systemResolver); Resolver resolver = extractTargetResolver(context); // set up the run queue runQueue = new LinkedHashSet<Rule>(rules); // fill the run queue runQueue.addAll(rules); Rule currentRule = null; try { // start by inserting the axioms insertAxioms(resolver, systemResolver); // process the queue while (runQueue.size() > 0) { // get the first rule from the queue currentRule = popRunQueue(); logger.debug("Executing rule: " + currentRule); // execute the rule currentRule.execute(context, resolver, systemResolver); } } catch (TuplesException te) { logger.error("Error getting data within rule: " + currentRule); throw new RulesException("Error getting data within rule: " + currentRule, te); } catch (ResolverException re) { logger.error("Error inserting data from rule: " + currentRule); throw new RulesException("Error inserting data from rule: " + currentRule, re); } catch (QueryException qe) { logger.error("Error executing rule: " + currentRule, qe); throw new RulesException("Error executing rule: " + currentRule, qe); } logger.debug("All rules complete"); } /** * Schedules a rule to be run. * * @param rule The rule to schedule. */ public void schedule(Rule rule) { logger.debug("Scheduling: " + rule.getName()); // re-insertions do NOT affect the order in the queue runQueue.add(rule); } /** * Remove the head of the queue. * * @return The first item in the queue. */ private Rule popRunQueue() { // get an iterator for the queue Iterator<Rule> iterator = runQueue.iterator(); // this queue must have data in it assert iterator.hasNext(); Rule head = iterator.next(); iterator.remove(); return head; } /** * Inserts all axioms into the output model in the current session. * * @param resolver The resolver to use for writing. */ private void insertAxioms(Resolver resolver, ResolverSession resolverSession) throws QueryException { logger.debug("Inserting axioms"); // check if axioms were provided if (axioms == null) { logger.debug("No axioms provided"); return; } try { // Create the statements Statements stmts = new TripleSetWrapperStatements(axioms, resolverSession, TripleSetWrapperStatements.PERSIST); // insert the statements resolver.modifyModel(targetGraph, stmts, true); } catch (TuplesException te) { throw new QueryException("Unable to convert axioms for storage", te); } catch (ResolverException re) { throw new QueryException("Unable to store axioms", re); } } /** * Calculate the localized gNode for the target graph, and update all rules with this info. * @param context The context to query for the canonical form of the graph. * @param sysResolver The resolver to localize the graph info on. * @throws RulesException If the graph URI could not be localized. */ private void localizeRuleTarget(OperationContext context, SystemResolver sysResolver) throws RulesException { try { targetGraph = sysResolver.localize(new URIReferenceImpl(targetGraphURI)); } catch (LocalizeException e) { throw new RulesException("Unable to make a determination on the destination graph: " + targetGraphURI, e); } targetGraph = context.getCanonicalModel(targetGraph); // tell the rules which graph they need to modify for (Rule rule: rules) rule.setTargetGraph(targetGraph); } /** * Determine the resolver to use for modifications to the target graph. * @param context The context to extract the resolver from. * @return A resolver associated with the current transactional context. * @throws RulesException If a resolver could not be obtained. */ private Resolver extractTargetResolver(OperationContext context) throws RulesException { if (targetGraph == UNINITIALIZED) throw new IllegalStateException("Target graph has not been resolved"); try { return context.obtainResolver(context.findModelResolverFactory(targetGraph)); } catch (QueryException e) { throw new RulesException("Unable to get access to modify the target graph: " + targetGraphURI); } } /** * Confirm that the parameters for {@link #run(Object)} are the expected type, since * they are not subject to static type checking. The complexity of this object will be * reduced when it is replaced by a {@link Session} again. * @param params The parameters to {@link #run(Object)}. For the moment this is a 2 element * array of Object, with elements of {@link OperationContext} and {@link SystemResolver}. * @throws IllegalArgumentException If the parameters are not of the expected form. */ private void validateParams(Object params) { if (!(params instanceof Object[])) { throw new IllegalArgumentException("Rules must be run with parameters of OperationContext/SystemResolver"); } if (!(((Object[])params)[0] instanceof OperationContext)) { throw new IllegalArgumentException("Rules must be run with an OperationContext"); } if (!(((Object[])params)[1] instanceof SystemResolver)) { throw new IllegalArgumentException("Rules must be run with a SystemResolver"); } } }