net.sf.okapi.steps.externalcommand.ExternalCommandStep Maven / Gradle / Ivy
/*===========================================================================
Copyright (C) 2010 by the Okapi Framework contributors
-----------------------------------------------------------------------------
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
===========================================================================*/
package net.sf.okapi.steps.externalcommand;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import net.sf.okapi.common.Event;
import net.sf.okapi.common.IParameters;
import net.sf.okapi.common.UsingParameters;
import net.sf.okapi.common.exceptions.OkapiException;
import net.sf.okapi.common.exceptions.OkapiIOException;
import net.sf.okapi.common.pipeline.BasePipelineStep;
import net.sf.okapi.common.pipeline.annotations.StepParameterMapping;
import net.sf.okapi.common.pipeline.annotations.StepParameterType;
import net.sf.okapi.common.resource.RawDocument;
import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.ExecuteException;
import org.apache.commons.exec.ExecuteWatchdog;
import org.apache.commons.exec.Executor;
import org.apache.commons.exec.PumpStreamHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Run a Command line tool on {@link RawDocument} {@link Event}s. The step returns a RawDocument Event generated by the
* external command. ${inputPath}
and ${outputPath}
variables must be defined in the command
* line string. For example:
* "sort ${inputPath} /O ${outputPath}"
* is a valid windows command which sorts lines on a file.
*
* @author HARGRAVEJE
*/
@UsingParameters(Parameters.class)
public class ExternalCommandStep extends BasePipelineStep {
private final Logger LOGGER = LoggerFactory.getLogger(getClass());
private static final String INPUT_FILE_VAR = "inputPath";
private static final String OUTPUT_FILE_VAR = "outputPath";
private boolean done = false;
private Parameters parameters;
private Executor executor;
private ExecuteWatchdog watchdog;
private URI outputURI;
public ExternalCommandStep () {
parameters = new Parameters();
}
@StepParameterMapping(parameterType = StepParameterType.OUTPUT_URI)
public void setOutputURI(URI outputURI) {
this.outputURI = outputURI;
}
@Override
public Parameters getParameters() {
return parameters;
}
@Override
public void setParameters(IParameters params) {
this.parameters = (Parameters) params;
}
@Override
public boolean isDone() {
return done;
}
@Override
public String getDescription() {
return "Execute an external command line.";
}
@Override
public String getName() {
return "External Command";
}
@Override
protected Event handleStartBatch(Event event) {
done = false;
executor = new DefaultExecutor();
// set process timeout if value is greater than zero
if (parameters.getTimeout() > 0) {
// convert from seconds to milliseconds
watchdog = new ExecuteWatchdog(parameters.getTimeout() * 1000L);
executor.setWatchdog(watchdog);
}
return event;
}
@Override
protected Event handleRawDocument(Event event) {
int exitValue;
Map subtitutions = new HashMap<>();
RawDocument rawDoc = event.getRawDocument();
// Set input path variable
String inputPath = (new File(rawDoc.getInputURI()).getPath());
subtitutions.put(INPUT_FILE_VAR, inputPath);
// Set output path variable
String outputPath = inputPath + ".out"; // Default
if ( isLastOutputStep() && outputURI != null && !outputURI.getPath().isEmpty() ) {
outputPath = (new File(outputURI).getPath());
}
subtitutions.put(OUTPUT_FILE_VAR, outputPath);
// Set source-related variables
subtitutions.put("srcLangName", rawDoc.getSourceLocale().toJavaLocale().getDisplayLanguage(Locale.ENGLISH));
subtitutions.put("srcLang", rawDoc.getSourceLocale().getLanguage());
subtitutions.put("srcBCP47", rawDoc.getSourceLocale().toBCP47());
// Set target-related variables
subtitutions.put("trgLangName", rawDoc.getTargetLocale().toJavaLocale().getDisplayLanguage(Locale.ENGLISH));
subtitutions.put("trgLang", rawDoc.getTargetLocale().getLanguage());
subtitutions.put("trgBCP47", rawDoc.getTargetLocale().toBCP47());
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayOutputStream err = new ByteArrayOutputStream();
PumpStreamHandler psh = new PumpStreamHandler(out, err);
String[] parts = splitCommand(parameters.getCommand());
CommandLine cl = new CommandLine(parts[0]);
cl.setSubstitutionMap(subtitutions);
for (int i = 1; i < parts.length; i++) {
cl.addArgument(parts[i], false);
}
try {
psh.start();
LOGGER.info("External Command: {}", cl.toString());
exitValue = executor.execute(cl);
}
catch (ExecuteException e) {
throw new OkapiException(e);
}
catch (IOException e) {
throw new OkapiIOException(e);
}
if (watchdog != null && watchdog.killedProcess()) {
throw new OkapiException("Command line process timed out: " + out.toString());
}
if (executor.isFailure(exitValue)) {
throw new OkapiException("Command line process failed: " + err.toString());
}
try {
psh.stop();
out.close();
} catch (IOException e) {
throw new OkapiIOException("Error closing process output streamm.", e);
}
try {
err.close();
} catch (IOException e) {
throw new OkapiIOException("Error closing process error streamm", e);
}
// Create new event resource pointing to the output
RawDocument outRawDoc = null;
outRawDoc = new RawDocument((new File(outputPath)).toURI(), rawDoc.getEncoding(),
rawDoc.getSourceLocale(), rawDoc.getTargetLocale());
event.setResource(outRawDoc);
done = true;
return event;
}
@Override
protected Event handleEndBatch(Event event) {
return event;
}
public static String[] splitCommand(String cmd) {
cmd = cmd.trim();
if (cmd.length() == 0) return new String[] { "" };
StringBuilder arg = new StringBuilder();
List result = new ArrayList<>();
final char noQuote = '\0';
char currentQuote = noQuote;
for (int i = 0; i < cmd.length(); i++) {
char c = cmd.charAt(i);
if (c == currentQuote) {
currentQuote = noQuote;
} else if (c == '"' && currentQuote == noQuote) {
currentQuote = '"';
} else if (c == '\'' && currentQuote == noQuote) {
currentQuote = '\'';
} else if (c == '\\' && i + 1 < cmd.length()) {
char next = cmd.charAt(i + 1);
if ((currentQuote == noQuote && Character.isWhitespace(next))
|| (currentQuote == '"' && next == '"')) {
arg.append(next);
i++;
} else {
arg.append(c);
}
} else {
if (Character.isWhitespace(c) && currentQuote == noQuote) {
if (arg.length() > 0) {
result.add(arg.toString());
arg = new StringBuilder();
} else {
// Discard
}
} else {
arg.append(c);
}
}
}
// Catch last arg
if (arg.length() > 0) {
result.add(arg.toString());
}
return result.toArray(new String[0]);
}
}