/******************************************************************************* * Copyright 2014 Miami-Dade County * * 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.sharegov.cirm.workflows; import java.util.HashMap; import java.util.Calendar; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.Stack; import org.hypergraphdb.HGGraphHolder; import org.hypergraphdb.HGHandle; import org.hypergraphdb.HyperGraph; import org.hypergraphdb.annotation.AtomReference; import org.hypergraphdb.util.Pair; import org.hypergraphdb.util.RefResolver; import org.semanticweb.owlapi.model.IRI; import org.semanticweb.owlapi.model.OWLClass; import org.semanticweb.owlapi.model.OWLDataProperty; import org.semanticweb.owlapi.model.OWLIndividual; import org.semanticweb.owlapi.model.OWLLiteral; import org.semanticweb.owlapi.model.OWLNamedIndividual; import org.semanticweb.owlapi.model.OWLNamedObject; import org.semanticweb.owlapi.model.OWLObject; import org.semanticweb.owlapi.model.OWLObjectProperty; import org.semanticweb.owlapi.model.SWRLRule; import org.semanticweb.owlapi.model.SWRLVariable; import org.sharegov.cirm.BOntology; import org.sharegov.cirm.OWL; import org.sharegov.cirm.utils.EvalUtils; import org.sharegov.cirm.utils.GenUtils; import org.sharegov.cirm.utils.JsonSerializable; import static org.sharegov.cirm.OWL.*; import mjson.Json; /** * * <p> * The workflow execution context represents state information for a particular * execution of a particular workflow. The state can be persistent and * it is managed entirely by the various steps being executed during the * workflow's instance lifetime as well the {@link ExecutionEngine} driving * the process. * </p> * * @author Borislav Iordanov * */ public class WorkflowExecutionContext implements HGGraphHolder, JsonSerializable { private HyperGraph graph; @AtomReference("symbolic") private Workflow workflow; @AtomReference("symbolic") private WorkflowStep currentStep; private Stack<WorkflowStep> history = new Stack<WorkflowStep>(); // We store runtime information about event rules (which one is active, history // of firing etc. as Json structures mapped to the rule HGDB handles). private Map<HGHandle, Json> eventRules = new HashMap<HGHandle, Json>(); private BOntology bontology; // The output variables per each step are stored globally here. The key is a step handle + the // variable id. For global variables, the step handle is the HGHandleFactory.anyHandle(). private Map<Pair<HGHandle, IRI>, Object> variables = new HashMap<Pair<HGHandle, IRI>, Object>(); // A global varresolver for the whole context is needed for "global" variables that are available // in all workflow steps. The RulesToWorkflow.boVar (i.e. '?bo' inside a rule) is the only one // such variable currently. When creating the workflow, this variable is resolved either to a prototypical // object of the OWLClass or (if a workflow is being re-generated for a specific business object) to // an actual business object with some data in it, but unlike other variables that can be resolved during // workflow creation, it remains a variable and doesn't get instantiated. It is supposed to be // implicitly defined in all workflow steps. The logic of how to deal with this variable is kind of // scattered in RulesToWorkflow and this class and perhaps some other classes. It is really treated // as a special case burried inside "if" conditions. It's ugly. The whole point is to be able to generate // a workflow for a prototypical business object of the type of interest, but use a real/operational business // object when executing the workflow. // //Another way to deal with this and possible other globals is to have // a special "WorkflowStart" step that has all globals as "output variables" that go to all other steps as // "input variables", but that doesn't seem to elegant either, it just kind of masks what's implicitly // global into making it explicitly local everywhere. private RefResolver<SWRLVariable, OWLObject> varResolver = new RefResolver<SWRLVariable, OWLObject>(){ public OWLObject resolve(SWRLVariable var) { if (var.getIRI().equals(RulesToWorkflow.boVar)) return bontology.getBusinessObject(); else if (EvalUtils.isVarGlobal(var.getIRI())) return (OWLObject)getGlobalVariable(var.getIRI()); //variables.get(new Pair<HGHandle, IRI>(graph.getHandleFactory().anyHandle(), var.getIRI())); else return null; } }; public WorkflowExecutionContext() { } public void setHyperGraph(HyperGraph graph) { this.graph = graph; } public RefResolver<SWRLVariable, OWLObject> getVarResolver() { return varResolver; } public void processEventRules() { for (Map.Entry<HGHandle, Json> e : this.eventRules.entrySet()) { EventRule rule = new EventRule((SWRLRule)graph.get(e.getKey()), e.getValue()); if (rule.isActive(this) && (Calendar.getInstance().after(rule.getFireTime())) || !rule.isViable(this)) rule.deactivate(this); else if (rule.isViable(this) && Calendar.getInstance().before(rule.getFireTime())) rule.activate(this); } } public void fireEventRule(String ruleId) { HGHandle ruleHandle = graph.getHandleFactory().makeHandle(ruleId); EventRule evRule = new EventRule((SWRLRule)WorkflowManager.getInstance().getGraph().get(ruleHandle), eventRules.get(ruleHandle)); evRule.fire(this); } public void backtrack() { if (history.isEmpty()) throw new WorkflowException("Can't backtrack on an empty history."); WorkflowStep top = history.pop(); while (top instanceof Branch && !history.isEmpty()) top = history.pop(); if (history.isEmpty()) // unlikely, but theoretically possible that first steps were only branches currentStep = workflow.getStartingStep(); if (top instanceof WorkflowUndoableStep) { // if (top instanceof PromptUserTask) // { // WorkflowUndoableStep promptRequest = (WorkflowUndoableStep)top.perform(this); // promptRequest.backtrack(this); // } ((WorkflowUndoableStep) top).backtrack(this); currentStep = top; } else throw new WorkflowException("Previous workflow step " + top + " cannot be undone."); } public void moveTo(WorkflowStep step) { history.push(currentStep); currentStep = step; } /** * Return a map of all output variables of the passed in workflow step. */ public Map<IRI, Object> getStepOutput(WorkflowStep step) { Map<IRI, Object> m = new HashMap<IRI, Object>(); if (! (step instanceof Task)) return m; Set<IRI> varnames = new HashSet<IRI>(); Task t = (Task)step; varnames.addAll(t.getOutputVariables()); if (t instanceof AtomEvalTask) varnames.add(AtomEvalTask.evalResultVar); for (IRI name : varnames) { Object value = variables.get(new Pair<HGHandle, IRI>(t.getPrototypeId(), name)); if (value != null) m.put(name, value); } return m; } public WorkflowStep step() { WorkflowStep next = currentStep.perform(this); if (next != currentStep) this.moveTo(next); return next; } public void setBusinessObjectOntology(BOntology bontology) { this.bontology = bontology; } public BOntology getBusinessObjectOntology() { return bontology; } public OWLNamedIndividual getBusinessObject() { return OWL.individual(bontology.getOntology().getOntologyID().getOntologyIRI().resolve("#bo")); } public Workflow getWorkflow() { return workflow; } public void setWorkflow(Workflow workflow) { this.workflow = workflow; } public WorkflowStep getCurrentStep() { return currentStep; } public void setCurrentStep(WorkflowStep step) { this.currentStep = step; } public IRI getBusinessObjectTypeIri() { return bontology.getTypeIRI(); } public String getBusinessObjectId() { return bontology.getObjectId(); } // public Map<Pair<HGHandle, IRI>, Object> getVariables() // { // return variables; // } public void removeVariable(HGHandle taskId, IRI varname) { variables.remove(new Pair<HGHandle, IRI>(taskId, varname)); } public void setVariable(HGHandle taskId, IRI varname, Object value) { if (EvalUtils.isVarGlobal(varname)) setGlobalVariable(varname, value); else variables.put(new Pair<HGHandle, IRI>(taskId, varname), value); } public Object getVariable(HGHandle taskId, IRI varname) { return variables.get(new Pair<HGHandle, IRI>(taskId, varname)); } public Object getGlobalVariable(IRI varname) { return variables.get(new Pair<HGHandle, IRI>(graph.getHandleFactory().anyHandle(), varname)); } public void setGlobalVariable(IRI varname, Object value) { variables.put(new Pair<HGHandle, IRI>(graph.getHandleFactory().anyHandle(), varname), value); } // public void setVariables(Map<Pair<HGHandle, IRI>, Object> variables) // { // this.variables = variables; // } public Stack<WorkflowStep> getHistory() { return history; } public void setHistory(Stack<WorkflowStep> history) { this.history = history; } @SuppressWarnings("unchecked") public <T> T valueOf(IRI varIri) { Object x = variables.get(varIri); return (T)x; } private Json varValueToJson(Object x) { Json j = Json.object(); if (x instanceof OWLNamedObject) { if (x instanceof OWLIndividual) return j.set("individual", ((OWLNamedObject)x).getIRI().toString()); else if (x instanceof OWLClass) return j.set("class", ((OWLNamedObject)x).getIRI().toString()); else if (x instanceof OWLDataProperty) return j.set("dataproperty", ((OWLNamedObject)x).getIRI().toString()); else if (x instanceof OWLObjectProperty) return j.set("objectproperty", ((OWLNamedObject)x).getIRI().toString()); else throw new IllegalArgumentException("Unable to json-ify: " + x); } else if (x instanceof OWLLiteral) { return j.set("literal", ((OWLLiteral)x).getLiteral()); } else try { return j.set("json", Json.make(x)); } catch (Exception ex) { return j.set("serialized", GenUtils.serializeAsString(x)); } } private Object varValueFromJson(Json j) { if (j.has("individual")) return individual(j.at("individual").asString()); else if (j.has("class")) return owlClass(j.at("class").asString()); else if (j.has("dataproperty")) return dataProperty(j.at("dataproperty").asString()); else if (j.has("objectproperty")) return dataProperty(j.at("objectproperty").asString()); else if (j.has("literal")) return OWL.dataFactory().getOWLLiteral( j.at("literal").asString()); else if (j.has("json")) return j.at("json").getValue(); else if (j.has("serialized")) return GenUtils.deserializeFromString(j.at("serialized").asString()); else throw new IllegalArgumentException("Can't deserialize var value from " + j); } public Json toJSON() { Json jhist = Json.array(); for (WorkflowStep step : this.history) { Json vars = Json.object(); vars.set(RulesToWorkflow.boVar.toString(), varValueToJson(getBusinessObject())); if (step instanceof Task) { Task t = (Task)step; for (IRI iri : t.getInputVariables().keySet()) { Object value = this.variables.get(t.getInputVariables().get(iri)); if (value != null) vars.set(iri.toString(), varValueToJson(value)); } } jhist.add(Workflow.toJSON(step).set("variables", vars)); } Json vars = Json.array(); for (Map.Entry<Pair<HGHandle, IRI>, Object> e : variables.entrySet()) { vars.add(Json.object("step-handle", e.getKey().getFirst().getPersistent().toString(), "iri", e.getKey().getSecond().toString(), "value", varValueToJson(e.getValue()))); } return Json.object() .set("variables", vars) .set("history", jhist) .set("currentStep", Workflow.toJSON(currentStep)) .set("bo", bontology.toJSON()); } public void fromJSON(Json json) { variables.clear(); for (Json v : json.at("variables").asJsonList()) { Pair<HGHandle, IRI> p = new Pair<HGHandle, IRI>( graph.getHandleFactory().makeHandle(v.at("step-handle").asString()), IRI.create(v.at("iri").asString())); variables.put(p, varValueFromJson(v.at("value"))); } history.clear(); for (Json h : json.at("history").asJsonList()) { HGHandle handle = graph.getHandleFactory().makeHandle(h.at("id").asString()); WorkflowStep step = graph.get(handle); history.add(step); } HGHandle currentStepHandle = graph.getHandleFactory().makeHandle(json.at("currentStep").at("id").asString()); currentStep = graph.get(currentStepHandle); bontology = BOntology.makeRuntimeBOntology(json.at("bo")); } }