com.freedomotic.core.Resolver Maven / Gradle / Ivy
/**
*
* Copyright (c) 2009-2014 Freedomotic team http://freedomotic.com
*
* This file is part of Freedomotic
*
* This Program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation; either version 2, or (at your option) any later version.
*
* This Program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* Freedomotic; see the file COPYING. If not, see
* .
*/
package com.freedomotic.core;
import com.freedomotic.app.Freedomotic;
import com.freedomotic.exceptions.VariableResolutionException;
import com.freedomotic.model.ds.Config;
import com.freedomotic.reactions.Command;
import com.freedomotic.rules.Payload;
import com.freedomotic.reactions.Reaction;
import com.freedomotic.rules.Statement;
import com.freedomotic.reactions.Trigger;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
/**
* Resolves command values using an event as the context of resolution eg: param
* sensorPlugin = "
*
* @event.sender" becomes sensorPlugin="TemperatureSensorPlugin"
*
* This class takes a list of properties in form key=value and propagates
* this list to all commands in a given reaction. After that this list (called
* context) is used to resolve the references to external values in the command.
* For example Commmand: turn on this x10 device x10-object =
* @event.object.name x10-address =
* @event.object.address this are resolved according to the parameters in the
* event that has fired the reaction containing the command 'turn on this x10
* device' in this case the event can be something like 'object receive click on
* the GUI' with paramenter object = Light 1 click = SINGLE_CLICK
*
* @author Enrico Nicoletti ([email protected])
*/
public final class Resolver {
// private static final String REFERENCE_DELIMITER = "@";
private ArrayList prefixes = new ArrayList();
private Payload context;
private Reaction reaction;
private Command command;
private Trigger trigger;
/**
* Creates an empty resolution context
*/
public Resolver() {
this.context = new Payload();
}
/**
* Creates a resolved clone of the reaction in input. All commands in the
* reaction are resolved according to the context given in the contructor.
*
* @param r
* @return a clone of the resolver reaction
*/
public Reaction resolve(Reaction r) {
this.reaction = r;
if ((context != null) && (reaction != null)) {
Reaction clone =
new Reaction(reaction.getTrigger(),
performSubstitutionInCommands(reaction.getCommands()));
return clone;
}
return null;
}
/**
* Creates a resolved clone of the command in input according to the current
* context given in input to the constructor.
*
* @param c
* @return
* @throws com.freedomotic.exceptions.VariableResolutionException
*/
public Command resolve(Command c)
throws CloneNotSupportedException, VariableResolutionException {
this.command = c;
if ((context != null) && (command != null)) {
Command clone = command.clone();
mergeContextParamsIntoCommand(clone);
performSubstitutionInCommand(clone);
return clone;
}
return null;
}
/**
* Creates a resolved clone of the trigger in input according to the current
* context given in input to the constructor.
*
* @param trigger
* @return
* @throws com.freedomotic.exceptions.VariableResolutionException
*/
public Trigger resolve(Trigger t) throws VariableResolutionException {
this.trigger = t;
if ((context != null) && (trigger != null)) {
Trigger clone = trigger.clone();
mergeContextParamsIntoTrigger(clone);
performSubstitutionInTrigger(clone);
return clone;
}
return null;
}
/*
* makes all commands in a reaction inherits the event parameters. after
* that resolves the properties of the command eg: if a command properties
* is: device = @event.device it becomes: device = P01 translating
* @event.device to the name of the device which has generated the event
*/
private List performSubstitutionInCommands(List commands) {
//clone reaction to not affect the original commands with temporary values
//construct a cloned reaction
List tmp = new ArrayList();
try {
for (Command originalCommand : commands) {
//resolving values using context data
Command clonedCmd = resolve(originalCommand);
tmp.add(clonedCmd);
}
} catch (Exception e) {
LOG.warning(Freedomotic.getStackTraceInfo(e));
}
return tmp;
}
/**
* search in a command attribute for a pattern
*
* @event.VARIABLE_NAME and replace it with the real value from event
* Payload p
*
* @param command
*/
private void performSubstitutionInCommand(Command command) throws VariableResolutionException {
for (Map.Entry aProperty : command.getProperties().entrySet()) {
String key = (String) aProperty.getKey();
String propertyValue = (String) aProperty.getValue();
for (final String prefix : prefixes) {
String regex = "@" + prefix + "[.A-Za-z0-9_-]*\\b(#)?";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(propertyValue);
while (matcher.find()) {
String occurrence = matcher.group();
//re-read the property each loop because is possible the previous loop has already replaced something
propertyValue = (String) aProperty.getValue();
String referenceToResolve = occurrence;
if (occurrence.endsWith("#")) {
//cutting out the optional last '#'
referenceToResolve = referenceToResolve.substring(0, referenceToResolve.length() - 1);
}
//cutting out the first char '@'
referenceToResolve =
referenceToResolve.substring(1,
referenceToResolve.length());
String replacer = command.getProperty(referenceToResolve);
if (((replacer != null) && !replacer.isEmpty())) {
String propertyValueResolved = propertyValue.replaceFirst(occurrence, replacer);
aProperty.setValue(propertyValueResolved);
} else {
throw new VariableResolutionException("Variable '" + referenceToResolve
+ "' cannot be resolved in command '" + command.getName() + "'.\n"
+ "Availabe tokens are: " + context.toString());
}
}
}
//all references are replaced with real values in the current property, now perform scripting
String possibleScript = (String) aProperty.getValue();
boolean success = false;
if (possibleScript.startsWith("=")) {
//this is a javascript
try {
ScriptEngineManager mgr = new ScriptEngineManager();
ScriptEngine js = mgr.getEngineByName("JavaScript");
String script = possibleScript.substring(1); //removing equal sign on the head
if (js == null) {
LOG.severe("Cannot instatiate a JavaScript engine");
}
try {
js.eval(script);
} catch (ScriptException scriptException) {
LOG.severe(scriptException.getMessage());
}
if (js.get(key) == null) {
LOG.log(Level.SEVERE,
"Script evaluation has returned a null value, maybe the key ''{0}'' is not evaluated properly.",
key);
}
aProperty.setValue(js.get(key).toString());
success = true;
} catch (Exception ex) {
success = false;
LOG.severe(Freedomotic.getStackTraceInfo(ex));
}
}
if (!success) {
aProperty.setValue(possibleScript);
}
}
}
/**
* search in a trigger attribute for a pattern
*
* @event.VARIABLE_NAME and replace it with the real value from event
* Payload p
*
* @param trigger
*/
private void performSubstitutionInTrigger(Trigger trigger) throws VariableResolutionException {
Iterator it = trigger.getPayload().iterator();
while (it.hasNext()) {
Statement statement = (Statement) it.next();
String key = (String) statement.getAttribute();
String propertyValue = (String) statement.getValue();
for (final String PREFIX : prefixes) {
Pattern pattern = Pattern.compile("@" + PREFIX + "[.A-Za-z0-9_-]*\\b(#)?"); //find any @token
Matcher matcher = pattern.matcher(propertyValue);
StringBuffer result = new StringBuffer(propertyValue.length());
while (matcher.find()) {
matcher.appendReplacement(result, "");
String tokenKey = matcher.group();
if (tokenKey.endsWith("#")) {
tokenKey = tokenKey.substring(0, tokenKey.length() - 1); //cutting out the optional last '#'
}
tokenKey =
tokenKey.substring(1,
tokenKey.length()); //cutting out the first char '@'
String tokenValue = trigger.getPayload().getStatementValue(tokenKey);
if (tokenValue == null) {
throw new VariableResolutionException("Variable '" + tokenValue + "' cannot be resolved in trigger '"
+ trigger.getName() + "'.\n" + "Availabe tokens are: "
+ context.toString());
}
//replace an @token.property with its real value
//System.out.println("Replace all " + tokenKey + " with " + tokenValue + " in " + propertyValue);
result.append(tokenValue);
}
matcher.appendTail(result);
statement.setValue(result.toString());
}
//all references are replaced with real values in the current statement, now perform scripting
String possibleScript = (String) statement.getValue().trim();
boolean success = false;
if (possibleScript.startsWith("=")) {
//this is a javascript
try {
ScriptEngineManager mgr = new ScriptEngineManager();
ScriptEngine js = mgr.getEngineByName("JavaScript");
//removing equal sign on the head
String script = possibleScript.substring(1);
if (js == null) {
LOG.severe("Cannot instatiate a JavaScript engine");
}
try {
js.eval(script);
} catch (ScriptException scriptException) {
LOG.severe(scriptException.getMessage());
}
if (js.get(key) == null) {
LOG.log(Level.SEVERE,
"Script evaluation in trigger ''{0}'' has returned a null value, maybe the key ''{1}'' is not evaluated properly.",
new Object[]{trigger.getName(), key});
}
statement.setValue(js.get(key).toString());
success = true;
} catch (Exception ex) {
success = false;
LOG.severe(ex.getMessage());
}
}
if (!success) {
//fall back to the value before scripting evaluation
statement.setValue(possibleScript);
}
}
}
private void mergeContextParamsIntoCommand(Command c) {
//adding parameters to command parameters with a prefix
Iterator it = context.iterator();
while (it.hasNext()) {
Statement statement = it.next();
c.setProperty(statement.getAttribute(), statement.getValue());
}
}
private void mergeContextParamsIntoTrigger(Trigger t) {
//adding parameters to command parameters with a prefix
t.getPayload().merge(context);
}
/**
*
* @param PREFIX
* @param aContext
*/
public void addContext(final String PREFIX, final Config aContext) {
if (context == null) {
context = new Payload();
}
//registering the new prefix
if (!prefixes.contains(PREFIX)) {
prefixes.add(PREFIX);
}
Set entries = aContext.getProperties().entrySet();
Iterator it = entries.iterator();
while (it.hasNext()) {
String key;
Map.Entry entry = (Map.Entry) it.next();
//removing the prefix of the properties if already exists
//to avoid dublicate prefixes like @event.event.object.name
if (entry.getKey().toString().startsWith(PREFIX)) {
key = entry.getKey().toString().substring(PREFIX.length());
} else {
key = entry.getKey().toString();
}
//System.out.println(" statement " + PREFIX + key + "=" + entry.getValue().toString());
context.addStatement(PREFIX + key,
entry.getValue().toString());
}
}
/**
*
* @param PREFIX
* @param aContext
*/
public void addContext(final String PREFIX, final Map aContext) {
if (context == null) {
context = new Payload();
}
//registering the new prefix
if (!prefixes.contains(PREFIX)) {
prefixes.add(PREFIX);
}
Iterator it = aContext.entrySet().iterator();
while (it.hasNext()) {
String key;
Entry entry = (Entry) it.next();
//removing the prefix of the properties if already exists
//to avoid duplicate prefixes like @event.event.object.name
if (entry.getKey().toString().startsWith(PREFIX)) {
key = entry.getKey().toString().substring(PREFIX.length());
} else {
key = entry.getKey().toString();
}
//System.out.println(" statement " + PREFIX + key + "=" + entry.getValue().toString());
context.addStatement(PREFIX + key,
entry.getValue().toString());
}
}
/**
*
* @param PREFIX
* @param aContext
*/
public void addContext(final String PREFIX, final Payload aContext) {
if (context == null) {
context = new Payload();
}
//registering the new prefix
if (!prefixes.contains(PREFIX)) {
prefixes.add(PREFIX);
}
Iterator it = aContext.iterator();
while (it.hasNext()) {
String key;
Statement statement = (Statement) it.next();
//removing the prefix of the properties if already exists
//to avoid dublicate prefixes like @event.event.object.name
if (statement.getAttribute().startsWith(PREFIX)) {
key = statement.getAttribute().substring(PREFIX.length());
} else {
key = statement.getAttribute().toString();
}
context.addStatement(PREFIX + key,
statement.getValue());
}
}
void clear() {
prefixes.clear();
context.clear();
reaction = null;
command = null;
trigger = null;
}
private static final Logger LOG = Logger.getLogger(Resolver.class.getName());
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy