/*
* #!
* %
* Copyright (C) 2014 - 2016 Humboldt-Universität zu Berlin
* %
* 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 de.hub.cs.dbis.lrb.operators;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import backtype.storm.task.OutputCollector;
import backtype.storm.task.TopologyContext;
import backtype.storm.topology.OutputFieldsDeclarer;
import backtype.storm.topology.base.BaseRichBolt;
import backtype.storm.tuple.Fields;
import backtype.storm.tuple.Tuple;
import backtype.storm.tuple.Values;
import de.hub.cs.dbis.aeolus.utils.TimestampMerger;
import de.hub.cs.dbis.lrb.queries.utils.TopologyControl;
import de.hub.cs.dbis.lrb.types.PositionReport;
import de.hub.cs.dbis.lrb.types.internal.StoppedCarTuple;
import de.hub.cs.dbis.lrb.types.util.PositionIdentifier;
import de.hub.cs.dbis.lrb.util.Time;
/**
* {@link StoppedCarsBolt} registers every stopped vehicle (that are not on the exit lane) and emits "stopped car"
* reports for further processing. The input is expected to be of type {@link PositionReport}, to be ordered by
* timestamp, and must be grouped by {@link TopologyControl#VEHICLE_ID_FIELD_NAME}.<br />
* <br />
* <strong>Input schema:</strong> {@link PositionReport}<br />
* <strong>Output schema:</strong> {@link StoppedCarTuple}
*
* @author mjsax
*/
public class StoppedCarsBolt extends BaseRichBolt {
private static final long serialVersionUID = -367873282705758387L;
private static final Logger LOGGER = LoggerFactory.getLogger(StoppedCarsBolt.class);
/**
* Number of consecutive {@link PositionReport}s for a car with same lane and position (grouped by highway and
* direction) to consider a car to be "stopped".
*/
private static final int STOPPED = 4;
/** The storm provided output collector. */
private OutputCollector collector;
/** Internally (re)used object to access individual attributes. */
private final PositionReport inputPositionReport = new PositionReport();
/** Internally (re)used object. */
private final PositionIdentifier vehiclePosition = new PositionIdentifier();
/** Internally (re)used object. */
private final PositionReport lastPositionReport = new PositionReport();
/** Internally (re)used object. */
private final PositionIdentifier lastVehiclePosition = new PositionIdentifier();
/** Holds the last positions for each vehicle (if those positions are equal to each other). */
private final Map<Integer, List<PositionReport>> lastPositions = new HashMap<Integer, List<PositionReport>>();
/** The currently processed 'minute number'. */
private int currentMinute = -1;
@Override
public void prepare(@SuppressWarnings("rawtypes") Map conf, TopologyContext context, OutputCollector collector) {
this.collector = collector;
}
@Override
public void execute(Tuple input) {
if(input.getSourceStreamId().equals(TimestampMerger.FLUSH_STREAM_ID)) {
if(input.getValue(0) == null) {
this.collector.emit(TimestampMerger.FLUSH_STREAM_ID, new Values((Object)null));
}
this.collector.ack(input);
return;
}
this.inputPositionReport.clear();
this.inputPositionReport.addAll(input.getValues());
LOGGER.trace(this.inputPositionReport.toString());
Integer vid = this.inputPositionReport.getVid();
Short time = this.inputPositionReport.getTime();
int minute = Time.getMinute(time.longValue());
assert (minute >= this.currentMinute);
if(minute > this.currentMinute) {
LOGGER.trace("New minute: {}", new Integer(minute));
this.currentMinute = minute;
this.collector.emit(TimestampMerger.FLUSH_STREAM_ID, new Values(time));
}
if(this.inputPositionReport.isOnExitLane()) {
this.lastPositions.remove(vid);
this.collector.ack(input);
return;
}
List<PositionReport> vehiclePositions = this.lastPositions.get(vid);
if(vehiclePositions == null) {
vehiclePositions = new LinkedList<PositionReport>();
vehiclePositions.add(this.inputPositionReport.copy());
this.lastPositions.put(vid, vehiclePositions);
this.collector.ack(input);
return;
}
this.lastPositionReport.clear();
this.lastPositionReport.addAll(vehiclePositions.get(0));
assert (this.inputPositionReport.getTime().shortValue() == this.lastPositionReport.getTime().shortValue() + 30);
this.vehiclePosition.set(this.inputPositionReport);
this.lastVehiclePosition.set(this.lastPositionReport);
if(this.vehiclePosition.equals(this.lastVehiclePosition)) {
vehiclePositions.add(0, this.inputPositionReport.copy());
if(vehiclePositions.size() >= STOPPED) {
if(vehiclePositions.size() > STOPPED) {
assert (vehiclePositions.size() == STOPPED + 1);
vehiclePositions.remove(STOPPED);
}
this.collector.emit(new StoppedCarTuple(vid, time, this.inputPositionReport.getXWay(),
this.inputPositionReport.getLane(), this.inputPositionReport.getPosition(),
this.inputPositionReport.getDirection()));
}
this.collector.ack(input);
return;
}
if(vehiclePositions.size() == STOPPED) { // was "stopped" -- moves again -- send negative VID
this.collector.emit(new StoppedCarTuple(new Integer(-vid.intValue()), time, this.inputPositionReport
.getXWay(), this.lastPositionReport.getLane(), this.lastPositionReport.getPosition(),
this.lastPositionReport.getDirection()));
}
vehiclePositions.clear();
vehiclePositions.add(this.inputPositionReport.copy());
this.collector.ack(input);
}
@Override
public void declareOutputFields(OutputFieldsDeclarer declarer) {
declarer.declare(StoppedCarTuple.getSchema());
declarer.declareStream(TimestampMerger.FLUSH_STREAM_ID, new Fields("ts"));
}
}