package com.lucidworks.storm.spring;
import java.io.Closeable;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import backtype.storm.Config;
import backtype.storm.Constants;
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 com.lucidworks.storm.StreamingApp;
import org.apache.log4j.Logger;
/**
* Executes a Spring-managed BoltAction implementation.
*/
public class SpringBolt extends BaseRichBolt {
private static final Logger log = Logger.getLogger(SpringBolt.class);
public static final boolean isTickTuple(final Tuple tuple) {
return tuple != null &&
Constants.SYSTEM_COMPONENT_ID.equals(tuple.getSourceComponent()) &&
Constants.SYSTEM_TICK_STREAM_ID.equals(tuple.getSourceStreamId());
}
public static enum ExecuteResult {
ACK, BUFFERED, IGNORED
}
protected String boltLogicBeanId;
protected Fields outputFields;
protected int tickRate = -1;
private boolean isTickTupleAware = false;
private transient StreamingDataAction delegate;
private transient OutputCollector collector;
private transient Map stormConf;
// bolt action impls may employ a small buffer to avoid sending too many requests to an external system
// but we don't want to ack tuples until the action verifies the buffer was successfully sent
private transient LinkedList<Tuple> bufferedTuples;
public SpringBolt(String boltBeanId) {
this(boltBeanId, null, -1);
}
public SpringBolt(String boltBeanId, int tickRate) {
this(boltBeanId, null, tickRate);
}
public SpringBolt(String boltBeanId, Fields outputFields) {
this(boltBeanId, outputFields, -1);
}
public SpringBolt(String boltBeanId, Fields outputFields, int tickRate) {
this.boltLogicBeanId = boltBeanId;
this.outputFields = outputFields;
this.tickRate = tickRate;
}
public int getTickRate() {
return tickRate;
}
public void prepare(Map map, TopologyContext topologyContext, OutputCollector outputCollector) {
this.stormConf = map;
this.collector = outputCollector;
getStreamingDataActionBean();
bufferedTuples = new LinkedList<Tuple>();
}
public void execute(Tuple input) {
try {
ExecuteResult result = ExecuteResult.IGNORED;
if (isTickTuple(input)) {
if (isTickTupleAware)
result = ((TickTupleAware) delegate).onTick();
} else {
result = delegate.execute(input, collector);
}
if (result == ExecuteResult.IGNORED) {
// bolt action ignored this tuple, so we just ack and keep processing
collector.ack(input);
return;
}
bufferedTuples.add(input);
if (result == ExecuteResult.ACK) {
// ack the current tuple and all buffered tuples
try {
for (Tuple buffered : bufferedTuples)
collector.ack(buffered);
} finally {
bufferedTuples.clear();
}
}
} catch (Throwable exc) {
collector.reportError(exc);
bufferedTuples.add(input);
try {
for (Tuple buffered : bufferedTuples)
collector.fail(buffered);
} finally {
bufferedTuples.clear();
}
}
}
protected StreamingDataAction getStreamingDataActionBean() {
if (delegate == null) {
// Get the Bolt Logic POJO from Spring
delegate = (StreamingDataAction) StreamingApp.spring(stormConf).getBean(boltLogicBeanId);
isTickTupleAware = (delegate instanceof TickTupleAware);
}
return delegate;
}
public void declareOutputFields(OutputFieldsDeclarer outputFieldsDeclarer) {
if (outputFields != null && outputFields.size() > 0)
outputFieldsDeclarer.declare(outputFields);
}
@Override
public Map<String, Object> getComponentConfiguration() {
Map<String, Object> conf = new HashMap<String, Object>();
if (tickRate > 0)
conf.put(Config.TOPOLOGY_TICK_TUPLE_FREQ_SECS, tickRate);
return conf;
}
@Override
public void cleanup() {
StreamingDataAction sda = getStreamingDataActionBean();
if (sda instanceof Closeable) {
try {
((Closeable) sda).close();
} catch (Exception ignore) {
log.warn("Error when trying to close StreamingDataAction due to: "+ignore);
}
}
}
}