R2Run.java [src/java/m/rusle2] Revision:   Date:
/*
 * $Id$
 *
 * This file is part of the Cloud Services Integration Platform (CSIP),
 * a Model-as-a-Service framework, API, and application suite.
 *
 * 2012-2017, OMSLab, Colorado State University.
 *
 * OMSLab licenses this file to you under the MIT license.
 * See the LICENSE file in the project root for more information.
 */
package m.rusle2;

import csip.Config;
import csip.api.server.Executable;
import csip.api.server.ServiceException;
import csip.SessionLogger;
import static csip.Utils.removeFirstLastChar;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.logging.Level;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringEscapeUtils;
import util.XMLUtils;

/**
 *
 * @author od
 */
class R2Run {

  SessionLogger LOG;
  String stdout;


  void setLogger(SessionLogger l) {
    LOG = l;
  }


  /**
   * Execute the rusle2 pyrome python script
   *
   * We assume that Python3.4 is installed under
   * /home/ubuntu/.wine/drive_c/Python34 and that wine's working dir is
   * /home/ubuntu/.wine The ownership of wine's working directory must
   * correspond with the user running tomcat
   *
   * @param r2_rsh
   * @return
   * @throws IOException
   */
  int executePyrome(File r2_rsh, Executable python, String suid) throws IOException, ServiceException {
    python.setArguments("rusle2csip.py", suid);
    LOG.info("Executing pyrome rusle2");
    int ret = python.exec();
    if (ret != 0) {
      String stderr = FileUtils.readFileToString(python.stderr(), "UTF-8");
      throw new ServiceException("RUSLE 2 PYTHON error: error executing pyrome:" + stderr);
    }

//        String runrusle2 = "winetricks vd=off ; wine /home/ubuntu/.wine/drive_c/Python34/python.exe rusle2csip.py\n";
    stdout += "\n" + FileUtils.readFileToString(python.stdout(), "UTF-8");
    if (LOG.isLoggable(Level.INFO)) {
      LOG.info("stdout: " + stdout);
      LOG.info("exit val: " + ret);
    }
    return ret;
  }

  // Must find the hydraulic element flow path element of a hydraulic element system
  // download this file, and modify the header, so it can be fed to python

  void prepareHydraulicElementFlowPathJ(String hydElemPtr, String r2db, File workingDir) throws IOException {
    final String hydelemFile = "hydelem_file.tmp";
    String hydelemflowpathFile = "";
    BufferedReader br = null;
    try {
      // first open hydElemPtr file
      getFile(hydElemPtr, workingDir.getParentFile(), hydelemFile);
      // look for HYD_SYSTEM_FLOW_PATH_TYPE
      LOG.info("reading --- " + workingDir.getParent() + "/" + hydelemFile);
      File hydElemFile = new File(workingDir.getParent(), hydelemFile);
      br = new BufferedReader(new FileReader(hydElemFile));
      String inLine;
      while ((inLine = br.readLine()) != null) {
        if (inLine.contains("HYD_SYSTEM_FLOW_PATH_TYPE")) {
          // get the text between the single two quotes- this will be the flowpathfile we need to get
          // <ObR><Name>HYD_SYSTEM_FLOW_PATH_TYPE</Name><Data>'0.3% grade channel'</Data><Type>HYD_ELEMENT_FLOW_PATH</Type></ObR></Obj>
          hydelemflowpathFile = inLine.split("'")[1];
          LOG.info("Read hydelemflowpathFile as =" + hydelemflowpathFile);
        }
      }
      String hydElemFlowPathPtr = r2db + "/hydraulic-element-flow-paths/" + hydelemflowpathFile + ".xml";
      LOG.info("HydelemflowpathPTR is =" + hydElemFlowPathPtr);
      // call ingprepareFileJ to finish the work...
      // next open the flowpathfile, and perform the standard formatting
      // of adding the <Obj> tag and the <Filename> tag...
      prepareFileJ(hydElemFlowPathPtr, new File(workingDir.getParent(), "hydelemflowpath_file.xml"), "", "hydraulic-element-flow-paths", "hydelemflowpath", false);
    } catch (Exception ioe) {
      LOG.info("IO Exception while preparing file=" + ioe.toString());
    } finally {
      if (br != null) {
        br.close();
      }
    }
  }


