package edu.mayo.bior.pipeline;
import java.io.InputStream;
import java.util.Arrays;
import java.util.List;
import java.util.NoSuchElementException;
import org.apache.log4j.Logger;
import com.tinkerpop.pipes.Pipe;
import com.tinkerpop.pipes.transform.IdentityPipe;
import com.tinkerpop.pipes.util.Pipeline;
import edu.mayo.cli.InvalidDataException;
import edu.mayo.pipes.InputStreamPipe;
import edu.mayo.pipes.LineCounterPipe;
import edu.mayo.pipes.PrintPipe;
import edu.mayo.pipes.exceptions.InvalidPipeInputException;
import edu.mayo.pipes.history.History;
import edu.mayo.pipes.history.HistoryInPipe;
import edu.mayo.pipes.history.HistoryOutPipe;
/**
* This pipeline can be used to expose a Pipe to behave like a UNIX command
* that operates with STDIN and STDOUT text streams.
*/
public class UnixStreamPipeline
{
public class Status {
public long numLinesIn = 0;
public long numLinesOut = 0;
// track how many data rows encounter an error
public long numLinesBadData = 0;
// Did the command run to completion (all rows processed, even if some rows failed)
public boolean isSuccessful = false;
public String toString() {
String out = "numLinesIn=" + numLinesIn + "\n"
+ "numLinesOut=" + numLinesOut + "\n"
+ "numLinesBadData=" + numLinesBadData + "\n"
+ "isSuccessful=" + isSuccessful + "\n";
return out;
}
}
private Status mStatus = new Status();
private static Logger sLogger = Logger.getLogger(UnixStreamPipeline.class);
public Status getStatus() {
return mStatus;
}
/**
* Executes the given Pipe like a stream-compatible UNIX command.
*
* This method will:
* <ol>
* <li>deserialize a HISTORY from the incoming text stream from STDIN</li>
* <li>invoke the given Pipe to do some business logic on the HISTORY</li>
* <li>serialize a HISTORY into an outgoing text stream to STDOUT</li>
* </ol>
*
* STDIN --> HISTORY --> LOGIC PIPE --> HISTORY* --> STDOUT
* </br>
* </br>
*
* NOTE: It is required that the given Pipe has a HISTORY as input and output.
*
* @param logic
* A Pipe that takes a HISTORY as input and output.
*
* @throws InvalidDataException
*/
public void execute(Pipe<History, History> logic) throws InvalidDataException
{
HistoryInPipe historyIn = new HistoryInPipe();
HistoryOutPipe historyOut = new HistoryOutPipe();
execute(historyIn, logic, historyOut);
}
/**
* Executes the given Pipe like a stream-compatible UNIX command.
*
* STDIN --> PRE LOGIC PIPE --> LOGIC PIPE --> POST LOGIC PIPE --> STDOUT
* </br>
* </br>
*
* NOTE: It is required that the given PRE LOGIC PIPE produce an output
* compatible with what the LOGIC PIPE expects as input. Likewise, the
* LOGIC pipe is required to produce an output compatible with what the
* POST LOGIC PIPE expects as input. If a PRE or POST pipe is not needed,
* use an {@link IdentityPipe} as a placeholder.
*
* @param preLogic
* A pipe that runs directly before the logic pipe. This pipe must take
* a {@link String} as input.
* @param logic
* A Pipe that performs the business logic.
* @param postLogic
* A pipe that runs directly after the logic pipe. This pipe must produce
* a {@link String} as output.
*
* @throws InvalidDataException
*/
public void execute(Pipe preLogic, Pipe logic, Pipe postLogic) throws InvalidDataException
{
// pipes
InputStreamPipe in = new InputStreamPipe();
PrintPipe print = new PrintPipe();
// Counter pipes
LineCounterPipe<History> counterPipeIn = new LineCounterPipe<History>();
LineCounterPipe<History> counterPipeOut = new LineCounterPipe<History>();
// pipeline definition
Pipe<InputStream, List<String>> pipeline = new Pipeline<InputStream, List<String>>
(
in, // each STDIN line --> String
preLogic, // String --> history
counterPipeIn,
logic, // history --> modified history*
counterPipeOut,
postLogic, // history* --> String
print // String --> STDOUT
);
// prime pipeline with STDIN stream
pipeline.setStarts(Arrays.asList(System.in));
// run pipeline
boolean hasNext = true;
//int line = 1;
while (hasNext)
{
try
{
//System.err.println("XXXXX: Line " + line++);
pipeline.next();
}
catch (NoSuchElementException e)
{
// reached the end
hasNext = false;
}
catch (InvalidPipeInputException e)
{
mStatus.numLinesBadData++;
sLogger.error(e.getMessage());
}
}
mStatus.numLinesIn = counterPipeIn.getLineCount();
mStatus.numLinesOut = counterPipeOut.getLineCount();
mStatus.isSuccessful = true;
if (mStatus.numLinesBadData > 0)
{
String msg =
String.format(
"WARNING: Found %s data error(s). Not all input data rows could be successfully processed.",
String.valueOf(mStatus.numLinesBadData)
);
// Do NOT throw the exception if we've only had a few lines not processed as this will cause an exit code of 1
// Instead, we'll dump to system.error and use exit 0 if we're able to run to completion
//throw new InvalidDataException(msg);
System.err.println(msg);
}
}
}