/*
* #!
* %
* 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.HashSet;
import java.util.Map;
import java.util.Set;
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.internal.AccidentTuple;
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.Constants;
import de.hub.cs.dbis.lrb.util.Time;
/**
* {@link AccidentDetectionBolt} processed stopped vehicle reports and emits accident information for further
* processing. The input is expected to be of type {@link StoppedCarTuple}, to be ordered by timestamp, and must be
* grouped by {@link PositionIdentifier}.<br />
* <br />
* <strong>Input schema:</strong> {@link StoppedCarTuple}<br />
* <strong>Output schema:</strong> {@link AccidentTuple} (stream: {@link TopologyControl#ACCIDENTS_STREAM_ID})
*
* @author msoyka
* @author richter
* @author mjsax
*/
public class AccidentDetectionBolt extends BaseRichBolt {
private static final long serialVersionUID = 5537727428628598519L;
private static final Logger LOGGER = LoggerFactory.getLogger(AccidentDetectionBolt.class);
/** The storm provided output collector. */
private OutputCollector collector;
/** Internally (re)used object to access individual attributes. */
private final StoppedCarTuple inputStoppedCarTuple = new StoppedCarTuple();
/** Internally (re)used object. */
private final PositionIdentifier stoppedVehiclePosition = new PositionIdentifier();
/** Hold all vehicles that have <em>stopped</em> within a segment. */
private final Map<PositionIdentifier, Set<Integer>> stoppedCarsPerPosition = new HashMap<PositionIdentifier, Set<Integer>>();
/** The currently processed 'minute number'. */
private short 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)) {
Object ts = input.getValue(0);
if(ts == null) {
this.collector.emit(TimestampMerger.FLUSH_STREAM_ID, new Values((Object)null));
} else {
this.checkMinute(Time.getMinute(((Number)ts).longValue()));
}
this.collector.ack(input);
return;
}
this.inputStoppedCarTuple.clear();
this.inputStoppedCarTuple.addAll(input.getValues());
LOGGER.trace(this.inputStoppedCarTuple.toString());
this.checkMinute(this.inputStoppedCarTuple.getMinuteNumber());
assert (this.inputStoppedCarTuple.getLane().shortValue() != Constants.EXIT_LANE);
Integer vid = this.inputStoppedCarTuple.getVid();
// negative VID implies "move" after "stop"
int v = vid.intValue();
boolean isStopReport = v > 0;
this.stoppedVehiclePosition.set(this.inputStoppedCarTuple);
Set<Integer> stoppedCars = this.stoppedCarsPerPosition.get(this.stoppedVehiclePosition);
if(isStopReport) {
if(stoppedCars == null) {
stoppedCars = new HashSet<Integer>();
stoppedCars.add(vid);
this.stoppedCarsPerPosition.put(this.stoppedVehiclePosition.copy(), stoppedCars);
} else {
stoppedCars.add(vid);
if(stoppedCars.size() > 1) {
this.collector.emit(TopologyControl.ACCIDENTS_STREAM_ID, new AccidentTuple(
this.inputStoppedCarTuple.getTime(), this.inputStoppedCarTuple.getXWay(),
this.inputStoppedCarTuple.getSegment(), this.inputStoppedCarTuple.getDirection()));
}
}
} else {
stoppedCars.remove(new Integer(-v));
if(stoppedCars.isEmpty()) {
this.stoppedCarsPerPosition.remove(this.inputStoppedCarTuple);
}
}
this.collector.ack(input);
}
private void checkMinute(short minute) {
assert (minute >= this.currentMinute);
if(minute > this.currentMinute) {
LOGGER.trace("New minute: {}", new Short(minute));
this.currentMinute = minute;
this.collector.emit(TimestampMerger.FLUSH_STREAM_ID, new Values(new Short((short)((minute * 60) - 61))));
}
}
@Override
public void declareOutputFields(OutputFieldsDeclarer declarer) {
declarer.declareStream(TopologyControl.ACCIDENTS_STREAM_ID, AccidentTuple.getSchema());
declarer.declareStream(TimestampMerger.FLUSH_STREAM_ID, new Fields("ts"));
}
}