package com.zillabyte.motherbrain.flow.components; import java.util.ArrayList; import java.util.List; import net.sf.json.JSONArray; import net.sf.json.JSONObject; import com.google.common.collect.Lists; import com.zillabyte.motherbrain.flow.Fields; import com.zillabyte.motherbrain.flow.FlowCompilationException; import com.zillabyte.motherbrain.flow.MapTuple; import com.zillabyte.motherbrain.flow.collectors.OutputCollector; import com.zillabyte.motherbrain.flow.config.FlowConfig; import com.zillabyte.motherbrain.flow.graph.Connection; import com.zillabyte.motherbrain.flow.operations.Function; import com.zillabyte.motherbrain.flow.operations.Operation; import com.zillabyte.motherbrain.flow.operations.OperationException; import com.zillabyte.motherbrain.flow.operations.multilang.MultiLangException; import com.zillabyte.motherbrain.relational.ColumnDef; public class ComponentOutput extends Function { private static final long serialVersionUID = -2209824682828708006L; private final List<ColumnDef> _componentFields; public ComponentOutput(final String name, ColumnDef... fields) { super(name); _componentFields = Lists.newArrayList(fields); } public ComponentOutput(final String name, List<ColumnDef> fields) { super(name); _componentFields = fields; } public ComponentOutput(JSONObject node, FlowConfig _flowConfig) throws FlowCompilationException { super(node.getString("name")); // Get the fields JSONArray sinkFields = node.getJSONArray("columns"); // Get the column defs ArrayList<ColumnDef> colDefs = new ArrayList<>(); int i=0; for(Object o : sinkFields) { JSONObject colDef = (JSONObject) o; final String colName = (String) colDef.keys().next(); final String colType = colDef.getString(colName); assert (colName != null); colDefs.add(new ColumnDef(i, ColumnDef.convertStringToDataType(colType), colName)); i++; } // Create the component _componentFields = colDefs; } public List<ColumnDef> componentFields() { return this._componentFields; } @Override public String type() { return "producer"; } public String produceStream() { return this.namespaceName() + "-stream"; } @Override protected void process(MapTuple t, OutputCollector c) throws OperationException, InterruptedException { MapTuple outputTuple = new MapTuple(t); boolean carryFieldSeen = false; if(parentComponentShouldMerge()) { String carryFieldName = getParentComponentCarryFieldName(); final JSONObject inputTuple = (JSONObject) t.get(carryFieldName); if(inputTuple != null) { for(Object o : inputTuple.keySet()) { final String ifield = (String) o; if(ifield != null && !outputTuple.containsValueKey(ifield)) { final Object i = inputTuple.get(o); if (i != null) { outputTuple.add(ifield, i); } } } carryFieldSeen = true; } if(!carryFieldSeen) throw (OperationException) new OperationException(this).setAllMessages("Attempted to merge fields in \"" + instanceName() + "\", but no input tuple given!"); outputTuple.remove(getParentComponentCarryFieldName()); } c.emit(outputTuple); } @Override public void onSetExpectedFields() throws OperationException { for(final Connection c : this.prevConnections()) { // Because we are a simple pass-through, any expected fields of us will be expected of the // previous operation as well. for(ColumnDef cd : _componentFields) { for(String s : cd.getAliases()) { Fields f = new Fields(s); c.source().addExpectedFields(c.streamName(), f); } } if(parentComponentShouldMerge()) { // If this component output needs to merge the input, then its previous operation will // emit all of the fields declared for the output as well as a carry field. c.source().addExpectedFields(c.streamName(), new Fields(getParentComponentCarryFieldName())); } } super.onSetExpectedFields(); } @Override public final void prepare() throws MultiLangException, InterruptedException { /* Noop */ } public boolean parentComponentShouldMerge() { return this.getContainerFlow().getFlowConfig().getShouldMerge(); } public String getParentComponentCarryFieldName() throws OperationException { return Operation.COMPONENT_CARRY_FIELD_PREFIX + this.getTopFlow().getId(); } @Override public String prefixifyStreamName(String stream) { // We don't prefix here because we want to translate from the embedded stream name to the // outer context's name. // TODO: there's a bug here with 2+ nested components. We need a new function that can // return the parent's prefix name. return stream; } }