package uk.ac.imperial.lsds.seep.gc14.operator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import uk.ac.imperial.lsds.seep.comm.routing.Router;
import uk.ac.imperial.lsds.seep.comm.serialization.DataTuple;
import uk.ac.imperial.lsds.seep.operator.StatelessOperator;
public class Filter implements StatelessOperator {
private static final long serialVersionUID = 1L;
// We need to store the operatorId
private int operatorId = 0;
private static final int designatedOpId = 1;
public Boolean filterNotNew = true;
public Boolean filterNoChangeInValue = true;
public float filterWhenChangeLessThan = 5;
/*
* Setting the heart beat interval to less than zero
* disables sending of heart beats
*/
public int heartBeatIntervalInSec = 30;
private int lastHeartBeat = -1;
private Map<String, Integer> lastLoadTimeStamp = new HashMap<String, Integer>();
private Map<String, Integer> lastWorkTimeStamp = new HashMap<String, Integer>();
private Map<String, Float> lastLoadValue = new HashMap<String, Float>();
private Map<String, Float> lastWorkValue = new HashMap<String, Float>();
private Map<String, Integer> toSelectWorkValue = new HashMap<String, Integer>();
public Filter() {
}
@Override
public void setUp() {
this.operatorId = api.getOperatorId();
}
boolean first = true;
@Override
public void processData(DataTuple data) {
long id_int = data.getLong("id");
int timestamp = data.getInt("timestamp");
float value = data.getFloat("value");
int property = data.getInt("property");
int plug_id = data.getInt("plug_id");
int household_id = data.getInt("household_id");
int house_id = data.getInt("house_id");
if(lastHeartBeat == -1){
lastHeartBeat = timestamp;
}
DataTuple output = data.setValues(id_int, timestamp, value, property, plug_id, household_id, house_id);
String id = plug_id
+ "&"
+ household_id
+ "&"
+ house_id;
/*
* Filter measurements that are not new, i.e., have a timestamp equal or
* smaller than the last observed measurement for this particular plug
*/
if (this.filterNotNew) {
if (property == 0) {
if (!dataIsNew(lastWorkTimeStamp,data, id))
output = null;
lastWorkTimeStamp.put(id, timestamp);
}
else {
if (!dataIsNew(lastLoadTimeStamp,data, id))
output = null;
lastLoadTimeStamp.put(id, timestamp);
}
}
/*
* Filter measurements when there is no change in value w.r.t. the
* last measurement for the very same plug
*/
if (this.filterNoChangeInValue) {
if (property != 0) {
if (!dataValueChanged(lastLoadValue,data, id)) {
output = null;
}
else {
lastLoadValue.put(id, value);
// check whether we also need to forward a work measurement
if (lastWorkTimeStamp.containsKey(id)) {
if (lastWorkTimeStamp.get(id) == timestamp) {
DataTuple outputWork = data.setValues(id_int, timestamp, lastWorkValue.get(id), 0, plug_id, household_id, house_id);
try {
// We are splitting the stream by house id
api.send_splitKey(outputWork, Router.customHash(house_id));
}
catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
else {
// should we forward the work value?
if (toSelectWorkValue.containsKey(id)) {
if (!(toSelectWorkValue.get(id) == timestamp))
output = null;
}
else {
output = null;
}
// store value in case we need to forward it later (work comes before load)
lastWorkValue.put(id, value);
lastWorkTimeStamp.put(id, timestamp);
}
}
/*
* If the item has not been filtered, we send it
* to downstream operators
*/
if (output != null) {
try {
// We are splitting the stream by house id
api.send_splitKey(output, Router.customHash(house_id));
}
catch (Exception e) {
e.printStackTrace();
}
}
/**
* We want to make sure that even if scaled out, only one filter is in charge of generating heartbeats
*/
if(operatorId == designatedOpId){
/*
* If requested, check whether a heart beat should be sent. In order
* to send one, we rely on the same tuple structure than for the actual
* input data.
*/
if (this.heartBeatIntervalInSec >= 0) {
int current = timestamp;
if(current-this.lastHeartBeat >= this.heartBeatIntervalInSec){
DataTuple beat = data.setValues(-1L,current,0f,0,0,0,0,0);
// We need to notify all downstream ops with the heartbeat
api.send_all(beat);
this.lastHeartBeat = current;
}
}
}
}
private boolean dataIsNew(Map<String, Integer> oldTimeStamp, DataTuple data, String id) {
int dataTimeStamp = data.getInt("timestamp");
if (!oldTimeStamp.containsKey(id)) {
oldTimeStamp.put(id, dataTimeStamp);
return true;
}
if (dataTimeStamp > oldTimeStamp.get(id)) {
oldTimeStamp.put(id, dataTimeStamp);
return true;
}
return false;
}
private boolean dataValueChanged(Map<String, Float> lastValue, DataTuple data, String id) {
float dataValue = data.getFloat("value");
if (!lastValue.containsKey(id)) {
lastValue.put(id, dataValue);
return true;
}
if (Math.abs(lastValue.get(id)-dataValue)>filterWhenChangeLessThan) {
lastValue.put(id, dataValue);
return true;
}
return false;
}
@Override
public void processData(List<DataTuple> dataList) {
}
}