package com.zillabyte.motherbrain.flow.operations.multilang.operations; import java.util.concurrent.TimeoutException; import net.sf.json.JSONObject; import org.apache.log4j.Logger; import org.javatuples.Pair; import com.zillabyte.motherbrain.benchmarking.Benchmark; import com.zillabyte.motherbrain.container.ContainerWrapper; import com.zillabyte.motherbrain.coordination.CoordinationException; import com.zillabyte.motherbrain.flow.EndCyclePolicy; import com.zillabyte.motherbrain.flow.MapTuple; import com.zillabyte.motherbrain.flow.StateMachineException; import com.zillabyte.motherbrain.flow.collectors.OutputCollector; import com.zillabyte.motherbrain.flow.operations.OperationException; import com.zillabyte.motherbrain.flow.operations.OperationLogger; import com.zillabyte.motherbrain.flow.operations.Source; import com.zillabyte.motherbrain.flow.operations.multilang.MultiLangException; import com.zillabyte.motherbrain.flow.operations.multilang.MultiLangProcessTupleObserver; public class MultiLangRunSource extends Source implements MultiLangOperation { private static final long serialVersionUID = 2067987097123293403L; private static final Logger _log = Logger.getLogger(MultiLangRunSource.class); private MultilangHandler _handler; private EndCyclePolicy _endCyclePolicy = EndCyclePolicy.NULL_EMIT; public MultiLangRunSource(JSONObject nodeSettings, ContainerWrapper container) { super(MultilangHandler.getName(nodeSettings), MultilangHandler.getConfig(nodeSettings)); _handler = new MultilangHandler(this, nodeSettings, container); if (nodeSettings.containsKey("end_cycle_policy")) { if (nodeSettings.getString("end_cycle_policy").equalsIgnoreCase("explicit") ) { _endCyclePolicy = EndCyclePolicy.EXPLICIT; } else if (nodeSettings.getString("end_cycle_policy").equalsIgnoreCase("infinite") ) { _endCyclePolicy = EndCyclePolicy.INFINITE; } } } @Override public void prepare() throws MultiLangException, InterruptedException { _handler.prepare(); } @Override public final void cleanup() throws MultiLangException, InterruptedException { _handler.cleanup(); } public MultiLangRunSource setEndCyclePolicy(EndCyclePolicy v) { _endCyclePolicy = v; return this; } @Override public void onBeginCycle(OutputCollector output) throws InterruptedException, OperationException, CoordinationException, StateMachineException, TimeoutException { super.onBeginCycle(output); _handler.writeMessage("{\"command\": \"begin_cycle\"}"); _handler.waitForDoneMessageWithoutCollecting(); _handler.maybeThrowNextError(); } @Override public void onEndCycle(OutputCollector output) throws OperationException, InterruptedException, CoordinationException, StateMachineException, TimeoutException { super.onEndCycle(output); _handler.maybeThrowNextError(); } @Override protected boolean nextTuple(OutputCollector collector) throws InterruptedException, OperationException { Benchmark.markBegin("multilang.source.next_tuple"); try { // INIT int emitted = 0; boolean nullEmitted = false; Object output = null; // Sanity _handler.ensureAlive(); if (_handler.tupleObserver().queue().size() != 0) { if(_handler.tupleObserver().queue().peek() == MultiLangProcessTupleObserver.DONE_MARKER) { // If we have a done marker at the top, just remove it. _handler.takeNextTuple(); } else { // Otherwise something is weird... throw (OperationException) new OperationException(this, "Attempt to get nextTuple when there are still queued tuples: " + _handler.tupleObserver().queue()).setUserMessage("If you are seeing this message it likely indicates an error occurred in the multilang source process (scroll up to find it!)."); } } // Issue the command Benchmark.markBegin("multilang.source.next_tuple.command_next"); _handler.writeMessage("{\"command\": \"next\"}"); _handler.maybeThrowNextError(); Benchmark.markEnd("multilang.source.next_tuple.command_next"); // Iterate until signaled to end, or exception do { // Collect the next tuple/execption/signal Benchmark.markBegin("multilang.source.next_tuple.take_next_tuple"); output = _handler.takeNextTuple(); Benchmark.markEnd("multilang.source.next_tuple.take_next_tuple"); _handler.maybeThrowNextError(); if (nullEmitted) { // Once nullEmitted is set, just ignore everything until we see a DONE_MARKER continue; } else if (output == MultiLangProcessTupleObserver.END_CYCLE_MARKER) { // Done with this cycle nullEmitted = true; continue; } else if (output == MultiLangProcessTupleObserver.DONE_MARKER) { // We've recieved the 'done' message. This is handled in the loop condition continue; } else if (output instanceof Pair<?, ?>) { // Valid message Pair<String, MapTuple> pair = (Pair<String, MapTuple>) output; String stream = pair.getValue0(); MapTuple tuple = pair.getValue1(); // Depending on our policy, this may be the end of the cycle! if (_endCyclePolicy == EndCyclePolicy.NULL_EMIT) { // Check for any null values. If they exist, then end the cycle for(Object o : tuple.values().values()) { if (o == null || o.equals("null")) { // <-- weird JS thing // Set the nullEmitted flag and continue collecting tuples until we see a DONE _log.info("null value detected; ending cycle"); nullEmitted = true; } } } // Emit the tuple, continue looking for the DONE mark emitted++; Benchmark.markBegin("multilang.source.next_tuple.emit"); collector.emit(stream, tuple); Benchmark.markEnd("multilang.source.next_tuple.emit"); continue; } else if (output instanceof Throwable) { System.err.println("GOT A THROWABLE"); throw (OperationException) new OperationException(this, (Throwable) output).setUserMessage("The multilang process returned an error"); } else { throw (OperationException) new OperationException(this).setAllMessages("The multilang process returned uninterpretable data of type: "+output.getClass().getName()+"."); } } while(output != MultiLangProcessTupleObserver.DONE_MARKER); // Shall we end the cycle? if (emitted == 0) { if (_endCyclePolicy == EndCyclePolicy.NULL_EMIT) { _operationLogger.writeLog("No tuples were emitted from the source '" + this.instanceName() + "' and the cycle will now end. If you wish to change this behavior, set the 'end_cycle_policy' to 'explicit'.", OperationLogger.LogPriority.SYSTEM); _log.info("no tuples emitted; ending cycle"); return false; } } // Was a nullEmitted? If so, end the cycle if (nullEmitted) { return false; } // We've made it here, so continue the cycle return true; } finally { Benchmark.markEnd("multilang.source.next_tuple"); } } @Override public boolean isAlive() { return _handler.isAlive(); } @Override public ContainerWrapper getContainer() { return _handler.getContainer(); } @Override public MultilangHandler getMultilangHandler() { return _handler; } }