package org.tomdz.storm.esper; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import backtype.storm.generated.GlobalStreamId; import backtype.storm.task.OutputCollector; import backtype.storm.task.TopologyContext; import backtype.storm.topology.OutputFieldsDeclarer; import backtype.storm.tuple.Fields; import backtype.storm.tuple.Tuple; import backtype.storm.topology.base.BaseRichBolt; import com.espertech.esper.client.Configuration; import com.espertech.esper.client.EPAdministrator; import com.espertech.esper.client.EPRuntime; import com.espertech.esper.client.EPServiceProvider; import com.espertech.esper.client.EPServiceProviderManager; import com.espertech.esper.client.EPStatement; import com.espertech.esper.client.EventBean; import com.espertech.esper.client.UpdateListener; public class EsperBolt extends BaseRichBolt implements UpdateListener { private static final long serialVersionUID = 1L; public static class Builder { protected final EsperBolt bolt; public Builder() { this(new EsperBolt()); } protected Builder(EsperBolt bolt) { this.bolt = bolt; } public InputsBuilder inputs() { return new InputsBuilder(bolt); } public OutputsBuilder outputs() { return new OutputsBuilder(bolt); } public StatementsBuilder statements() { return new StatementsBuilder(bolt); } public EsperBolt build() { return bolt; } } public static class InputsBuilder extends Builder { private InputsBuilder(EsperBolt bolt) { super(bolt); } public AliasedInputBuilder aliasComponent(String componentId) { return new AliasedInputBuilder(bolt, new StreamId(componentId)); } public AliasedInputBuilder aliasStream(String componentId, String streamId) { return new AliasedInputBuilder(bolt, new StreamId(componentId, streamId)); } } public static final class AliasedInputBuilder { private final EsperBolt bolt; private final StreamId streamId; private final Map<String, String> fieldTypes; private AliasedInputBuilder(EsperBolt bolt, StreamId streamId) { this(bolt, streamId, new HashMap<String, String>()); } private AliasedInputBuilder(EsperBolt bolt, StreamId streamId, Map<String, String> fieldTypes) { this.bolt = bolt; this.streamId = streamId; this.fieldTypes = fieldTypes; } public TypedInputBuilder withField(String fieldNames) { return new TypedInputBuilder(bolt, streamId, fieldTypes, fieldNames); } public TypedInputBuilder withFields(String... fieldNames) { return new TypedInputBuilder(bolt, streamId, fieldTypes, fieldNames); } public InputsBuilder toEventType(String name) { bolt.addInputAlias(streamId, name, new TupleTypeDescriptor(fieldTypes)); return new InputsBuilder(bolt); } } public static final class TypedInputBuilder { private final EsperBolt bolt; private final StreamId streamId; private final Map<String, String> fieldTypes; private final String[] fieldNames; private TypedInputBuilder(EsperBolt bolt, StreamId streamId, Map<String, String> fieldTypes, String... fieldNames) { this.bolt = bolt; this.streamId = streamId; this.fieldTypes = fieldTypes; this.fieldNames = fieldNames; } public AliasedInputBuilder ofType(Class<?> type) { for (String fieldName : fieldNames) { fieldTypes.put(fieldName, type.getName()); } return new AliasedInputBuilder(bolt, streamId, fieldTypes); } } public static final class OutputsBuilder extends Builder { private OutputsBuilder(EsperBolt bolt) { super(bolt); } public OutputStreamBuilder onStream(String streamName) { return new OutputStreamBuilder(bolt, streamName); } public OutputStreamBuilder onDefaultStream() { return new OutputStreamBuilder(bolt, "default"); } } public static final class OutputStreamBuilder { private final EsperBolt bolt; private final String streamName; private OutputStreamBuilder(EsperBolt bolt, String streamName) { this.bolt = bolt; this.streamName = streamName; } public NamedOutputStreamBuilder fromEventType(String name) { return new NamedOutputStreamBuilder(bolt, streamName, name); } public OutputsBuilder emit(String... fields) { bolt.setAnonymousOutput(streamName, fields); return new OutputsBuilder(bolt); } } public static final class NamedOutputStreamBuilder { private final EsperBolt bolt; private final String streamName; private final String eventTypeName; private NamedOutputStreamBuilder(EsperBolt bolt, String streamName, String eventTypeName) { this.bolt = bolt; this.streamName = streamName; this.eventTypeName = eventTypeName; } public OutputsBuilder emit(String... fields) { bolt.addNamedOutput(streamName, eventTypeName, fields); return new OutputsBuilder(bolt); } } public static final class StatementsBuilder extends Builder { private StatementsBuilder(EsperBolt bolt) { super(bolt); } public StatementsBuilder add(String statement) { bolt.addStatement(statement); return this; } } private final Map<StreamId, String> inputAliases = new LinkedHashMap<StreamId, String>(); private final Map<StreamId, TupleTypeDescriptor> tupleTypes = new LinkedHashMap<StreamId, TupleTypeDescriptor>(); private final Map<String, EventTypeDescriptor> eventTypes = new LinkedHashMap<String, EventTypeDescriptor>(); private final List<String> statements = new ArrayList<String>(); private transient EPServiceProvider esperSink; private transient EPRuntime runtime; private transient EPAdministrator admin; private transient OutputCollector collector; private EsperBolt() { } private void addInputAlias(StreamId streamId, String name, TupleTypeDescriptor typeDesc) { inputAliases.put(streamId, name); if (typeDesc != null) { tupleTypes.put(streamId, typeDesc); } } private void addNamedOutput(String streamId, String eventTypeName, String... fields) { eventTypes.put(eventTypeName, new EventTypeDescriptor(eventTypeName, fields, streamId)); } private void setAnonymousOutput(String streamId, String... fields) { eventTypes.put(null, new EventTypeDescriptor(null, fields, streamId)); } private void addStatement(String stmt) { statements.add(stmt); } public EventTypeDescriptor getEventType(String name) { return eventTypes.get(name); } public Collection<EventTypeDescriptor> getEventTypes() { return new ArrayList<EventTypeDescriptor>(eventTypes.values()); } public EventTypeDescriptor getEventTypeForStreamId(String streamId) { for (EventTypeDescriptor eventType: eventTypes.values()) { if (streamId.equals(eventType.getStreamId())) { return eventType; } } return null; } @Override public void declareOutputFields(OutputFieldsDeclarer declarer) { for (EventTypeDescriptor eventType: eventTypes.values()) { declarer.declareStream(eventType.getStreamId(), eventType.getFields()); } } @Override public void prepare(@SuppressWarnings("rawtypes") Map conf, TopologyContext context, OutputCollector collector) { this.collector = collector; Configuration configuration = new Configuration(); setupEventTypes(context, configuration); this.esperSink = EPServiceProviderManager.getProvider(this.toString(), configuration); this.esperSink.initialize(); this.runtime = esperSink.getEPRuntime(); this.admin = esperSink.getEPAdministrator(); for (String stmt : statements) { EPStatement statement = admin.createEPL(stmt); statement.addListener(this); } } private String getEventTypeName(String componentId, String streamId) { String alias = inputAliases.get(new StreamId(componentId, streamId)); if (alias == null) { alias = String.format("%s_%s", componentId, streamId); } return alias; } private void setupEventTypes(TopologyContext context, Configuration configuration) { Set<GlobalStreamId> sourceIds = context.getThisSources().keySet(); for (GlobalStreamId id : sourceIds) { String eventTypeName = getEventTypeName(id.get_componentId(), id.get_streamId()); Fields fields = context.getComponentOutputFields(id.get_componentId(), id.get_streamId()); TupleTypeDescriptor typeDesc = tupleTypes.get(new StreamId(id.get_componentId(), id.get_streamId())); Map<String, Object> props = setupEventTypeProperties(fields, typeDesc); configuration.addEventType(eventTypeName, props); } } private Map<String, Object> setupEventTypeProperties(Fields fields, TupleTypeDescriptor typeDesc) { Map<String, Object> properties = new HashMap<String, Object>(); int numFields = fields.size(); for (int idx = 0; idx < numFields; idx++) { String fieldName = fields.get(idx); Class<?> clazz = null; if (typeDesc != null) { String clazzName = typeDesc.getFieldType(fieldName); if (clazzName != null) { try { clazz = Class.forName(clazzName); } catch (ClassNotFoundException ex) { throw new RuntimeException("Cannot find class " + clazzName + "declared for field " + fieldName); } } } if (clazz == null) { clazz = Object.class; } properties.put(fieldName, clazz); } return properties; } @Override public void execute(Tuple tuple) { String eventType = getEventTypeName(tuple.getSourceComponent(), tuple.getSourceStreamId()); Map<String, Object> data = new HashMap<String, Object>(); Fields fields = tuple.getFields(); int numFields = fields.size(); for (int idx = 0; idx < numFields; idx++) { String name = fields.get(idx); Object value = tuple.getValue(idx); data.put(name, value); } runtime.sendEvent(data, eventType); collector.ack(tuple); } @Override public void update(EventBean[] newEvents, EventBean[] oldEvents) { if (newEvents != null) { for (EventBean newEvent : newEvents) { EventTypeDescriptor eventType = getEventType(newEvent.getEventType().getName()); if (eventType == null) { // anonymous event ? eventType = getEventType(null); } if (eventType != null) { collector.emit(eventType.getStreamId(), toTuple(newEvent, eventType.getFields())); } } } } private List<Object> toTuple(EventBean event, Fields fields) { int numFields = fields.size(); List<Object> tuple = new ArrayList<Object>(numFields); for (int idx = 0; idx < numFields; idx++) { tuple.add(event.get(fields.get(idx))); } return tuple; } @Override public void cleanup() { if (esperSink != null) { esperSink.destroy(); } } }