package org.nextprot.api.blast.service;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import org.nextprot.api.commons.utils.SystemCommandExecutor;
import org.nextprot.api.commons.exception.NextProtException;
import org.nextprot.api.commons.utils.ExceptionWithReason;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Serializable;
import java.nio.file.Files;
import java.util.List;
import java.util.Objects;
import java.util.logging.Logger;
/**
* A base object for blast suite programs
*/
public abstract class BlastProgram<I, O, C extends BlastProgram.Params> {
private static final Logger LOGGER = Logger.getLogger(BlastProgram.class.getName());
private final String name;
private final C config;
protected BlastProgram(String name, C config) {
Objects.requireNonNull(config, "Internal error: missing blast configuration");
this.name = name;
this.config = config;
}
public O run(I input) throws ExceptionWithReason {
try {
// pre process
File fastaFile = constructFastaFile(input);
List<String> commandLine = buildCommandLine(config, fastaFile);
preConfig(input, config);
O out = process(commandLine);
// post process
destroyFastaFile(fastaFile);
postConfig(config);
return out;
} catch (IOException e) {
throw new NextProtException("Internal error: cannot run process "+name, e);
}
}
private File constructFastaFile(I input) throws IOException {
File tmpQueryFastaFile = File.createTempFile(name, ".fasta");
PrintWriter pw = new PrintWriter(tmpQueryFastaFile);
writeFastaInput(pw, input);
pw.close();
LOGGER.info("create temporary file "+tmpQueryFastaFile.getName());
return tmpQueryFastaFile;
}
private O process(List<String> commandLine) throws ExceptionWithReason {
SystemCommandExecutor commandExecutor = new SystemCommandExecutor(commandLine);
try {
commandExecutor.executeCommand();
String stderr = commandExecutor.getLastExecutionStandardError();
if (!stderr.isEmpty()) {
ExceptionWithReason ewr = new ExceptionWithReason();
ewr.getReason().addCause(name+ " exception", stderr.replace("\n", " "));
ewr.getReason().setMessage("Error while executing "+name);
throw ewr;
}
return buildOutputFromStdout(commandExecutor.getLastExecutionStandardOutput());
} catch (InterruptedException | IOException e) {
throw new NextProtException("Internal error: cannot process "+name+" command line "+ commandLine, e);
}
}
private void destroyFastaFile(File fastaFile) throws IOException {
if (!config.isDebugMode()) {
LOGGER.info("delete temporary file "+fastaFile.getName());
Files.deleteIfExists(fastaFile.toPath());
}
}
protected void preConfig(I input, C config) { }
protected void postConfig(C config) {
config.setDebugMode(null);
config.unsetPathes();
}
/**
* Build command line to execute by blast program
* @param config input configuration
* @param fastaFile the input fasta file
* @return command line list
*/
protected abstract List<String> buildCommandLine(C config, File fastaFile);
/**
* Build output object from command stdout
* @param stdout the command output
* @return an object containing output
*/
protected abstract O buildOutputFromStdout(String stdout) throws IOException;
/**
* Write fasta entries from input object
*
* @param pw the writer object
* @param input the input to extract entries from
*/
protected abstract void writeFastaInput(PrintWriter pw, I input);
/**
* Configuration object for program execution
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public static class Params implements Serializable {
private String binPath;
private String nextprotBlastDbPath;
private Boolean isDebugMode = false;
public Params(String binPath, String nextprotBlastDbPath) {
if (binPath == null)
throw new NextProtException("Internal error: bin path is missing");
if (nextprotBlastDbPath == null)
throw new NextProtException("Internal error: nextprot blast db path is missing");
this.binPath = binPath;
this.nextprotBlastDbPath = nextprotBlastDbPath;
}
@JsonIgnore
public String getNextprotBlastDbPath() {
return nextprotBlastDbPath;
}
@JsonIgnore
public String getBinPath() {
return binPath;
}
@JsonIgnore
public Boolean isDebugMode() {
return isDebugMode;
}
public void setDebugMode(Boolean debugMode) {
isDebugMode = debugMode;
}
public void unsetPathes() {
binPath = null;
nextprotBlastDbPath = null;
}
}
/**
* Write a fasta entry
* @param pw
* @param header
* @param sequence
*/
static void writeFastaEntry(PrintWriter pw, String header, String sequence) {
pw.write(">");
pw.write(header);
pw.write("\n");
pw.write(sequence);
pw.write("\n");
}
}