  int determineNumberOfFlowPaths(String hydElemPtr) {
    hydElemPtr = hydElemPtr.substring(hydElemPtr.lastIndexOf("\\") + 1);

    // This rediumentary algorithm for determining the number of flow paths for
    // a hydaulic element system is based on conversation with Jack Carlson in late 03/2016
    LOG.info("********\n\n\n\nHYD ELEM PTR=" + hydElemPtr);
    //if (hydElemPtr.substring(hydElemPtr.lastIndexOf("\")+1).startsWith("1"))
    if (hydElemPtr.startsWith("1")) {
      if (hydElemPtr.contains("middle")) {
        return 2;
      } else {
        return 1;
      }
    }

    // presently there are only 2 kinds of hydraulic element systems with 2 elements.
    // ones with only "middle" in the name which should have 3 flow paths (all evenly distributed along the slope),
    // and ones with "middle" and "bottom" in the name which should have 2 flow paths (one in middle, one at bottom).
    //
    // presently there is one hydraulic element with "along" instead of "middle" in the name
    // "2 Water and Sediment Control Basins along RUSLE slope.xml"  This should be treated the same as middle.
    //
    if (hydElemPtr.startsWith("2")) {
      if (((hydElemPtr.contains("middle")) || (hydElemPtr.contains("along"))) && (!hydElemPtr.contains("bottom"))) {
        return 3;
      } else {
        // These should have middle and bottom
        return 2;
      }
    }

    // presently there are only 2 kinds of hydraulic element systems with 3 elements.
    // ones with only "middle" in the name which should have 4 flow paths (all evenly distributed along the slope),
    // and ones with "middle" and "bottom" in the name which should have 3 flow paths (two in middle, one at bottom).
    if (hydElemPtr.startsWith("3")) {
      if ((hydElemPtr.contains("middle")) && (!hydElemPtr.contains("bottom"))) {
        return 4;
      } else {
        // These should have middle and bottom
        return 3;
      }
    }
    // Should not get here.
    return 0;
  }


  double[] determineFlowPathDistribution(String hydElemPtr) {
    hydElemPtr = hydElemPtr.substring(hydElemPtr.lastIndexOf("\\") + 1);
    // This rediumentary algorithm for determining the hyd elem flow path position relative to slope length for
    // a hydaulic element system is based on conversation with Jack Carlson in late 03/2016
    if (hydElemPtr.substring(hydElemPtr.lastIndexOf("\\") + 1).startsWith("1")) {
      if (hydElemPtr.contains("middle")) {
        return new double[]{.5, 1};
      } else {
        return new double[]{1};
      }
    }

    // presently there are only 2 kinds of hydraulic element systems with 2 elements.
    // ones with only "middle" in the name which should have 3 flow paths (all evenly distributed along the slope),
    // and ones with "middle" and "bottom" in the name which should have 2 flow paths (one in middle, one at bottom).
    //
    // presently there is one hydraulic element with "along" instead of "middle" in the name
    // "2 Water and Sediment Control Basins along RUSLE slope.xml"  This should be treated the same as middle.
    //
    if (hydElemPtr.startsWith("2")) {
      if (((hydElemPtr.contains("middle")) || (hydElemPtr.contains("along"))) && (!hydElemPtr.contains("bottom"))) {
        return new double[]{.333, .666, 1};
      } else {
        // These should have middle and bottom
        return new double[]{.5, 1};
      }
    }

    // presently there are only 2 kinds of hydraulic element systems with 3 elements.
    // ones with only "middle" in the name which should have 4 flow paths (all evenly distributed along the slope),
    // and ones with "middle" and "bottom" in the name which should have 3 flow paths (two in middle, one at bottom).
    if (hydElemPtr.startsWith("3")) {
      if ((hydElemPtr.contains("middle")) && (!hydElemPtr.contains("bottom"))) {
        return new double[]{.25, .5, .75, 1};
      } else {
        // These should have middle and bottom
        return new double[]{.333, .666, 1};
      }
    }
    // Should not get here...
    return new double[]{0};
  }

  // Generic File Preparation routine
  // Downloads, and prepares a file for use in Rusle2 Pyrome
  //
  // fileType: the type of file to process
  // Valid fileType(s) include: "contour",
  //
  // fileDir: the dir of the filename specified in the <Filename> tag in the R2 XML file
  // Valid fileDir(s) include: "countour-systems"
  //
  // idx: An index value, if there are multiple items of the same type for slope segments

  void prepareFileJ(String httpPtr, File outputFile, String fileType, String fileDir, String basefilename, boolean prepareSoilForPyrome) throws IOException {
    BufferedReader br = null;
    Writer bw = null;

    String fn = (((basefilename != null) & (basefilename.length() > 0)) ? "\\" + basefilename + "1" : "\\aaa");
    try {
      // Generate shell script to prepare --fileType-- file for pyrome
      LOG.info("******** the " + fileType + " file name=" + outputFile.getName());
      String tmpFilename = outputFile.getName().substring(0, outputFile.getName().length() - 4) + ".tmp";
      LOG.info("******** the " + fileType + " tmp file name=" + tmpFilename);

      getFile(httpPtr, outputFile.getParentFile(), tmpFilename);
      bw = new PrintWriter(outputFile);

      File origFile = new File(outputFile.getParent(), tmpFilename);
      br = new BufferedReader(new FileReader(origFile));

      if ((fileType.contentEquals("CLIMATE")) || (fileType.contentEquals("SOIL")) || (prepareSoilForPyrome)) {
        bw.write("<?xml version=\"1.0\"?>\n");
      }
      if (!fileType.contentEquals("SOIL")) {
        bw.write("<Obj>\n");
        if (fileType.length() > 1) {
          bw.write("<Type>" + fileType + "</Type>\n");
        }
        // if an incrementing index is needed, replace "0" with counter variable
        bw.write("<Filename>" + fileDir + fn + "</Filename>\n");
      }
      int i = 0;
      String inputLine;
      while ((inputLine = br.readLine()) != null) {
        // skip the first line of the file, except for soil files which only have 1 line
        if ((i > 0) || (fileType.contentEquals("SOIL") && (i == 0) && (inputLine.startsWith("<Obj>")))) {
          bw.write(inputLine + "\n");
        }
        i++;
      }
    } catch (IOException ioe) {
      LOG.info("IO Exception while preparing flie=" + outputFile.getName() + "---" + ioe.toString());
    } finally {
      if (br != null) {
        br.close();
      }
      if (bw != null) {
        bw.close();
      }
    }
  }

