V1_2.java [src/java/m/dssat/substor] Revision: default  Date:
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package m.dssat.substor;

import csip.Config;
import csip.api.server.Executable;
import csip.ModelDataService;
import csip.api.client.ModelDataServiceCall;
import csip.api.server.PayloadParameter;
import csip.api.server.ServiceException;
import csip.annotations.Resource;
import javax.ws.rs.Path;
import csip.annotations.*;
import csip.utils.Parallel;
import csip.utils.ZipFiles;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import m.dssat.utils.DSSATresultParser;
import m.dssat.utils.PlantGro_datastructure;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.runtime.RuntimeConstants;

//TODO: add check on String length DSSBatch
@Name("DSSAT simulation execution")
@Description("DSSAT SUBSTOR csip service")
@VersionInfo("1.2")
@Path("m/substor/1.2")
@Options(timeout = "P2H")
@Resource(type = ResourceType.EXECUTABLE, file = "/DSSAT_4.7.5.8/dscsm047", id = "DSSAT")
@Resource(type = ResourceType.ARCHIVE, file = "/DSSAT_4.7.5.8/data.zip")
public class V1_2 extends ModelDataService {

  private static final String TMP_DSSAT = "tmp.DSSAT";
  static final String KEY_FILE = "param";
  static final String SIM_TYPE = "simulation_type";
  static final String KEY_LOGLEVEL = "loglevel";
  static final String ID_SOIL = "id_soil";
  static final String ID_WEATHER = "wsta....";
  static final String LONGITUDE = "longitude";
  static final String LATITUDE = "latitude";
  static final String FILE_NAME = "file_name";
  static final String COKEY = "cokey";
  static final String START_DATE = "start_date";
  static final String END_DATE = "end_date";
  static final String SOL_EXT = ".SOL";
  static final String WTH_EXT = ".WTH";
  static final String OUT_PARAMS = "output_params";

  public final String workspace = workspace().getPath().toString();


  public String getWorkspace() {
    return workspace;
  }


  @Override
  protected void doProcess() throws Exception {
    PayloadParameter inputParams = parameter();
    String paramFile = inputParams.getString(KEY_FILE);
    double longitude = inputParams.getDouble(LONGITUDE);
    double latitude = inputParams.getDouble(LATITUDE);
    String cokey = inputParams.getString(COKEY);
    String start_date = inputParams.getString(START_DATE);
    String end_date = inputParams.getString(END_DATE);
    String simulation_type = inputParams.getString(SIM_TYPE);
    String[] colNames = inputParams.getStringArray(OUT_PARAMS);

    String flag = getSimulationType(simulation_type, paramFile);
    XfileIDs ids = new XfileIDs();
    parseXfile(ids, paramFile);

    if (flag.toLowerCase().equals("a")) {
      Parallel.run(Config.getBoolean("dssat.serial.datafetch", false),
          () -> {
            fetchClimate(ids.getWthID(), longitude, latitude, start_date, end_date);
          },
          () -> {
            fetchSoil(ids, longitude, latitude, cokey);
          }
      );
    } else {
      fetchSoil(ids, longitude, latitude, cokey);
      int startYear = Integer.valueOf(start_date.split("-")[0]);
      int endYear = Integer.valueOf(end_date.split("-")[0]);
      List<Years> wthFetch = new ArrayList<>();
      for (int year = startYear; year <= endYear; year++) {
        wthFetch.add(new Years(year, ids.getWthID()));
      }
      wthFetch.stream()
          .parallel()
          .forEach(y -> fetchClimate(
          y.getFileName(),
          longitude,
          latitude,
          y.getStart(),
          y.getEnd()));
    }

    Executable p = resources().getExe("DSSAT");
    p.addArguments(
        flag,
        paramFile
    );

    int ret = p.exec();
    if (ret != 0) {
      throw new ServiceException("Error running DSSAT 4.7.5.8: " + ret);
    }
    if (new File(workspace().getFile("PlantGro.OUT").getAbsolutePath()).exists()) {
      DSSATresultParser parser = new DSSATresultParser(colNames, workspace().getFile("PlantGro.OUT"));
      PlantGro_datastructure results = parser.parse();
      results().put("results", results.get());
    } else {
      throw new FileNotFoundException("PlantGro.OUT");
    }
  }

  public class Years {

    private final String start;
    private final String end;
    private final String weatherFileName;


    Years(int start, String weatherFileName) {
      this.weatherFileName = weatherFileName + String.valueOf(start).substring(2) + "01";
      this.start = start + "-01-01";
      this.end = start + "-12-31";
    }


    public String getStart() {
      return start;
    }


    public String getEnd() {
      return end;
    }


    public String getFileName() {
      return weatherFileName;
    }

  }


  private String getSimulationType(String simulation_type, String paramFile) throws IOException {
    switch (simulation_type.toLowerCase()) {
      case "single":
        return "A";
//      case "batch":
//        return "B";
//      case "seasonal":
//        return "N";
      case "sequence":
        createConfig(paramFile);
        return "Q";
//      case "spatial":
//        return "S";
      default:
        String msg = "Simulation " + simulation_type + " not implemented yet";
        throw new IllegalArgumentException(msg);
    }
  }


  private void parseXfile(XfileIDs ids, String paramFile) throws IOException {
    String file;
    if (paramFile.matches("(.*).v[1-9][0-9]")) {
      file = getFileX(paramFile);
    } else {
      file = paramFile;
    }
    getSoilWeatherFileIds(ids, file);
  }


