com.freedomotic.things.EnvObjectLogic 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.things;
import com.freedomotic.behaviors.BehaviorLogic;
import com.freedomotic.app.Freedomotic;
import com.freedomotic.core.Resolver;
import com.freedomotic.environment.EnvironmentLogic;
import com.freedomotic.environment.EnvironmentRepository;
import com.freedomotic.environment.ZoneLogic;
import com.freedomotic.events.ObjectHasChangedBehavior;
import com.freedomotic.exceptions.VariableResolutionException;
import com.freedomotic.model.ds.Config;
import com.freedomotic.model.geometry.FreedomPolygon;
import com.freedomotic.model.geometry.FreedomShape;
import com.freedomotic.model.object.EnvObject;
import com.freedomotic.model.object.Representation;
import com.freedomotic.reactions.Command;
import com.freedomotic.reactions.CommandPersistence;
import com.freedomotic.reactions.Reaction;
import com.freedomotic.reactions.ReactionPersistence;
import com.freedomotic.rules.Statement;
import com.freedomotic.reactions.Trigger;
import com.freedomotic.reactions.TriggerPersistence;
import com.freedomotic.util.TopologyUtils;
import com.google.inject.Inject;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.shiro.authz.annotation.RequiresPermissions;
/**
*
* @author Enrico
*/
public class EnvObjectLogic {
private EnvObject pojo;
private boolean changed;
// private String message;
private Map commandsMapping; //mapping between action name -> hardware command instance
private Map behaviors = new HashMap();
private EnvironmentLogic environment;
@Inject
protected EnvironmentRepository environmentRepository;
/**
* Instantiation disabled from outside its package. Use
* {@code EnvObjectFactory} to generate instances of {@code EnvObjectLogic}
*/
protected EnvObjectLogic() {
super();
}
/**
* gets the hardware command mapped to the action in input for example:
* Action -> Hardware Command Turn on -> Turn on light with X10 Actuator
* Turn off -> Turn off light with X10 Actuator
*
* @param action
* @return a Command or null if action doesn't exist or the mapping is not
* valid
*/
@RequiresPermissions("objects:read")
public final Command getHardwareCommand(String action) {
if ((action != null) && (!action.trim().isEmpty())) {
Command commandToSearch = commandsMapping.get(action.trim().toLowerCase());
if (commandToSearch != null) {
return commandToSearch;
} else {
LOG.log(Level.SEVERE, "Doesn''t exists a valid hardware command associated to action ''{0}'' of object ''{1}"
+ "''. \n"
+ "This are the available mappings between action -> command for object ''{2}'': {3}",
new Object[]{action, pojo.getName(), pojo.getName(), commandsMapping.toString()});
return null;
}
} else {
LOG.log(Level.SEVERE, "The action ''{0}'' is not valid in object ''{1}''",
new Object[]{action, pojo.getName()});
return null;
}
}
/**
* Create an HashMap with all object properties useful in an event
*
* @return a set of key/values of object properties
*/
@RequiresPermissions("objects:read")
public Map getExposedProperties() {
HashMap result = pojo.getExposedProperties();
return result;
}
/**
*
* @return
*/
@RequiresPermissions("objects:read")
public Map getExposedBehaviors() {
Map result = new HashMap();
for (BehaviorLogic behavior : getBehaviors()) {
result.put("object.behavior." + behavior.getName(),
behavior.getValueAsString());
}
return result;
}
/**
*
* @param newName
*/
@RequiresPermissions("objects:update")
public final void rename(String newName) {
String oldName = this.getPojo().getName();
newName = newName.trim();
LOG.log(Level.WARNING, "Renaming object ''{0}'' in ''{1}''", new Object[]{oldName, newName});
//change the object name
this.getPojo().setName(newName);
//change trigger references to this object
for (Trigger t : TriggerPersistence.getTriggers()) {
renameValuesInTrigger(t, oldName, newName);
}
//change commands references to this object
for (Command c : CommandPersistence.getUserCommands()) {
renameValuesInCommand(c, oldName, newName);
}
//rebuild reactions description
for (Reaction r : ReactionPersistence.getReactions()) {
r.setChanged();
}
}
/**
*
* @param action
* @param command
*/
@RequiresPermissions("objects:update")
public void setAction(String action, Command command) {
if ((action != null) && !action.isEmpty() && (command != null)) {
commandsMapping.put(action.trim(),
command);
pojo.getActions().setProperty(action.trim(),
command.getName());
}
}
/**
*
* @param trigger
* @param behaviorName
*/
@RequiresPermissions("objects:update")
public void addTriggerMapping(Trigger trigger, String behaviorName) {
//checking input parameters
if ((behaviorName == null) || behaviorName.isEmpty() || (trigger == null)) {
throw new IllegalArgumentException("behavior name and trigger cannot be null");
}
//parameters in input are ok, continue...
Iterator> it = pojo.getTriggers().entrySet().iterator();
//remove old references if any
while (it.hasNext()) {
Entry e = it.next();
if (e.getValue().equals(behaviorName)) {
it.remove(); //remove the old value that had to be updated
}
}
pojo.getTriggers().setProperty(trigger.getName(), behaviorName);
LOG.log(Level.CONFIG, "Trigger mapping in object {0}: behavior ''{1}'' is now associated to trigger named ''{2}''",
new Object[]{this.getPojo().getName(), behaviorName, trigger.getName()});
}
/**
*
* @param t
* @return
*/
@RequiresPermissions("objects:read")
public String getBehaviorNameMappedToTrigger(String t) {
return getPojo().getTriggers().getProperty(t);
}
/**
*
* @param value
*/
@RequiresPermissions("objects:update")
public synchronized void setChanged(boolean value) {
if (value == true) {
this.changed = true;
ObjectHasChangedBehavior objectEvent = new ObjectHasChangedBehavior(this, this);
//send multicast because an event must be received by all triggers registred on the destination channel
LOG.log(Level.FINE, "Object {0} changes something in its status (eg: a behavior value)",
this.getPojo().getName());
Freedomotic.sendEvent(objectEvent);
} else {
changed = false;
}
}
/**
* When defining an object logic the registration of its behaviors is needed
* otherwise they are not used.
*
* @param b
*/
@RequiresPermissions("objects:update")
public final void registerBehavior(BehaviorLogic b) {
if (getBehavior(b.getName()) != null) {
behaviors.remove(b.getName());
LOG.warning("Re-registering existing behavior " + b.getName() + " in object " + this.getPojo().getName());
//throw new IllegalArgumentException("Impossible to register behavior " + b.getName() + " in object "
// + this.getPojo().getName() + " because it is already registed");
}
behaviors.put(b.getName(), b);
}
/**
* Finds a behavior using its name (case sensitive)
*
* @param name
* @return the reference to the behavior or null if it doesn't exists
*/
@RequiresPermissions("objects:read")
public final BehaviorLogic getBehavior(String name) {
BehaviorLogic behaviorLogic = behaviors.get(name);
// Manage the case the behavior is not found
if (behaviorLogic == null) {
// Create a list of available behaviors
StringBuilder buff = new StringBuilder();
for (BehaviorLogic behavior : behaviors.values()) {
buff.append(behavior.getName()).append(" ");
}
// Pring an user friendly message
LOG.log(Level.SEVERE, "Cannot find a behavior named ''{0}'' for thing named ''{1}''. "
+ "Avalable behaviors for this Thing are: {2}",
new Object[]{name, getPojo().getName(), buff.toString()});
}
return behaviorLogic;
}
/**
* Caches developers level commands and creates user level commands as
* specified in the createCommands() method of its subclasses
*/
@RequiresPermissions("objects:read")
public void init() {
//validation
if (pojo == null) {
throw new IllegalStateException("An object must have a valid pojo before initialization");
}
pojo.initTags();
createCommands();
createTriggers();
commandsMapping = new HashMap();
cacheDeveloperLevelCommand();
// assign object to an environment
this.setEnvironment(environmentRepository.findOne(pojo.getEnvironmentID()));
// update topology information
updateTopology();
}
@Deprecated
@RequiresPermissions("objects:read")
private boolean isChanged() {
return changed;
}
/**
*
* @return
*/
@RequiresPermissions("objects:read")
public EnvironmentLogic getEnvironment() {
return this.environment;
}
/**
*
* @return
*/
@RequiresPermissions("objects:read")
public EnvObject getPojo() {
// if (pojo.getUUID() == null || auth.isPermitted("objects:read:" + pojo.getUUID().substring(0, 5))
// ) {
return pojo;
// }
// return null;
}
/**
*
*/
@RequiresPermissions("objects:delete")
public final void destroy() {
pojo = null;
commandsMapping.clear();
commandsMapping = null;
behaviors.clear();
behaviors = null;
}
/**
*
* @param obj
* @return
*/
@Override
@RequiresPermissions("objects:read")
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final EnvObjectLogic other = (EnvObjectLogic) obj;
if ((this.pojo != other.pojo) && ((this.pojo == null) || !this.pojo.equals(other.pojo))) {
return false;
}
return true;
}
/**
*
* @return
*/
@Override
@RequiresPermissions("objects:read")
public int hashCode() {
int hash = 7;
hash = (53 * hash) + ((this.pojo != null) ? this.pojo.hashCode() : 0);
return hash;
}
/**
*
* @return
*/
@RequiresPermissions("objects:read")
public Iterable getBehaviors() {
return behaviors.values();
}
/**
*
*/
@RequiresPermissions("objects:create")
public final void setRandomLocation() {
int randomX = 0 + (int) (Math.random() * environmentRepository.findAll().get(0).getPojo().getWidth());
int randomY = 0 + (int) (Math.random() * environmentRepository.findAll().get(0).getPojo().getHeight());
setLocation(randomX, randomY);
}
/**
*
* @param x
* @param y
*/
@RequiresPermissions("objects:update")
public void setLocation(int x, int y) {
for (Representation rep : getPojo().getRepresentations()) {
rep.setOffset(x, y);
}
updateTopology();
//commit the changes to this object
setChanged(true);
}
/**
* Sets the object location without invoking an object change notification
* An user should never use this method. It's needed by the framework and
* reserver for it's exclusive use.
*/
public void synchLocation(int x, int y) {
for (Representation rep : getPojo().getRepresentations()) {
rep.setOffset(x, y);
}
updateTopology();
}
@RequiresPermissions({"objects:read", "zones.update"})
private void updateTopology() {
FreedomShape shape = getPojo().getRepresentations().get(0).getShape();
int xoffset = getPojo().getCurrentRepresentation().getOffset().getX();
int yoffset = getPojo().getCurrentRepresentation().getOffset().getY();
//now apply offset to the shape
FreedomPolygon translatedObject
= (FreedomPolygon) TopologyUtils.translate((FreedomPolygon) shape, xoffset, yoffset);
//REGRESSION
for (EnvironmentLogic locEnv : environmentRepository.findAll()) {
for (ZoneLogic zone : locEnv.getZones()) {
if (this.getEnvironment() == locEnv && TopologyUtils.intersects(translatedObject, zone.getPojo().getShape())) {
//DEBUG: System.out.println("object " + getPojo().getName() + " intersects zone " + zone.getPojo().getName());
//add to the zones this object belongs
zone.getPojo().getObjects().add(this.getPojo());
LOG.log(Level.FINE, "Object {0} is in zone {1}", new Object[]{getPojo().getName(), zone.getPojo().getName()});
} else {
//remove from the zone
zone.getPojo().getObjects().remove(this.getPojo());
//DEBUG: System.out.println("object " + getPojo().getName() + " NOT intersects zone " + zone.getPojo().getName());
}
}
}
}
/**
* Changes a behavior value accordingly to the value property in the trigger
* in input
*
* @param trigger an hardware level trigger
* @return true if the values is applied successfully, false otherwise
*/
public final boolean executeTrigger(Trigger trigger) {
// Get the behavior name connected to the trigger in input
String behaviorName = getBehaviorNameMappedToTrigger(trigger.getName());
// If missing because it's not an hardware trigger check if it is specified in the trigger itself
if (behaviorName == null) {
//LOG.severe("Hardware trigger '" + t.getName() + "' is not bound to any action of object " + this.getPojo().getName());
//check if the behavior name is written in the trigger
behaviorName = trigger.getPayload().getStatements("behavior.name").isEmpty()
? "" : trigger.getPayload().getStatements("behavior.name").get(0).getValue();
if (behaviorName.isEmpty()) {
return false;
}
}
Statement valueStatement = trigger.getPayload().getStatements("behaviorValue").get(0);
if (valueStatement == null) {
LOG.log(Level.WARNING,
"No value in hardware trigger ''{0}'' to apply to behavior ''{1}'' of Thing {2}",
new Object[]{trigger.getName(), behaviorName, getPojo().getName()});
return false;
}
LOG.log(Level.CONFIG,
"Sensors notification ''{0}'' is going to change ''{1}'' behavior ''{2}'' to ''{3}''",
new Object[]{trigger.getName(), getPojo().getName(), behaviorName, valueStatement.getValue()});
Config params = new Config();
params.setProperty("value", valueStatement.getValue());
// Validating the target behavior
BehaviorLogic behavior = getBehavior(behaviorName);
if (behavior != null) {
behavior.filterParams(params, false); //false means not fire commands, only change behavior value
} else {
LOG.log(Level.SEVERE, "Cannot apply trigger ''{0}'' to Thing {1}", new Object[]{trigger.getName(), getPojo().getName()});
return false;
}
return true;
}
/**
* Executes the hardware command related to the action passed as paramenter
* using an user command.
*
* @param action the name of the action to executeCommand as defined in the
* object XML
* @param params parameters of the event that have started the reaction
* execution
* @return true if the command is succesfully executed by the actuator and
* false otherways
*/
@RequiresPermissions("objects:read")
protected final boolean executeCommand(final String action, final Config params) {
LOG.log(Level.FINE, "Executing action ''{0}'' of object ''{1}''", new Object[]{action, getPojo().getName()});
if (getPojo().getActAs().equalsIgnoreCase("virtual")) {
//it's a virtual object like a button, not needed real execution of a command
LOG.log(Level.CONFIG,
"The object ''{0}'' act as virtual device, so its hardware commands are not executed.",
getPojo().getName());
return true;
}
final Command command = getHardwareCommand(action.trim());
if (command == null) {
LOG.log(Level.WARNING,
"The hardware level command for action ''{0}'' in object ''{1}'' doesn''t exists or is not set",
new Object[]{action, pojo.getName()});
return false; //command not executed
}
//resolves developer level command parameters like myObjectName = "@event.object.name" -> myObjectName = "Light 1"
//in this case the parameter in the userLevelCommand are used as basis for the resolution process (the context)
//along with the parameters getted from the relative behavior (if exists)
LOG.log(Level.FINE, "Environment object ''{0}'' tries to ''{1}'' itself using hardware command ''{2}''",
new Object[]{pojo.getName(), action, command.getName()});
Resolver resolver = new Resolver();
//adding a resolution context for object that owns this hardware level command. 'owner.' is the prefix of this context
resolver.addContext("request.", params);
resolver.addContext("owner.", getExposedProperties());
resolver.addContext("owner.", getExposedBehaviors());
try {
final Command resolvedCommand = resolver.resolve(command); //eg: turn on an X10 device
Command result;
//mark the command as not executed if it is supposed to not return
//an execution state value
if (Boolean.valueOf(command.getProperty("send-and-forget")) == true) {
LOG.config("Command '" + resolvedCommand.getName() + "' is 'send-and-forget' no execution result will be catched from plugin's reply");
resolvedCommand.setReplyTimeout(-1); //disable reply request
Freedomotic.sendCommand(resolvedCommand);
return false;
} else {
//10 seconds is the default timeout if not already set
if (resolvedCommand.getReplyTimeout() < 1) {
resolvedCommand.setReplyTimeout(10000); //enable reply request
}
result = Freedomotic.sendCommand(resolvedCommand); //blocking wait until timeout
}
if (result == null) {
LOG.log(Level.WARNING, "Received null reply after sending hardware command " + resolvedCommand.getName());
} else if (result.isExecuted()) {
return true; //succesfully executed
}
} catch (CloneNotSupportedException ex) {
Logger.getLogger(EnvObjectLogic.class.getName()).log(Level.SEVERE, null, ex);
} catch (VariableResolutionException ex) {
Logger.getLogger(EnvObjectLogic.class.getName()).log(Level.SEVERE, null, ex);
}
return false; //command not executed
}
/**
*
*/
protected void createCommands() {
//default empty implementation
}
/**
*
*/
protected void createTriggers() {
//default empty implementation
}
/**
*
* @param pojo
*/
@RequiresPermissions("objects:update")
protected void setPojo(EnvObject pojo) {
this.pojo = pojo;
}
@RequiresPermissions({"objects:update", "triggers:update"})
private void renameValuesInTrigger(Trigger t, String oldName, String newName) {
if (!t.isHardwareLevel()) {
if (t.getName().contains(oldName)) {
t.setName(t.getName().replace(oldName, newName));
LOG.log(Level.WARNING, "trigger name renamed to {0}", t.getName());
}
Iterator it = t.getPayload().iterator();
while (it.hasNext()) {
Statement statement = it.next();
if (statement.getValue().contains(oldName)) {
statement.setValue(statement.getValue().replace(oldName, newName));
LOG.log(Level.WARNING, "Trigger value in payload renamed to {0}", statement.getValue());
}
}
}
}
@RequiresPermissions({"objects:read", "commands:update"})
private void renameValuesInCommand(Command c, String oldName, String newName) {
if (c.getName().contains(oldName)) {
c.setName(c.getName().replace(oldName, newName));
LOG.log(Level.WARNING, "Command name renamed to {0}", c.getName());
}
if (c.getProperty("object") != null) {
if (c.getProperty("object").contains(oldName)) {
c.setProperty("object",
c.getProperty("object").replace(oldName, newName));
LOG.log(Level.WARNING, "Property ''object'' in command renamed to {0}", c.getProperty("object"));
}
}
}
private void cacheDeveloperLevelCommand() {
if (commandsMapping == null) {
commandsMapping = new HashMap();
}
for (String action : pojo.getActions().stringPropertyNames()) {
String commandName = pojo.getActions().getProperty(action);
Command command = CommandPersistence.getHardwareCommand(commandName);
if (command != null) {
LOG.log(Level.CONFIG,
"Caching the command ''{0}'' as related to action ''{1}'' ",
new Object[]{command.getName(), action});
setAction(action, command);
} else {
LOG.log(Level.CONFIG,
"Don''t exist a command called ''{0}'' is not possible to bound this command to action ''{1}'' of {2}",
new Object[]{commandName, action, this.getPojo().getName()});
}
}
}
/**
*
* @param selEnv
*/
@RequiresPermissions("objects:update")
public void setEnvironment(EnvironmentLogic selEnv) {
if (selEnv == null) {
LOG.warning("Trying to assign a null environment to object " + this.getPojo().getName()
+ ". It will be relocated to the fallback environment");
selEnv = environmentRepository.findAll().get(0);
if (selEnv == null) {
throw new IllegalArgumentException("Fallback environment is null for object " + getPojo().getName());
}
}
this.environment = selEnv;
getPojo().setEnvironmentID(selEnv.getPojo().getUUID());
}
/**
*
* @param tagList
*/
@RequiresPermissions("objects:update")
public void addTags(String tagList) {
String[] tags = tagList.toLowerCase().split(",");
getPojo().getTagsList().addAll(Arrays.asList(tags));
}
private static final Logger LOG = Logger.getLogger(EnvObjectLogic.class.getName());
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy