package com.zillabyte.motherbrain.flow.local; import java.util.List; import java.util.Map.Entry; import java.util.Set; import org.javatuples.Pair; import com.google.common.collect.HashMultimap; import com.google.common.collect.SetMultimap; import com.google.common.collect.Sets; import com.zillabyte.motherbrain.flow.MapTuple; import com.zillabyte.motherbrain.flow.collectors.OutputCollector; import com.zillabyte.motherbrain.flow.collectors.coordinated.ObserveIncomingTupleAction; import com.zillabyte.motherbrain.flow.graph.Connection; import com.zillabyte.motherbrain.flow.operations.Operation; import com.zillabyte.motherbrain.flow.operations.OperationException; import com.zillabyte.motherbrain.flow.operations.decorators.EmitDecorator; import com.zillabyte.motherbrain.relational.DefaultStreamException; import com.zillabyte.motherbrain.relational.UnexpectedFieldException; import com.zillabyte.motherbrain.top.MotherbrainException; public class LocalFlowOutputCollector implements OutputCollector { private int _localCounter = 0; private long _consumed = 0; private long _emitted = 0; private LocalOperationSlot _slot; public LocalFlowOutputCollector(LocalOperationSlot slot) { _slot = slot; } @Override public void emit(String streamName, MapTuple t) throws OperationException { this.emitAndGetTasks(streamName, t); } @Override public void emit(MapTuple t) throws OperationException { try { emit(this.getDefaultStream(), t); } catch (DefaultStreamException e) { throw new OperationException(_slot.operation(), e); } } @Override public void onAfterTuplesEmitted() throws OperationException { } @Override public void resetCounter() { _localCounter = 0; } @Override public long getCounter() { return _localCounter; } @Override public Operation getOperation() { return _slot.operation(); } @Override public void configure(Object context) { } @Override public String getDefaultStream() throws DefaultStreamException { return _slot.operation().defaultStream(); } @Override public void emitDirect(Integer taskId, String streamId, List<?> rawTuple) { _slot.controller().emitDirect(_slot.task(), taskId, streamId, rawTuple); } @Override public List<Integer> emitAndGetTasks(String streamName, MapTuple t) throws OperationException { t = handlePreEmit(streamName, t); List<Integer> tasks = _slot.controller().emitToStream(streamName, t, this._slot.task()); return tasks; } @Override public Integer getThisTask(Object context) { return _slot.task(); } @Override public void constructTaskOperationInfo(Object context) { } @Override public SetMultimap<String, Integer> getTaskOperationMap() { SetMultimap<String, Integer> ret = HashMultimap.create(); for(LocalOperationSlot s : _slot.controller().allSlots()) { ret.put(s.operation().namespaceName(), s.task()); } return ret; } @Override public Set<Integer> getAllTasks() { Set<Integer> set = Sets.newHashSet(); for(LocalOperationSlot slot : _slot.controller().allSlots()) { set.add(slot.task()); } return set; } @Override public Set<Integer> getAdjacentDownStreamTasks() { Set<Integer> set = Sets.newHashSet(); for(Pair<Connection, LocalOperationSlot> p : _slot.controller().getDownstreamSlots(_slot)) { set.add(p.getValue1().task()); } return set; } @Override public Set<Integer> getAdjacentUpStreamNonLoopTasks() { Set<Integer> set = Sets.newHashSet(); for(Pair<Connection, LocalOperationSlot> p : _slot.controller().getUpstreamSlots(_slot)) { if (p.getValue0().loopBack() == false) { set.add(p.getValue1().task()); } } return set; } @Override public ObserveIncomingTupleAction observePostQueuedCoordTuple(Object tuple, Integer sourceTask) throws OperationException { if (tuple instanceof MapTuple) { this.observeIncomingTuple((MapTuple)tuple); } return ObserveIncomingTupleAction.CONTINUE; } @Override public ObserveIncomingTupleAction observePreQueuedCoordTuple(Object tuple, Integer originTask) throws OperationException { return ObserveIncomingTupleAction.CONTINUE; } @Override public long getConsumeCount() { return _consumed; } @Override public long getEmitCount() { return _emitted; } @Override public long getAckCount() { return 0; } @Override public long getFailCount() { return 0; } @Override public long getCoordEmitCount() { return 0; } @Override public long getCoordConsumeCount() { return 0; } @Override public void handleChecks() { } @Override public boolean inPressureState() { return false; } public Operation operation() { return this._slot.operation(); } /**************************************************************************** * BELOW: just copied from StromOutpuCollector... TODO: DRY it up. ****************************************************************************/ private MapTuple _inputTuple; private MapTuple handlePreEmit(String streamName, MapTuple t) throws OperationException { // Pre-processors t = handlePostEmitDecorators(streamName, t); t = handleMerge(t); // Sanity ensureExpected(streamName, t); return t; } @Override public void observeIncomingTuple(MapTuple tuple) { _inputTuple = tuple; } protected void ensureExpected(String streamName, MapTuple t) throws OperationException { // _log.info("ensureExpected: streamName=" + streamName + " mapTuple=" + t); if (operation().outputStreams().contains(streamName) == false) { this.operation().getTopFlow().graph().debug(); throw new OperationException(this.operation(), new UnexpectedFieldException().setUserMessage("Emitted to an unexpected stream: '" + streamName + "'. Expected: " + this.operation().outputStreams())); } for (String field : this.operation().getExpectedFields(streamName)) { if (t.values().containsKey(field) == false) { throw new OperationException(this.operation(), new UnexpectedFieldException().setUserMessage("The tuple: '" + t.toString() + "' does not contain expected field: '" + field + "'")); } } } private MapTuple handlePostEmitDecorators(String stream, MapTuple t) throws OperationException { try { for(EmitDecorator dec : operation().emitDecorators(stream)) { t = dec.execute(t); } return t; } catch(MotherbrainException e) { throw new OperationException(operation(), e); } } private MapTuple handleMerge(MapTuple t) throws OperationException { // Sanity.. if (operation().getOperationShouldMerge() && _inputTuple == null) throw new OperationException(operation(), "the input tuple is null for a merge!"); // If we need to merge the input tuple into the output tuple... if (_inputTuple != null) { final Set<Entry<String, Object>> inputs = _inputTuple.values().entrySet(); // Put all of the input fields in the output tuple for (final Entry<String, Object> entry : inputs) { final String field = entry.getKey(); final Object value = entry.getValue(); if (operation().getOperationShouldMerge() && !t.containsValueKey(field)) { // 'pop' this field off the carry t.add(field, value); } if (field.contains(Operation.COMPONENT_CARRY_FIELD_PREFIX)) { // Carry the tuple to the next operation.... if (Operation.NONLINEAR_OPS.contains(operation().type())) throw new OperationException(operation(), "input field merge requested for component containing aggregation, merges are only allowed on components with only each and filter operations (this includes nested component operations)."); t.add(field, value); } } } // Done return t; } @Override public Object getCurrentBatch() { return "__local_batch"; } }