/*******************************************************************************
* 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.net.URLEncoder;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Set;
import mjson.Json;
import static mjson.Json.object;
import static org.sharegov.cirm.OWL.*;
import org.hypergraphdb.util.Pair;
import org.hypergraphdb.util.RefResolver;
import org.semanticweb.owlapi.model.IRI;
import org.semanticweb.owlapi.model.OWLLiteral;
import org.semanticweb.owlapi.model.OWLObject;
import org.semanticweb.owlapi.model.SWRLAtom;
import org.semanticweb.owlapi.model.SWRLBuiltInAtom;
import org.semanticweb.owlapi.model.SWRLDArgument;
import org.semanticweb.owlapi.model.SWRLLiteralArgument;
import org.semanticweb.owlapi.model.SWRLRule;
import org.semanticweb.owlapi.model.SWRLVariable;
import org.sharegov.cirm.rules.EvaluateAtom;
import org.sharegov.cirm.utils.EvalUtils;
import org.sharegov.cirm.utils.RESTClient;
/**
*
* <p>
* Represents an asynchronous event rule. This looks like a normal SWRL rule, except it's
* interpreted differently: it is not included in the goal-directed workflow. Rather, it
* creates an event to be fired in the future and possibly changing the direction of the
* normal workflow.
* </p>
*
* <p>
* This class is just a wrapper that manages the behavior of a SWRL event rule. An event rule is
* detected as such if any of the following conditions are met:
*
* <ul>
* <li>
* A <code>http://www.miamidade.gov/ontology#timeElapsed</code> built-in is invoked in the
* rule's premises.
* </li>
* <li>
* Maybe some other way in the future....
* </li>
* </ul>
*
* </p>
*
* @author Borislav Iordanov
*
*/
public class EventRule
{
static SimpleDateFormat ISO8601Local = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
private SWRLRule rule;
private Json data;
private AppliedRule applied;
private synchronized AppliedRule evaluate(WorkflowExecutionContext ctx)
{
if (this.applied != null)
return this.applied;
final AppliedRule applied = new AppliedRule();
applied.rule = rule;
for (boolean again = true; again; )
{
again = false;
for (SWRLAtom atom : rule.getBody())
{
Pair<SWRLVariable, OWLObject> assignment =
EvalUtils.dispatch(new VarAssignment(ctx.getBusinessObjectOntology().getOntology(),
applied.getVarResolver()), atom);
if (assignment != null && applied.valueOf(assignment.getFirst()) == null)
{
again = true;
applied.assign(assignment.getFirst(), assignment.getSecond());
}
}
}
for (SWRLAtom atom : rule.getBody())
{
RefResolver<SWRLVariable, OWLObject> varresolver = new RefResolver<SWRLVariable, OWLObject>()
{
public OWLObject resolve(SWRLVariable var) { return applied.valueOf(var); }
};
AtomValue value = EvalUtils.dispatch(new EvaluateAtom(varresolver,
ctx.getBusinessObjectOntology().getOntology()), atom);
SWRLAtom instantiated = EvalUtils.dispatch(new AtomInstantiation(applied), atom);
applied.instantiated.put(atom, instantiated);
applied.truthValues.put(atom, value);
}
return this.applied = applied;
}
public EventRule(SWRLRule rule, Json data)
{
this.rule = rule;
this.data = data;
}
private static SWRLBuiltInAtom findBuiltIn(Set<SWRLAtom> S, IRI builtinName)
{
for (SWRLAtom atom : S)
{
if (! (atom instanceof SWRLBuiltInAtom))
continue;
if (((SWRLBuiltInAtom)atom).getPredicate().equals(builtinName))
return (SWRLBuiltInAtom)atom;
}
return null;
}
/**
* @param rule
* @return
*/
public static boolean isEventRule(SWRLRule rule)
{
return findBuiltIn(rule.getBody(), RulesToWorkflow.timeElapsed) != null;
}
public boolean isActive(WorkflowExecutionContext ctx)
{
return data.is("active", true);
}
public boolean isViable(WorkflowExecutionContext ctx)
{
evaluate(ctx);
return applied.isSatisfied() != AtomValue.False;
}
public Calendar getFireTime()
{
SWRLBuiltInAtom bin = findBuiltIn(rule.getBody(), RulesToWorkflow.timeElapsed);
bin = (SWRLBuiltInAtom)applied.instantiated.get(bin);
SWRLDArgument arg1 = bin.getArguments().get(0);
SWRLDArgument arg2 = bin.getArguments().get(1);
if (arg1 instanceof SWRLVariable || arg2 instanceof SWRLVariable)
return null; // can't activate, don't have the value of the base date yet
String baseDate = ((SWRLLiteralArgument)arg1).getLiteral().getLiteral();
String offsetPeriod = ((SWRLLiteralArgument)arg2).getLiteral().getLiteral();
Calendar cal = Calendar.getInstance();
try
{
cal.setTime(ISO8601Local.parse(baseDate));
}
catch (ParseException e)
{
throw new RuntimeException(e);
}
char periodType = offsetPeriod.charAt(offsetPeriod.length()-1);
int periodValue = Integer.parseInt(offsetPeriod.substring(0, offsetPeriod.length()-1));
switch (periodType)
{
case 'd':cal.add(Calendar.DATE, periodValue); break;
case 'h':cal.add(Calendar.HOUR, periodValue); break;
case 'm':cal.add(Calendar.MINUTE, periodValue); break;
default:throw new RuntimeException("Invalid period format : " + offsetPeriod);
}
return cal;
}
@SuppressWarnings("deprecation")
public void activate(WorkflowExecutionContext ctx)
{
if (isActive(ctx))
throw new IllegalStateException("Event rule trigger already active.");
else if (!isViable(ctx))
throw new IllegalStateException("Event rule is not viable and won't be activated.");
evaluate(ctx);
try
{
Calendar cal = getFireTime();
if (cal == null)
return;
String callbackUrl = null;
OWLLiteral thisUrl = dataProperty(individual("CiRMOperationService"), "hasUrl");
if (thisUrl == null)
throw new RuntimeException("Missing CiRMOperationService information from ontology.");
callbackUrl = thisUrl.getLiteral() + "/eventrule?boiri=" +
URLEncoder.encode(ctx.getBusinessObject().getIRI().toString() + "&id=" +
data.at("hghandle"));
// Call Time Machine REST Service to submit the timer
OWLLiteral tmUrl = dataProperty(individual("TimeMachineService"), "hasUrl");
if (tmUrl == null)
throw new RuntimeException("Missing TimeMachine information.");
Json taskSpec = object();
taskSpec.set("myurl", callbackUrl)
.set("time", object()
.set("day", cal.get(Calendar.DATE))
.set("month", cal.get(Calendar.MONTH + 1))
.set("year", cal.get(Calendar.YEAR))
.set("hour", cal.get(Calendar.HOUR_OF_DAY))
.set("minute", cal.get(Calendar.MINUTE))
.set("second", cal.get(Calendar.SECOND))
);
Json result = RESTClient.post(tmUrl.getLiteral(), taskSpec);
if (!result.is("ok", true))
throw new RuntimeException("Unable to schedule task: " + result.at("error"));
else {
data.set("taskid", result.at("taskid"));
data.set("active", true);
data.set("time", cal.getTimeInMillis());
}
}
catch (Exception ex)
{
throw new RuntimeException(ex);
}
}
public void deactivate(WorkflowExecutionContext ctx)
{
if (!isActive(ctx))
throw new IllegalStateException("Event rule trigger is not currently active.");
OWLLiteral tmUrl = dataProperty(individual("TimeMachineService"), "hasUrl");
if (tmUrl == null)
throw new RuntimeException("Missing TimeMachine information.");
Json result = RESTClient.del(tmUrl + "/task/" + data.at("taskid"));
if (!result.is("ok", true))
throw new RuntimeException("Unable to delete task " + data.at("taskid") + ": " + result.at("error"));
else
{
data.set("active", false);
}
}
public void fire(WorkflowExecutionContext ctx)
{
evaluate(ctx);
if (applied.isSatisfied() == AtomValue.True)
{
for (SWRLAtom a : rule.getHead())
{
// We eval built-ins and assert everything else.
if (a instanceof SWRLBuiltInAtom)
new BuiltInAtomTask((SWRLBuiltInAtom)a).perform(ctx);
else
new AssertAtomTask(a).perform(ctx);
}
}
}
}