  private String getFileX(String paramFile) throws IOException {
    BufferedReader bf = new BufferedReader(new FileReader(workspace().getFile(paramFile)));

    String line;
    String filex = null;
    Boolean toParse = Boolean.FALSE;
    while ((line = bf.readLine()) != null) {
      if (toParse) {
        String[] parse = line.split("\\s+");
        filex = parse[0];
        toParse = Boolean.FALSE;
      }
      if (line.startsWith("@")) {
        toParse = Boolean.TRUE;
      }
    }
    bf.close();

    String[] pathx = filex.split("/");
    String namex = pathx[pathx.length - 1];
    return namex;
  }


  private void getSoilWeatherFileIds(XfileIDs ids, String fileName) throws IOException, FileNotFoundException {
    BufferedReader bf = new BufferedReader(new FileReader(workspace().getFile(fileName)));
    String line;
    while ((line = bf.readLine()) != null) {
      if (line.toLowerCase().contains(ID_SOIL) && line.toLowerCase().contains(ID_WEATHER)) {
        String[] tmp = line.split("\\s+");
        int index = 0;
        int soilIndex = 0;
        int weatherIndex = 0;
        for (String s : tmp) {
          if (s.toLowerCase().equals(ID_SOIL)) {
            soilIndex = index;
          }
          if (s.toLowerCase().equals(ID_WEATHER)) {
            weatherIndex = index;
          }
          index++;
        }
        line = bf.readLine();
        String[] lineSplit = line.trim().split("\\s+");
        String solID = lineSplit[soilIndex];
        String wthID = lineSplit[weatherIndex];
        ids.setSolID(solID);
        ids.setWthID(wthID);
        break;
      }
    }
    bf.close();
  }


  @Override
  protected void postProcess() throws Exception {
    File zip = ZipFiles.zip(new File(workspace().getDir().getAbsolutePath()));
    results().put(zip);
  }


  public static void onContextInit() {
    System.out.println("Init simple service");
  }


  public static void onContextDestroy() {
    System.out.println("destroy simple service");
  }


  private void fetchClimate(String weatherFile_name, double longitude, double latitude, String start_date, String end_date) {

    ModelDataServiceCall res;
    try {
      res = new ModelDataServiceCall()
          .put(FILE_NAME, weatherFile_name)
          .put(LONGITUDE, longitude)
          .put(LATITUDE, latitude)
          .put(START_DATE, start_date)
          .put(END_DATE, end_date)
          .url(Config.getString("dssat.weather.service", "http://csip.engr.colostate.edu:8088/csip-climate/d/csm_weather/1.0"))
          .withDefaultLogger()
          .call();
    } catch (Exception ex) {
      throw new RuntimeException("Fetchin weather");
    }

    if (res.serviceFinished()) {
      String fileName = weatherFile_name + WTH_EXT;
      try {
        res.download(fileName, workspace().getFile(fileName));
      } catch (Exception ex) {
        throw new RuntimeException("donwloading");
      }
    } else {
      throw new RuntimeException(res.getError());
    }
  }


  private void fetchSoil(XfileIDs ids, double longitude, double latitude, String cokey) throws Exception {
    String soilFile_name = ids.getSolID().substring(0, 2);

    ModelDataServiceCall res = new ModelDataServiceCall()
        .put(FILE_NAME, soilFile_name)
        .put(LONGITUDE, longitude)
        .put(LATITUDE, latitude)
        .put(COKEY, cokey)
        .put(ID_SOIL, ids.getSolID())
        .url(Config.getString("dssat.soil.service", "http://csip.engr.colostate.edu:8088/csip-soils/d/csm_soils/1.0"))
        .withDefaultLogger()
        .call();

    if (res.serviceFinished()) {
      String fileName = soilFile_name + SOL_EXT;
      res.download(fileName, workspace().getFile(fileName));
    } else {
      throw new ServiceException(res.getError());
    }
  }


  private File renameInputTemplate(String template) {
    String sourcePath = workspace().getFile(template).getAbsolutePath();
    String destPath = workspace().getDir().getAbsolutePath() + "/" + TMP_DSSAT;
    File sourceTemplate = new File(sourcePath);
    File destTemplate = new File(destPath);

    try {
      Files.copy(sourceTemplate.toPath(), destTemplate.toPath());
    } catch (IOException ex) {
      throw new RuntimeException(ex.getMessage());
    }

    try {
      if (!Files.readAllLines(sourceTemplate.toPath()).equals(Files.readAllLines(destTemplate.toPath()))) {
        throw new RuntimeException("File not fully copied");
      } else {
        Files.delete(sourceTemplate.toPath());
      }
    } catch (IOException ex) {
      throw new RuntimeException(ex.getMessage());
    }

    return sourceTemplate;
  }


  private File createConfig(String template) throws IOException {
    VelocityEngine velocity = new VelocityEngine();
    velocityInit(velocity);
    VelocityContext context = new VelocityContext();
    context.put("dssat", this);

    File filledTemplate = renameInputTemplate(template);

    try (FileWriter w = new FileWriter(filledTemplate.getAbsolutePath())) {
      Template vt = velocity.getTemplate(TMP_DSSAT, "utf-8");
      vt.merge(context, w);
    }
    return filledTemplate;
  }


  public void velocityInit(VelocityEngine velocity) {
    velocity.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH,
        workspace().getDir().getAbsolutePath());
    velocity.init();
  }

  class XfileIDs {

    private String solID;
    private String wthID;


    public void setSolID(String solID) {
      this.solID = solID;
    }


    public void setWthID(String wthID) {
      this.wthID = wthID;
    }


    public String getSolID() {
      return solID;
    }


    public String getWthID() {
      return wthID;
    }

  }

}