/*
* Dog - Addons
*
* Copyright (c) 2011 Dario Bonino
*
* 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 it.polito.elite.dog.addons.rules.util;
import it.polito.elite.dog.addons.rules.schemalibrary.ABlock;
import it.polito.elite.dog.addons.rules.schemalibrary.CBlock;
import it.polito.elite.dog.addons.rules.schemalibrary.Command;
import it.polito.elite.dog.addons.rules.schemalibrary.Day;
import it.polito.elite.dog.addons.rules.schemalibrary.EBlock;
import it.polito.elite.dog.addons.rules.schemalibrary.Instant;
import it.polito.elite.dog.addons.rules.schemalibrary.Interval;
import it.polito.elite.dog.addons.rules.schemalibrary.Lhs;
import it.polito.elite.dog.addons.rules.schemalibrary.NotificationType;
import it.polito.elite.dog.addons.rules.schemalibrary.Param;
import it.polito.elite.dog.addons.rules.schemalibrary.RecurringInterval;
import it.polito.elite.dog.addons.rules.schemalibrary.Rule;
import it.polito.elite.dog.addons.rules.schemalibrary.RuleList;
import it.polito.elite.dog.addons.rules.schemalibrary.StateType;
import java.util.Calendar;
import java.util.List;
import javax.xml.datatype.Duration;
import javax.xml.datatype.XMLGregorianCalendar;
/**
*
* @author <a href="mailto:dario.bonino@polito.it">Dario Bonino</a>
* @see <a href="http://elite.polito.it">http://elite.polito.it</a>
*
*/
public class Xml2DrlTranslator
{
public Xml2DrlTranslator()
{
// empty constructor...no instance data at the moment
}
/**
* Translates a set of XML rules specified as a JAXB class tree into a
* proper DRL rule definition string
*
* @param localRules
* @return a {@link String} containing the DRL representation of the given
* rules
*/
public synchronized String xml2drl(RuleList localRules)
{
// a string buffer for composing the DRL rules
StringBuffer drlRules = new StringBuffer();
// add the needed imports
drlRules.append("import it.polito.elite.dog.addons.rules.RuleEngine;\n");
drlRules.append("import it.polito.elite.dog.core.library.model.state.*;\n");
drlRules.append("import it.polito.elite.dog.core.library.model.notification.*;\n");
drlRules.append("import it.polito.elite.dog.core.library.model.notification.core.*;\n");
drlRules.append("import it.polito.elite.dog.addons.rules.util.TimeConversion;\n");
// add a globall reference to the SynchroState service
drlRules.append("global it.polito.elite.dog.addons.rules.RuleEngine dogRule;\n");
drlRules.append("global it.polito.elite.dog.addons.rules.util.TimeConversion timeConverter;\n");
// get the single rules and iterate over them calling the
// xmlRule2drlRule method
for (Rule currentRule : localRules.getRule())
{
drlRules.append(this.xmlRule2drlRule(currentRule));
drlRules.append("\n\n");
}
// the string representation of the DRL rules generated from the XML
// rules file
return drlRules.toString();
}
public synchronized String xmlRule2drlRule(Rule singleRule)
{
// a string buffer for composing the DRL rule
StringBuffer drlRule = new StringBuffer();
// get the rule name
drlRule.append("rule \"" + singleRule.getName() + "\"\n");
drlRule.append("when\n");
// handle the LHS here
drlRule.append(this.lhs2drlLhs(singleRule));
// insert the "then" DRL clause
drlRule.append("then\n");
// handle the RHS here
drlRule.append(this.rhs2drlRhs(singleRule));
// insert the rule closing DRL clause
drlRule.append("end");
// the string representation of the DRL rule generated from the given
// XML rule element
return drlRule.toString();
}
/**
* Given the XML class tree corresponding to a single rule handles the
* translation of the rule LHS into the Drools drl format
*
* @param singleRule
* @return a {@link String} containing the rule LHS expressed in DRL
*/
private String lhs2drlLhs(Rule singleRule)
{
// the buffer for accumulating the rule translation
StringBuffer drlLhs = new StringBuffer();
// 1) check if there are one or more orIfs
List<Lhs> orIfs = singleRule.getOrIf();
if (orIfs.size() > 0)
{
// comment the C-Block
drlLhs.append("# start IF + OR-IF\n");
drlLhs.append("(or\n"); // an or operator must be prepended
}
// 2) handle the main if
drlLhs.append(this.xmlIf2drlwhen(singleRule.getIf()));
// 3) handle or-ifs
for (Lhs orIf : orIfs)
{
drlLhs.append(this.xmlIf2drlwhen(orIf));
}
if (orIfs.size() > 0)
drlLhs.append(")\n"); // append the ending parenthesis of the or
// operator
drlLhs.append("# end IF + OR-IF\n");
// convert the buffer to a String
return drlLhs.toString();
}
/**
* Converts an XML if or an XML or-if clause into the corresponding WHEN
* clause in DRL
*
* @param ruleLhs
* the If(Or-If) to convert
* @return a {@String} containing the DRL representation of the
* given If (Or-If) clause
*/
private String xmlIf2drlwhen(Lhs ruleLhs)
{
StringBuffer drlIf = new StringBuffer();
// 2.1) get the C-Block if exists
List<CBlock> cBlocks = ruleLhs.getWhen();
if (cBlocks.size() > 0)
{
// comment the C-Block
drlIf.append("# start IF (OR-IF) - C-BLOCKs in AND with the associated E-BLOCK\n");
drlIf.append("(and\n"); // and operator between the if and all the
// when blocks
}
// 2.2) get the E-Block
EBlock ruleEvent = ruleLhs.getEvent();
// handle the event translation
drlIf.append(this.event2drl(ruleEvent));
// 2.3) handle C-Blocks
if (cBlocks.size() > 0)
{
for (CBlock cBlock : cBlocks)
{
drlIf.append(this.cblock2drl(cBlock));
}
drlIf.append(")\n"); // append the ending parenthesis of the and
// operator
}
drlIf.append("# end IF (OR-IF)\n");
return drlIf.toString();
}
/**
* Translated an e-block specified in the XML rule base into the
* corresponding DRL condition
*
* @param ruleEvent
* @return a {@link String} containing the DRL representing the e-block
*/
private Object event2drl(EBlock ruleEvent)
{
// the string buffer for holding the e-block translation
StringBuffer drlEBlock = new StringBuffer();
drlEBlock.append("# begin E-Block\n");
// get the notification representation
NotificationType notification = ruleEvent.getNotification();
if (notification != null)
{
List<Param> notificationParams = notification.getParam();
if (!notificationParams.isEmpty())
{
// handle StateChangeNotifications separately
String notificationClass = notification.getClazz();
// explicit and declaration
// drlEBlock.append("(and \n");
// sigh sigh only hard-coding can save us....
if (notificationClass.equalsIgnoreCase("StateChangeNotification"))
{
Param notificationParam = notificationParams.get(0);
// declare the state change notification to match. It
// shall come from the given device and shall be
// carrying the above specified state
drlEBlock.append(notification.getClazz() + "(deviceUri == '"
+ notification.getFromDevice() + "', newState.currentStateValue[0].value == '"+notificationParam.getValue()+"'");
if ((notificationParam.getType() != null) && (!notificationParam.getType().isEmpty()))
{
// add the constraint on the state type
drlEBlock.append(", newState.stateName == '" + notificationParam.getType() + "'");
}
drlEBlock.append(")\n");
}
else
{
// only match the notification coming from the given device
// and matching the additional parameters
// specified through the XML params
drlEBlock.append(notification.getClazz() + "( deviceUri == '" + notification.getFromDevice()
+ "', ");
// counter for avoiding to concatenate the comma sign after
// the last element
int i = 1;
for (Param currentParam : notificationParams)
{
// append the condition corresponding to the given
// parameter
drlEBlock.append(currentParam.getName() + " == '" + currentParam.getValue() + "'");
if (i < notificationParams.size())
drlEBlock.append(", ");
i++;
}
drlEBlock.append(")\n");
}
// end of the explicit and declaration
// drlEBlock.append(")\n");
}
else
{
// the notification does not have any parameter, therefore it is
// a discrete notification
// only the notification class and the device sending the
// notification must be matched
drlEBlock.append(notification.getClazz() + "( deviceUri == '" + notification.getFromDevice() + "')\n");
}
}
drlEBlock.append("# end E-Block\n");
// convert the string buffer to a string
return drlEBlock.toString();
}
/**
* Translated a c-block specified in the XML rule base into the
* corresponding DRL condition
*
* @param cBlock
* @return a {@link String} containing the DRL representing the c-block
*/
private String cblock2drl(CBlock cBlock)
{
// the string buffer for holding the e-block translation
StringBuffer drlCBlock = new StringBuffer();
drlCBlock.append("# begin C-Block\n");
// TODO: handle recurring time interval...at the moment we only provide
// support to c-blocks containing states
StateType stateConstraint = cBlock.getState();
RecurringInterval recurringInterval = cBlock.getTimeInterval();
if (stateConstraint != null)
{
// handle state constraints
//TODO move to the new state structure...
// discrete state
if (stateConstraint.getName() != null)
{
drlCBlock .append("eval(dogRule.getCurrentSStateOf(\"" +
stateConstraint.getOfDevice() + "\",\"" +
stateConstraint.getClazz() + "\").equalsIgnoreCase(\"" +
stateConstraint.getName() + "\"))\n");
}
else
// handle a condition on a continuous state value
{
// get the value2
drlCBlock.append("eval(dogRule.getCurrentDStateOf(\"" +
stateConstraint.getOfDevice() + "\",\"" +
stateConstraint.getClazz() + "\") " +
stateConstraint.getEvaluator() + " " +
stateConstraint.getValue() + ")\n");
}
}
else if (recurringInterval != null)
{
// first get the interval definition
Interval interval = recurringInterval.getInterval();
// get the start time
XMLGregorianCalendar startGTime = interval.getStartTime();
// get the end time
XMLGregorianCalendar endGTime = interval.getEndTime();
if (endGTime == null)
{
// get the duration
Duration intervalDuration = interval.getDuration();
if (intervalDuration != null)
{
endGTime = (XMLGregorianCalendar) startGTime.clone();
endGTime.add(intervalDuration);
}
}
// now start and end time have been defined.... (start time are
// between 00:00:00 and 24:00:00), get them in seconds
TimeConversion converter = new TimeConversion();
long startSTime = converter.hmsToS(startGTime.getHour(), startGTime.getMinute(), startGTime.getSecond());
long endSTime = converter.hmsToS(endGTime.getHour(), endGTime.getMinute(), endGTime.getSecond());
// handle weekdays
Day weekdays = recurringInterval.getWeekdays();
StringBuffer allDays = new StringBuffer();
for (int cDay : weekdays.getDay())
{
allDays.append(cDay + ",");
}
// delete the last character and trim
allDays.deleteCharAt(allDays.length() - 1);
allDays.trimToSize();
// write the DRL expression
drlCBlock.append("eval(timeConverter.isCurrentTimeIn(" + startSTime + "L ," + endSTime + "L ,\""
+ allDays.toString() + "\"))\n");
}
drlCBlock.append("# end C-Block\n");
// convert the string buffer to a string
return drlCBlock.toString();
}
private String rhs2drlRhs(Rule singleRule)
{
// the buffer for holding the DRL translation of the XML rule rhs
StringBuffer drlRhs = new StringBuffer();
// get the action blocks
List<ABlock> aBlocks = singleRule.getThen().getAction();
// convert a-blocks
for (ABlock aBlock : aBlocks)
{
drlRhs.append("# start A-Block\n");
drlRhs.append(this.aBlock2drl(aBlock));
drlRhs.append("# end A-Block\n");
}
return drlRhs.toString();
}
private String aBlock2drl(ABlock aBlock)
{
// the string buffer for holding the ABlock DRL definition
StringBuffer drlABlock = new StringBuffer();
// check if timed
Instant cmdInstant = aBlock.getInstant();
Interval cmdInterval = aBlock.getInterval();
// direct command
List<Command> cmds = aBlock.getCommand();
for (Command cmd : cmds)
{
if ((cmdInstant == null) && (cmdInterval == null))
{
// treat the current command
drlABlock.append(this.cmd2drl(cmd));
}
else
{
// the opposite command shall be scheduled
// this is not trivial, especially assuming that
// the opposite command is the one who resets the device state
// at the same value it had before the command was sent
if (cmdInterval != null)
{
// schedule the opposite command
drlABlock.append(this.scheduleDrlCmd(cmd, cmdInterval));
}
else if (cmdInstant != null)
{
// just schedule the command at the given time instant
drlABlock.append(this.scheduleDrlCommand(cmd, cmdInstant));
}
}
}
return drlABlock.toString();
}
private Object scheduleDrlCommand(Command cmd, Instant cmdInstant)
{
// extract the start time and the call the common wrapper for scheduled
// commands
// time, according to the XSD rules can only span over one day
// (hh:mm:ss)
XMLGregorianCalendar instantTime = cmdInstant.getTime();
// extract the after...if exists
Duration afterTime = cmdInstant.getAfter();
long instant = 0;
long after = 0;
// if time is not null... then handle the precise time activation,
if (instantTime != null)
{
// handle the calendar....
TimeConversion converter = new TimeConversion();
instant = converter.hmsToS(instantTime.getHour(), instantTime.getMinute(), instantTime.getSecond());
}
else if (afterTime != null)
{
// handle the calendar
Calendar cal = Calendar.getInstance();
// evaluate the time interval after which the command must be sent
after = afterTime.getTimeInMillis(cal) / 1000;
}
return this.scheduleDrlCmd(cmd, instant, Long.MIN_VALUE, after);
}
/**
* Translates a timed command, i.e., a command with an associated duration
* and (or) with a start time and an end time
*
* @param cmd
* The JAXB object representing the command
* @param cmdInterval
* The JAXB Interval object describing the time window in which
* the command must be executed or for which the command must
* last
* @return a {@link String} containing the DRL clause representing the
* command
*/
private String scheduleDrlCmd(Command cmd, Interval cmdInterval)
{
// handle the interval
long startTime = 0;
TimeConversion converter = new TimeConversion();
if (cmdInterval.getStartTime() != null)
{
XMLGregorianCalendar start = cmdInterval.getStartTime();
// handle the calendar....
startTime = converter.hmsToS(start.getHour(), start.getMinute(), start.getSecond());
}
long endTime = 0;
if (cmdInterval.getEndTime() != null)
{
XMLGregorianCalendar end = cmdInterval.getEndTime();
endTime = converter.hmsToS(end.getHour(), end.getMinute(), end.getSecond());
}
else
{
long duration = converter.durationToS(cmdInterval.getDuration());
endTime = startTime + duration;
}
return this.scheduleDrlCmd(cmd, startTime, endTime, 0);
}
// start and end time are expressed in seconds
private String scheduleDrlCmd(Command cmd, long startTime, long endTime, long afterTime)
{
StringBuffer drlCmd = new StringBuffer();
// schedule the opposite command
drlCmd.append("dogRule.scheduleCommand(\"" + cmd.getToDevice() + "\",\"" + cmd.getName() + "\"");
// check parameters
List<Param> parameters = cmd.getParam();
// if there are any parameters to the command, serialize them as an
// array
// TODO: generalize this to avoid wrong interpretation of commands due
// to wrong parameter order
if ((parameters != null) && (!parameters.isEmpty()))
{
StringBuffer params = new StringBuffer();
boolean first = true;
for (Param param : parameters)
{
if (!first)
params.append(",");
else
first = false;
params.append(param.getValue());
}
drlCmd.append(", new Object[] {" + params + "}");
}
else
drlCmd.append(", null");
if (startTime > 0)
drlCmd.append(", new Long(" + startTime + "L)");
else
drlCmd.append(", null");
if (endTime > 0)
drlCmd.append(", new Long(" + endTime + "L)");
else
drlCmd.append(", null");
if (afterTime > 0)
drlCmd.append(", new Long(" + afterTime + "L)");
else
drlCmd.append(", null");
drlCmd.append(");\n");
return drlCmd.toString();
}
/**
* Translate an action block involving a direct command into a corresponding
* DRL clause
*
* @param cmd
* The JAXB class representing the command
* @return a {@link String} containing the DRL clause representing the
* command
*/
private String cmd2drl(Command cmd)
{
StringBuffer drlCmd = new StringBuffer();
// compose the drl action block
drlCmd.append("dogRule.executeCommand(\"" + cmd.getToDevice() + "\",\"" + cmd.getName() + "\"");
// check parameters
List<Param> parameters = cmd.getParam();
// if there are any parameters to the command, serialize them as an
// array
// TODO: generalize this to avoid wrong interpretation of commands due
// to wrong parameter order
if ((parameters != null) && (!parameters.isEmpty()))
{
StringBuffer params = new StringBuffer();
boolean first = true;
for (Param param : parameters)
{
if (!first)
params.append(",");
else
first = false;
params.append(param.getValue());
}
drlCmd.append(", new Object[] {" + params + "}");
}
else
drlCmd.append(", null");
drlCmd.append(");\n");
return drlCmd.toString();
}
}