  // TO DO
  // Consider moving to a helper class since this is a generally applicable function

  private boolean getFile(String url, File destDir, String filename) throws FileNotFoundException, IOException {
    String u = XMLUtils.escapeURL(url);
    if (exists(u)) {
      File f = new File(destDir, filename);
      FileUtils.copyURLToFile(new URL(u), f);
      return f.exists();
    }
    return false;
  }


  public static boolean exists(String URLName) throws IOException {
    HttpURLConnection.setFollowRedirects(false);
    // note : you may also need
    //        HttpURLConnection.setInstanceFollowRedirects(false)
    HttpURLConnection con = (HttpURLConnection) new URL(URLName).openConnection();
    con.setRequestMethod("GET");
    return (con.getResponseCode() == HttpURLConnection.HTTP_OK);
  }


  /**
   * Gets results returned from RomeShell
   *
   * @param key
   * @return
   */
  protected String getResult(String key) {
    String sPattern = "RomeFileGetAttrValue " + key;

    // Check if attr is available via the GetAttrValue cmd and return if so
    if (stdout.contains(sPattern)) {
      int start = stdout.indexOf(sPattern) + sPattern.length() + 2;
      int end = stdout.indexOf('\r', start);
      return stdout.substring(start, end).trim();
    }

    // Check if attr is available via the TestAttrValue cmd and return if so
    sPattern = "RomeFileTestAttrValue " + key;
    if (stdout.contains(sPattern)) {
      int start = stdout.indexOf(sPattern) + sPattern.length() + 2;
      int end = stdout.indexOf('\r', start);
      return stdout.substring(start, end).trim();
    }

    sPattern = ":" + removeFirstLastChar(key) + "\"";
    if (stdout.contains(sPattern)) {
      int start = stdout.indexOf(sPattern) + sPattern.length() + 2;
      int end = stdout.indexOf('\r', start);
      String output = stdout.substring(start, end).trim();
      if ((output == null) || (output.length() <= 0)) {
        output = "null";
      }
      return output;
    }
    // If attr is not available return null
    return "null";
  }


  /**
   * Gets results returned from Pyrome
   *
   * @param key
   * @return
   */
  protected String getResultPyrome(String key) {
    if (stdout.contains(key)) {
      int start = stdout.indexOf(key) + key.length() + 1;
      int end = stdout.indexOf('\r', start);
      return stdout.substring(start, end).trim();
    }
    return "null";
  }


  /**
   * Gets results returned from Pyrome
   *
   * @param key
   * @return
   */
  protected String getResultPyromeArray(String key, boolean bQuoted, boolean bEscape, boolean bIndexed) {
    StringBuilder result = new StringBuilder("[");
    String sPattern = "";
    String text = "";
    int idx = 0;
    do {
      sPattern = bIndexed ? (key + ":" + idx) : key;
      LOG.info("Searching for key=" + sPattern);
      text = getResultPyrome(sPattern);
      LOG.info("Result of search=" + text);
      if (!text.contentEquals("null")) {
        if (idx > 0) {
          result.append(",");
        }
        if (bQuoted) {
          result.append("\"" + (bEscape ? StringEscapeUtils.escapeJava(text) : text) + "\"");
        } else {
          result.append(bEscape ? StringEscapeUtils.escapeJava(text) : text);
        }
        idx++;
      }
    } while (!text.contentEquals("null"));
    result.append("]");
    return result.toString();
  }


  public boolean isFileUrlReachable(String targetUrl) throws IOException {
    String fileUrl = XMLUtils.escapeXMLFileURL(targetUrl);
    return isUrlReachable(fileUrl);
  }


  public boolean isUrlReachable(String targetUrl) throws IOException {
    String r2db = Config.getString("r2.db", "http://oms-db.engr.colostate.edu/r2");
    String testUrl = r2db + "/" + targetUrl;
    return exists(testUrl);
  }

  protected static String escapeJavaNoFwdSlash(String text) {
    String javaEsc = StringEscapeUtils.escapeJava(text);
    return javaEsc.replace("\\/", "/");
  }
}