/* (c) 2015 Open Source Geospatial Foundation - all rights reserved * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.ogr.core; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.io.FileUtils; import org.opengis.referencing.crs.CoordinateReferenceSystem; /** * Base class for helpers used to invoke an external tool. * * @author Andrea Aime, GeoSolutions * @author Stefano Costa, GeoSolutions */ public abstract class AbstractToolWrapper implements ToolWrapper { private String executable; private Map<String, String> environment; public AbstractToolWrapper(String executable, Map<String, String> environment) { this.executable = executable; this.environment = new HashMap<String, String>(); if (environment != null) { this.environment.putAll(environment); } } @Override public String getExecutable() { return executable; } @Override public Map<String, String> getEnvironment() { return new HashMap<String, String>(environment); } @Override public boolean isInputFirst() { return true; } @Override public File convert(File inputData, File outputDirectory, String typeName, Format format, CoordinateReferenceSystem crs) throws IOException, InterruptedException { // build the command line List<String> cmd = new ArrayList<String>(); cmd.add(executable); String toolFormatParameter = getToolFormatParameter(); if (toolFormatParameter != null) { cmd.add(toolFormatParameter); cmd.add(format.getToolFormat()); } if (format.getOptions() != null) { for (String option : format.getOptions()) { cmd.add(option); } } StringBuilder sb = new StringBuilder(); String outFileName = null; int exitCode = -1; try { onBeforeRun(cmd, inputData, outputDirectory, typeName, format, crs); outFileName = setInputOutput(cmd, inputData, outputDirectory, typeName, format); exitCode = run(cmd, sb); } finally { onAfterRun(exitCode); } if (exitCode != 0) throw new IOException(executable + " did not terminate successfully, exit code " + exitCode + ". Was trying to run: " + cmd + "\nResulted in:\n" + sb); // output may be a directory, handle that case gracefully File output = new File(outputDirectory, outFileName); if(output.isDirectory()) { output = new File(output, outFileName); } return output; } /** * Sets up input and output parameters. * * <p> * Uses {@link #isInputFirst()} internally to determine whether input or output should come first in the list of arguments. * </p> * * <p> * May be overridden by subclasses, e.g. to support commands that modify the input file inline and thus need no output parameter. * </p> * * @param cmd the command to run and its arguments * @param inputData the input file * @param outputDirectory the output directory * @param typeName the type name * @param format the format descriptor * @return the name of the (main) output file */ protected String setInputOutput(List<String> cmd, File inputData, File outputDirectory, String typeName, Format format) { String outFileName = typeName; if (format.getFileExtension() != null) outFileName += format.getFileExtension(); if (isInputFirst()) { cmd.add(inputData.getAbsolutePath()); cmd.add(new File(outputDirectory, outFileName).getAbsolutePath()); } else { cmd.add(new File(outputDirectory, outFileName).getAbsolutePath()); cmd.add(inputData.getAbsolutePath()); } return outFileName; } /** * Utility method to dump a {@link CoordinateReferenceSystem} to a temporary file on disk. * * @param parentDir * @param crs * @return the temp file containing the CRS definition in WKT format * @throws IOException */ protected static File dumpCrs(File parentDir, CoordinateReferenceSystem crs) throws IOException { File crsFile = null; if (crs != null) { // we don't use an EPSG code since there is no guarantee we'll be able to reverse // engineer one. Using WKT also ensures the EPSG params such as the TOWGS84 ones are // not lost in the conversion // We also write to a file because some operating systems cannot take arguments with // quotes and spaces inside (and/or ProcessBuilder is not good enough to escape them) crsFile = File.createTempFile("srs", "wkt", parentDir); String s = crs.toWKT(); s = s.replaceAll("\n\r", "").replaceAll(" ", ""); FileUtils.writeStringToFile(crsFile, s); } return crsFile; } /** * Invoked by <code>convert()</code> before the command is actually run, but after the options specified in the format configuration have been * added to the arguments list. * * <p> * Default implementation does nothing at all. May be implemented by subclasses to append additional arguments to <code>cmd</code>. * </p> * * @param cmd the command to run and its arguments * @param inputData * @param outputDirectory * @param typeName * @param format * @param crs * @throws IOException */ protected void onBeforeRun(List<String> cmd, File inputData, File outputDirectory, String typeName, Format format, CoordinateReferenceSystem crs) throws IOException { // default implementation does nothing } /** * Invoked by <code>convert()</code> after the command is run. Invocation is done inside a <code>try ... finally</code> block, so it happens even * if an exception is thrown during command execution. * * <p> * Default implementation does nothing at all. May be implemented by subclasses to do some clean-up work. * </p> * * @param exitCode the exit code of the invoked command. Usually, 0 indicates normal termination * @throws IOException */ protected void onAfterRun(int exitCode) throws IOException { // default implementation does nothing } /** * Runs the specified command appending the output to the string builder and * returning the exit code. * * @param cmd the command to run and its arguments * @param sb command output is appended here * @return the exit code of the invoked command. Usually, 0 indicates normal termination * @throws IOException * @throws InterruptedException */ protected int run(List<String> cmd, StringBuilder sb) throws IOException, InterruptedException { // run the process and grab the output for error reporting purposes ProcessBuilder builder = new ProcessBuilder(cmd); if(environment != null) builder.environment().putAll(environment); builder.redirectErrorStream(true); Process p = builder.start(); BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream())); String line = null; while ((line = reader.readLine()) != null) { if (sb != null) { sb.append("\n"); sb.append(line); } } return p.waitFor(); } }