SiLeBAT/FSK-Lab

View on GitHub
de.bund.bfr.knime.fsklab.nodes/src/de/bund/bfr/knime/fsklab/v2_0/writer/WriterNodeModel.java

Summary

Maintainability
F
1 wk
Test Coverage
/*
 ***************************************************************************************************
 * Copyright (c) 2017 Federal Institute for Risk Assessment (BfR), Germany
 *
 * This program is free software: you can redistribute it and/or modify it under the terms of the
 * GNU General Public License as published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with this program. If
 * not, see <http://www.gnu.org/licenses/>.
 *
 * Contributors: Department Biological Safety - BfR
 *************************************************************************************************
 */
package de.bund.bfr.knime.fsklab.v2_0.writer;

import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.threetenbp.ThreeTenModule;
import de.bund.bfr.fskml.FSKML;
import de.bund.bfr.fskml.FskMetaDataObject;
import de.bund.bfr.fskml.FskMetaDataObject.ResourceType;
import de.bund.bfr.fskml.sedml.SourceScript;
import de.bund.bfr.knime.fsklab.FskPlugin;
import de.bund.bfr.knime.fsklab.nodes.NodeUtils;
import de.bund.bfr.knime.fsklab.nodes.ScriptHandler;
import de.bund.bfr.knime.fsklab.nodes.WriterNodeUtils;
import de.bund.bfr.knime.fsklab.r.client.LibRegistry;
import de.bund.bfr.knime.fsklab.v2_0.CombinedFskPortObject;
import de.bund.bfr.knime.fsklab.v2_0.FskPortObject;
import de.bund.bfr.knime.fsklab.v2_0.FskSimulation;
import de.bund.bfr.knime.fsklab.v2_0.JoinRelation;
import de.bund.bfr.metadata.swagger.ConsumptionModel;
import de.bund.bfr.metadata.swagger.DataModel;
import de.bund.bfr.metadata.swagger.DoseResponseModel;
import de.bund.bfr.metadata.swagger.ExposureModel;
import de.bund.bfr.metadata.swagger.GenericModel;
import de.bund.bfr.metadata.swagger.HealthModel;
import de.bund.bfr.metadata.swagger.Model;
import de.bund.bfr.metadata.swagger.OtherModel;
import de.bund.bfr.metadata.swagger.Parameter;
import de.bund.bfr.metadata.swagger.PredictiveModel;
import de.bund.bfr.metadata.swagger.ProcessModel;
import de.bund.bfr.metadata.swagger.QraModel;
import de.bund.bfr.metadata.swagger.RiskModel;
import de.bund.bfr.metadata.swagger.ToxicologicalModel;
import de.unirostock.sems.cbarchive.ArchiveEntry;
import de.unirostock.sems.cbarchive.CombineArchive;
import de.unirostock.sems.cbarchive.meta.DefaultMetaDataObject;
import de.unirostock.sems.cbarchive.meta.MetaDataObject;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.xml.stream.XMLStreamException;
import metadata.SwaggerUtil;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.StringUtils;
import org.jdom2.DefaultJDOMFactory;
import org.jdom2.Element;
import org.jdom2.Namespace;
import org.jlibsedml.Algorithm;
import org.jlibsedml.Annotation;
import org.jlibsedml.ChangeAttribute;
import org.jlibsedml.DataGenerator;
import org.jlibsedml.Libsedml;
import org.jlibsedml.Plot2D;
import org.jlibsedml.SEDMLDocument;
import org.jlibsedml.SedML;
import org.jlibsedml.SteadyState;
import org.jlibsedml.Task;
import org.jlibsedml.XPathTarget;
import org.knime.core.node.ExecutionContext;
import org.knime.core.node.InvalidSettingsException;
import org.knime.core.node.NoInternalsModel;
import org.knime.core.node.NodeLogger;
import org.knime.core.node.NodeSettingsRO;
import org.knime.core.node.NodeSettingsWO;
import org.knime.core.node.defaultnodesettings.SettingsModelString;
import org.knime.core.node.port.PortObject;
import org.knime.core.node.port.PortObjectSpec;
import org.knime.core.node.port.PortType;
import org.knime.core.node.util.CheckUtils;
import org.knime.core.util.FileUtil;
import org.sbml.jsbml.SBMLDocument;
import org.sbml.jsbml.SBMLException;
import org.sbml.jsbml.SBMLWriter;
import org.sbml.jsbml.ext.comp.CompConstants;
import org.sbml.jsbml.ext.comp.CompModelPlugin;
import org.sbml.jsbml.ext.comp.CompSBMLDocumentPlugin;
import org.sbml.jsbml.ext.comp.CompSBasePlugin;
import org.sbml.jsbml.ext.comp.ExternalModelDefinition;
import org.sbml.jsbml.ext.comp.ReplacedBy;
import org.sbml.jsbml.ext.comp.Submodel;
import org.sbml.jsbml.xml.XMLAttributes;
import org.sbml.jsbml.xml.XMLNode;
import org.sbml.jsbml.xml.XMLTriple;
import org.threeten.bp.Instant;
import org.threeten.bp.LocalDate;
import org.threeten.bp.ZoneId;

class WriterNodeModel extends NoInternalsModel {

  private static final PortType[] IN_TYPES = {FskPortObject.TYPE};
  private static final PortType[] OUT_TYPES = {};

  private static final NodeLogger LOGGER = NodeLogger.getLogger("Writer node");

  // used in SBML joining annotation
  public static String FIRST_MODEL = "firstModel";
  public static String SECOND_MODEL = "secondModel";
  public static String SUB_MODEL1 = "submodel1";
  public static String SUB_MODEL2 = "submodel2";
  public static final String METADATA_TAG = "parameter";
  public static final String METADATA_NS = "fsk";
  public static final String METADATA_COMMAND = "command";


  static final String CFG_FILE = "file";
  private final SettingsModelString filePath = new SettingsModelString(CFG_FILE, null);

  
  // To calm down Code Climate Warning: define constants for string used more than 3 times
  private static final String RDATA_EXTENSION = "rdata";
  
  public WriterNodeModel() {
    super(IN_TYPES, OUT_TYPES);
  }

  @Override
  protected void saveSettingsTo(NodeSettingsWO settings) {
    filePath.saveSettingsTo(settings);
  }

  @Override
  protected void loadValidatedSettingsFrom(NodeSettingsRO settings)
      throws InvalidSettingsException {
    filePath.loadSettingsFrom(settings);
  }

  @Override
  protected void validateSettings(NodeSettingsRO settings) throws InvalidSettingsException {
    filePath.validateSettings(settings);
  }

  @Override
  protected void reset() {}

  @Override
  protected PortObjectSpec[] configure(PortObjectSpec[] inSpecs) throws InvalidSettingsException {
    String warning = CheckUtils.checkDestinationFile(filePath.getStringValue(), true);
    if (warning != null) {
        setWarningMessage(warning);
    }
    
    return new PortObjectSpec[] {};
  }

  /*
   * add resource files to archive
   */
  private static void addResourcesToArchive(List<Path> resources, CombineArchive archive,
      String filePrefix, Map<String, URI> uris, ScriptHandler scriptHandler) throws Exception {
    for (final Path resourcePath : resources) {

      final String filenameString = filePrefix + resourcePath.getFileName().toString();
      final File resourceFile = resourcePath.toFile();
      String extension = FilenameUtils.getExtension(filenameString.toLowerCase());
      switch(extension) {
        case "txt": archive.addEntry(resourceFile, filenameString, uris.get("plain"));
        break;
        case RDATA_EXTENSION: archive.addEntry(resourceFile, filenameString, uris.get(RDATA_EXTENSION));
        break;
        case "csv": archive.addEntry(resourceFile, filenameString, uris.get("csv"));
        break;
        case "jpeg": archive.addEntry(resourceFile, filenameString, uris.get("jpeg"));
        break;
        case "bmp": archive.addEntry(resourceFile, filenameString, uris.get("bmp"));
        break;
        case "png": archive.addEntry(resourceFile, filenameString, uris.get("png"));
        break;
        case "tiff":  archive.addEntry(resourceFile, filenameString, uris.get("tiff"));
        break;
        case "xlsx": archive.addEntry(resourceFile, filenameString, uris.get("xlsx"));
        break;
        // ADD additional resource files that the model script might need
        case "r" : archive.addEntry(resourceFile, filenameString,
            FSKML.getURIS(1, 0, 12).get(scriptHandler.getFileExtension()));
        break;
        case "py" : archive.addEntry(resourceFile, filenameString,
            FSKML.getURIS(1, 0, 12).get(scriptHandler.getFileExtension()));
        break;
        case "h5": archive.addEntry(resourceFile, filenameString, URI.create("http://purl.org/NET/mediatypes/text-xplain"));
        break;
        // ADD JSON file
        case "json": archive.addEntry(resourceFile, filenameString, URI.create("http://purl.org/NET/mediatypes/text-xplain"));
        break;
        // ADD HTML file
        case "html": archive.addEntry(resourceFile, filenameString, URI.create("https://www.iana.org/assignments/media-types/text/html"));
        break;
        // ADD HTM file
        case "htm":  archive.addEntry(resourceFile, filenameString, URI.create("https://www.iana.org/assignments/media-types/text/html"));
        break;
        // ADD RMarkdown file
        case "rmd": archive.addEntry(resourceFile, filenameString, URI.create("https://www.iana.org/assignments/media-types/text/markdown"));
        break;
        default: LOGGER.warn(filenameString + " not written to file. Extension is not supported");
      }
    }
  }
  
  public static void writeFSKObject(FskPortObject fskObj, CombineArchive archive, String filePrefix,
      Map<String, URI> URIS, ScriptHandler scriptHandler) throws Exception {

    
    addVersion(archive);
   

    // Adds model metadata
    addMetaData(archive, fskObj.modelMetadata, filePrefix + "metaData.json");

    // If the model has an associated working directory with resources these resources
    // need to be saved into the archive.
    if (fskObj.getEnvironmentManager().isPresent()) {
      Optional<Path> workingDirectory = fskObj.getEnvironmentManager().get().getEnvironment();
      if (workingDirectory.isPresent()) {
        // Adds resources
        try (Stream<Path> stream = Files.list(workingDirectory.get())) {
          List<Path> resources = stream.collect(Collectors.toList());
          addResourcesToArchive(resources, archive, filePrefix, URIS, scriptHandler);  
        } catch (Exception e) {
          LOGGER.warn(e.toString());
        }
        
        // after writing the FSKX object, delete the environment to remove temporary files
        fskObj.getEnvironmentManager().get().deleteEnvironment(workingDirectory.get());
      }
    }
    
    // Add generated resources
    if (fskObj.getGeneratedResourcesDirectory().isPresent()) {
      try (Stream<Path> stream = Files.list(fskObj.getGeneratedResourcesDirectory().get().toPath())) {
        List<Path> resources = stream.collect(Collectors.toList());
        addResourcesToArchive(resources, archive, filePrefix, URIS, scriptHandler);
      } catch (Exception e) {
        LOGGER.warn(e.toString());
      }
    }

    // Adds model script
    final ArchiveEntry modelEntry =
        addRScript(archive, fskObj.getModel(), filePrefix + "model." + scriptHandler.getFileExtension(), scriptHandler);
    modelEntry.addDescription(new FskMetaDataObject(ResourceType.modelScript).metaDataObject);

    // Adds visualization script
    final ArchiveEntry vizEntry = addRScript(archive, fskObj.getViz(),
        filePrefix + "visualization." + scriptHandler.getFileExtension(), scriptHandler);
    vizEntry.addDescription(new FskMetaDataObject(ResourceType.visualizationScript).metaDataObject);

    // Adds R workspace file
    if (fskObj.getWorkspace() != null) {
      addWorkspace(archive, fskObj.getWorkspace(), filePrefix);
    }
    // Add simulations
    {
      SEDMLDocument sedmlDoc = createSedml(fskObj, scriptHandler);

      File tempFile = FileUtil.createTempFile("sim", "");
      sedmlDoc.writeDocument(tempFile);
      archive.addEntry(tempFile, filePrefix + "sim.sedml", URIS.get("sedml"));
    }

    // Add simulations as parameter scripts
    for (FskSimulation sim : fskObj.simulations) {
      addParameterScript(archive, sim, filePrefix, scriptHandler);
    }

    // Add SVG plot. If file is not set (empty string) or does not exist then skip
    // this step.
    File plotFile = new File(fskObj.getPlot());
    if (plotFile.exists()) {
      URI uri = URI.create("https://www.iana.org/assignments/media-types/image/svg+xml");
      archive.addEntry(plotFile, filePrefix + "plot.svg", uri);
    }

    // Add readme. Entry has a README annotation to distinguish of other
    // plain text files
    String userReadme = fskObj.getReadme();
    String finalReadme = WriterNodeUtils.prepareReadme(userReadme);
    addReadme(archive, finalReadme, filePrefix);
  }

  private static void writeCombinedObject(CombinedFskPortObject fskObj, CombineArchive archive,
      Map<String, URI> URIS, String filePrefix, ScriptHandler scriptHandler) throws Exception {
    filePrefix = filePrefix + normalizeName(fskObj) + System.getProperty("file.separator");
    
    FskPortObject ffskObj = fskObj.getFirstFskPortObject();
    try (ScriptHandler singleScriptHandler = ScriptHandler
        .createHandler(SwaggerUtil.getLanguageWrittenIn(ffskObj.modelMetadata), ffskObj.packages)) {

      if (ffskObj instanceof CombinedFskPortObject) {
        writeCombinedObject((CombinedFskPortObject) ffskObj, archive, URIS, filePrefix,
            singleScriptHandler);
      } else {
        writeFSKObject(ffskObj, archive,
            filePrefix + normalizeName(ffskObj) + System.getProperty("file.separator"), URIS,
            singleScriptHandler);
      }
    } catch (Exception e) {
      throw new Exception(e.getLocalizedMessage(), e);
    }

    FskPortObject sfskObj = fskObj.getSecondFskPortObject();
    try (ScriptHandler singleScriptHandler = ScriptHandler
        .createHandler(SwaggerUtil.getLanguageWrittenIn(sfskObj.modelMetadata), sfskObj.packages)) {

      if (sfskObj instanceof CombinedFskPortObject) {
        writeCombinedObject((CombinedFskPortObject) sfskObj, archive, URIS, filePrefix,
            singleScriptHandler);
      } else {
        writeFSKObject(sfskObj, archive,
            filePrefix + normalizeName(sfskObj) + System.getProperty("file.separator"), URIS,
            singleScriptHandler);
      }
    } catch (Exception e) {
      throw new Exception(e.getLocalizedMessage(), e);
    }
    
    // Adds R workspace file
    if (fskObj.getWorkspace() != null) {
      addWorkspace(archive, fskObj.getWorkspace(), filePrefix);
    }

    // Adds model metadata of combined model
    addMetaData(archive, fskObj.modelMetadata, filePrefix + "metaData.json");
    // Add combined simulations
    {
      SEDMLDocument sedmlDoc = createSedml(fskObj, scriptHandler);

      File tempFile = FileUtil.createTempFile("sim", "");
      sedmlDoc.writeDocument(tempFile);
      archive.addEntry(tempFile, filePrefix + "sim.sedml", URIS.get("sedml"));
    }

  }
  
  public static void addModificationDate(Model modelMetadata) {
    Date dateJavaFormat = new Date();
    LocalDate dateThreeTenFormat = Instant.ofEpochMilli(dateJavaFormat.getTime()).atZone(ZoneId.systemDefault()).toLocalDate();
    String modelType = modelMetadata.getModelType();
      if(modelType.equalsIgnoreCase( "genericModel")) {
        ((GenericModel)modelMetadata).getGeneralInformation().addModificationDateItem(dateThreeTenFormat);
      }else if(modelType.equalsIgnoreCase( "dataModel")) {
        ((DataModel)modelMetadata).getGeneralInformation().addModificationDateItem(dateThreeTenFormat);
      }else if(modelType.equalsIgnoreCase( "consumptionModel")) {
        ((ConsumptionModel)modelMetadata).getGeneralInformation().addModificationDateItem(dateThreeTenFormat);
      }else if(modelType.equalsIgnoreCase( "doseResponseModel")) {
        ((DoseResponseModel)modelMetadata).getGeneralInformation().addModificationDateItem(dateThreeTenFormat);
      }else if(modelType.equalsIgnoreCase( "exposureModel")) {
        ((ExposureModel)modelMetadata).getGeneralInformation().addModificationDateItem(dateThreeTenFormat);
      }else if(modelType.equalsIgnoreCase( "healthModel")) {
        ((HealthModel)modelMetadata).getGeneralInformation().addModificationDateItem(dateThreeTenFormat);
      }else if(modelType.equalsIgnoreCase( "otherModel")) {
        ((OtherModel)modelMetadata).getGeneralInformation().addModificationDateItem(dateThreeTenFormat);
      }else if(modelType.equalsIgnoreCase( "predictiveModel")) {
        ((PredictiveModel)modelMetadata).getGeneralInformation().addModificationDateItem(dateThreeTenFormat);
      }else if(modelType.equalsIgnoreCase( "processModel")) {
        ((ProcessModel)modelMetadata).getGeneralInformation().addModificationDateItem(dateThreeTenFormat);
      }else if(modelType.equalsIgnoreCase( "qraModel")) {;
        ((QraModel)modelMetadata).getGeneralInformation().addModificationDateItem(dateThreeTenFormat);
      }else if(modelType.equalsIgnoreCase( "riskModel")) {
        ((RiskModel)modelMetadata).getGeneralInformation().addModificationDateItem(dateThreeTenFormat);
      }else if(modelType.equalsIgnoreCase( "toxicologicalModel")) {
        ((ToxicologicalModel)modelMetadata).getGeneralInformation().addModificationDateItem(dateThreeTenFormat);
      }
  }
  
  @Override
  protected PortObject[] execute(PortObject[] inObjects, ExecutionContext exec) throws Exception {
    FskPortObject in = (FskPortObject) inObjects[0];
    addModificationDate(in.modelMetadata);
    try (ScriptHandler scriptHandler = ScriptHandler
        .createHandler(SwaggerUtil.getLanguageWrittenIn(in.modelMetadata), in.packages)) {
      
      
      
      URL url = FileUtil.toURL(filePath.getStringValue());
      File localPath = FileUtil.getFileFromURL(url);

      if (localPath != null) {
        localPath.delete();
        writeArchive(localPath, in, exec, scriptHandler);
      } else {

        // Creates archive in temporary archive file
        File archiveFile = FileUtil.createTempFile("model", "fskx");

        // The file is deleted since we need the path only for the COMBINE archive
        archiveFile.delete();

        // Writes COMBINE archive
        writeArchive(archiveFile, in, exec, scriptHandler);

        // Copies temporary file to output stream
        try (OutputStream os = FileUtil.openOutputConnection(url, "PUT").getOutputStream()) {
          Files.copy(archiveFile.toPath(), os);
        }

        // Deletes temporary file
        archiveFile.delete();
      }

      
    } catch(Exception e) {
      throw new Exception(e.getLocalizedMessage(), e);
    }
    
    return new PortObject[] {};
  }

  private static void writeArchive(File archiveFile, FskPortObject portObject,
      ExecutionContext exec, ScriptHandler scriptHandler) throws Exception {

    Map<String, URI> URIS = FSKML.getURIS(1, 0, 12);

    try (final CombineArchive archive = new CombineArchive(archiveFile)) {

      if (portObject instanceof CombinedFskPortObject) {
        writeCombinedObject((CombinedFskPortObject) portObject, archive, URIS, "", scriptHandler);
      } else {
        writeFSKObject(portObject, archive, "", URIS, scriptHandler);
      }

      // Add SBML document
      {
        SBMLDocument sbmlDocument = createSBML(portObject, archive, "model", URIS, "");

        // Create temporary file and write sbmlDocument into it
        File temporaryFile = File.createTempFile("connections", ".sbml");
        SBMLWriter.write(sbmlDocument, temporaryFile, null, null);

        String targetName;
        if (portObject instanceof CombinedFskPortObject) {
          targetName = normalizeName(portObject) + "/" + sbmlDocument.getModel().getId() + ".sbml";
        } else {
          targetName = sbmlDocument.getModel().getId() + ".sbml";
        }

        archive.addEntry(temporaryFile, targetName, URIS.get("sbml"));
      }

      final URI libUri = NodeUtils.getLibURI();
      List<String> missingPackages = new ArrayList<>();
      List<VersionedPackage> versionedPackages = new ArrayList<>(portObject.packages.size());

      // Get versions of R packages
      for (String packageName : portObject.packages) {
        String command = scriptHandler.getPackageVersionCommand(packageName);

        try {
          String packageVersion = scriptHandler.runScript(command, exec, true)[0];
          versionedPackages.add(new VersionedPackage(packageName, packageVersion));

          Path path = LibRegistry.instance().getPath(packageName);
          if (path != null) {
            File file = path.toFile();
            archive.addEntry(file, file.getName(), libUri);
          }
        } catch (Exception err) {
          missingPackages.add(packageName);
        }
      }

      // Try to retrieve versions of missing packages from the repository
      if (!missingPackages.isEmpty()) {
        String command = scriptHandler.getPackageVersionCommand(missingPackages);

        try {
          String[] execResult = scriptHandler.runScript(command, exec, true);

          for (int index = 0; index < missingPackages.size(); index++) {
            String packageName = missingPackages.get(index);
            String packageVersion = execResult[missingPackages.size() + index];
            versionedPackages.add(new VersionedPackage(packageName, packageVersion));
          }
        } catch (Exception err) {
          // If not able to get package versions from repository, then only add the
          // package names
          LOGGER.info("not able to get package version for: " + missingPackages);
          missingPackages.stream().map(name -> new VersionedPackage(name, ""))
              .forEach(versionedPackages::add);
        }
      }
      
      final String language = StringUtils.defaultIfBlank(
          SwaggerUtil.getLanguageWrittenIn(portObject.modelMetadata), "R");
      PackagesInfo packagesInfo = new PackagesInfo(language, versionedPackages);

      try {
        String jsonString = FskPlugin.getDefault().MAPPER104.writeValueAsString(packagesInfo);
        addPackagesFile(archive, jsonString, "packages.json");
      } catch (Exception err) {
        // do nothing
      }
      
      archive.pack();
    }
  }

  /**
   * Utility class for serializing package information to JSON. The properties "Package" and
   * "Version" are kept for backward-compatibility.
   */
  private static class VersionedPackage {

    @JsonProperty("Package")
    private final String packageName;

    @JsonProperty("Version")
    private final String version;

    VersionedPackage(final String packageName, final String version) {
      this.packageName = packageName;
      this.version = version;
    }
  }

  /**
   * Utility class for the packages information files. It is used for serializing/deserializing
   * packages information.
   */
  private static class PackagesInfo {

    @JsonProperty("Language")
    private final String language;

    @JsonProperty("PackageList")
    private final List<VersionedPackage> packages;

    PackagesInfo(final String language, final List<VersionedPackage> packages) {
      this.language = language;
      this.packages = packages;
    }
  }

  private static void addPackagesFile(final CombineArchive archive, final String packageInfoList,
      final String filename) throws IOException, URISyntaxException {

    File rPackagesFile = File.createTempFile("tempPackage", ".json");
    FileUtils.writeStringToFile(rPackagesFile, packageInfoList, "UTF-8");

    archive.addEntry(rPackagesFile, filename, FSKML.getURIS(1, 0, 12).get("json"));
    rPackagesFile.delete();

  }

  public static String normalizeName(FskPortObject fskObj) {
    String name = "noModelName";
    if(SwaggerUtil.getModelName(fskObj.modelMetadata) != null) {
      name = SwaggerUtil.getModelName(fskObj.modelMetadata)
          .replaceAll("[^a-zA-Z0-9_]", "") // remove everything not char,number or _
          .replace(" ", "");
      //if name starts with number, remove it
      while (name.matches("^[0-9].*$")) {
        name = name.substring(1);
      }
      
    }
    return name;
  }

  private static SBMLDocument createSBML(FskPortObject fskObj, CombineArchive archive,
      String ModelId, Map<String, URI> URIS, String filePrefix) throws IOException {
    filePrefix = filePrefix + normalizeName(fskObj) + System.getProperty("file.separator");
    SBMLDocument doc = new SBMLDocument(3, 1);
    doc.addDeclaredNamespace("xmlns:fsk",
        "https://foodrisklabs.bfr.bund.de/wp-content/uploads/2017/01/FSK-ML_guidance_document_021216.pdf");

    if (fskObj instanceof CombinedFskPortObject) {
      CombinedFskPortObject comFskObj = (CombinedFskPortObject) fskObj;
      try {
        org.sbml.jsbml.Model fskmodel = doc.createModel();
        CompSBMLDocumentPlugin compDoc = (CompSBMLDocumentPlugin) doc.createPlugin("comp");
        CompModelPlugin compMainModel = (CompModelPlugin) fskmodel.getPlugin("comp");

        FskPortObject firstFskObj = comFskObj.getFirstFskPortObject();

        SBMLDocument doc1 =
            createSBML(firstFskObj, archive, normalizeName(firstFskObj), URIS, filePrefix);
        String doc1FileName = writeSBMLFile(doc1, archive,
            filePrefix + normalizeName(firstFskObj) + System.getProperty("file.separator"), URIS);
        createExtSubModel(doc1, doc1FileName,
            filePrefix + SwaggerUtil.getModelName(firstFskObj.modelMetadata), compDoc,
            compMainModel, SUB_MODEL1);

        FskPortObject secondFskObj = comFskObj.getSecondFskPortObject();
        SBMLDocument doc2 =
            createSBML(secondFskObj, archive, normalizeName(secondFskObj), URIS, filePrefix);
        String doc2FileName = writeSBMLFile(doc2, archive,
            filePrefix + normalizeName(secondFskObj) + System.getProperty("file.separator"), URIS);
        createExtSubModel(doc2, doc2FileName,
            filePrefix + SwaggerUtil.getModelName(secondFskObj.modelMetadata), compDoc,
            compMainModel, SUB_MODEL2);

        fskmodel.setId(normalizeName(fskObj));

        JoinRelation[] relations = comFskObj.getJoinerRelation();
        if (relations != null) {
          for (JoinRelation joinRelarion : relations) {
            org.sbml.jsbml.Parameter overridedParameter =
                fskmodel.createParameter(joinRelarion.getTargetParam());

            overridedParameter.setConstant(false);

            CompSBasePlugin plugin =
                (CompSBasePlugin) overridedParameter.getPlugin(CompConstants.shortLabel);
            ReplacedBy replacedBy = plugin.createReplacedBy();
            replacedBy.setIdRef(joinRelarion.getSourceParam());
            replacedBy.setSubmodelRef(SUB_MODEL1);
            // annotate the conversion command
            org.sbml.jsbml.Annotation annot = overridedParameter.getAnnotation();
            XMLAttributes attrs = new XMLAttributes();
            attrs.add(NodeUtils.METADATA_COMMAND_VALUE, joinRelarion.getCommand());
            XMLNode parameterNode =
                new XMLNode(new XMLTriple(METADATA_COMMAND, null, METADATA_NS), attrs);
            annot.appendNonRDFAnnotation(parameterNode);
          }
        }

      } catch (SBMLException | XMLStreamException e) {
        e.printStackTrace();
      }

    } else {

      org.sbml.jsbml.Model fskmodel = doc.createModel(ModelId);
      for (Parameter param : SwaggerUtil.getParameter(fskObj.modelMetadata)) {
        org.sbml.jsbml.Parameter sbmlParameter = fskmodel.createParameter();
        sbmlParameter.setName(param.getName());
        sbmlParameter.setId(param.getId());
        sbmlParameter
            .setConstant(param.getClassification().equals(Parameter.ClassificationEnum.CONSTANT));
        if (param.getValue() != null && !param.getValue().equals("")) {
          org.sbml.jsbml.Annotation annot = sbmlParameter.getAnnotation();
          XMLAttributes attrs = new XMLAttributes();
          attrs.add("value", param.getValue());
          XMLNode parameterNode =
              new XMLNode(new XMLTriple(METADATA_TAG, null, METADATA_NS), attrs);
          annot.appendNonRDFAnnotation(parameterNode);
        }
      }
    }

    return doc;
  }

  public static String writeSBMLFile(SBMLDocument doc, CombineArchive archive, String filePrefix,
      Map<String, URI> URIS) throws IOException, SBMLException, XMLStreamException {
    File tempFile = FileUtil.createTempFile("sbml", "");
    String fileName = filePrefix + doc.getModel().getId() + ".sbml";
    new SBMLWriter().write(doc, tempFile);
    archive.addEntry(tempFile, fileName, FSKML.getURIS(1, 0, 12).get("sbml"));
    return fileName;
  }

  public static ExternalModelDefinition createExtSubModel(SBMLDocument doc, String externalFileName,
      String filePrefix, CompSBMLDocumentPlugin compDoc, CompModelPlugin compMainModel,
      String subModelName) throws IOException, SBMLException, XMLStreamException {

    ExternalModelDefinition externalModel =
        compDoc.createExternalModelDefinition(doc.getModel().getId());
    externalModel.setSource(externalFileName);
    Submodel submodel = compMainModel.createSubmodel(subModelName);
    submodel.setModelRef(doc.getModel().getId());

    return externalModel;
  }

  private static ArchiveEntry addRScript(final CombineArchive archive, final String script,
      final String filename, ScriptHandler scriptHandler) throws IOException, URISyntaxException {

    final File file = File.createTempFile("temp", ".r");
    FileUtils.writeStringToFile(file, script, "UTF-8");

    final ArchiveEntry entry = archive.addEntry(file, filename, FSKML.getURIS(1, 0, 12).get(scriptHandler.getFileExtension()));
    file.delete();

    return entry;
  }

  private static ArchiveEntry addMetaData(CombineArchive archive, Model model, String filename)
      throws IOException {

    JsonFactory jsonFactory = new JsonFactory();
    jsonFactory.configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET, false);
    jsonFactory.configure(JsonParser.Feature.AUTO_CLOSE_SOURCE, false);
    ObjectMapper mapper = new ObjectMapper(jsonFactory);
    mapper.registerModule(new ThreeTenModule());
    mapper.setSerializationInclusion(Include.NON_NULL);

    File file = File.createTempFile("temp", ".json");

    mapper.writeValue(file, model);


    ArchiveEntry entry = archive.addEntry(file, filename, FSKML.getURIS(1, 0, 12).get("json"));
    file.delete();

    return entry;
  }


  public static ExternalModelDefinition createExtSubModel(SBMLDocument doc, String externalFileName,
      CompSBMLDocumentPlugin compDoc, CompModelPlugin compMainModel, String subModelName)
      throws IOException, SBMLException, XMLStreamException {

    ExternalModelDefinition externalModel =
        compDoc.createExternalModelDefinition(doc.getModel().getId());
    externalModel.setSource(externalFileName);
    Submodel submodel = compMainModel.createSubmodel(subModelName);
    submodel.setModelRef(doc.getModel().getId());

    return externalModel;
  }



  private static SEDMLDocument createSedml(FskPortObject portObj, ScriptHandler scriptHandler) {

    SEDMLDocument doc = Libsedml.createDocument();
    SedML sedml = doc.getSedMLModel();

    SwaggerUtil.getParameter(portObj.modelMetadata).stream()
        .filter(param -> param.getClassification() == Parameter.ClassificationEnum.OUTPUT)
        .map(param -> param.getId())
        .map(id -> new DataGenerator(id, "", Libsedml.parseFormulaString(id)))
        .forEach(sedml::addDataGenerator);

    final String languageUri =
        "https://iana.org/assignments/mediatypes/text/x-" + scriptHandler.getFileExtension();

    // Add simulation
    SteadyState simulation = new SteadyState("steadyState", "", new Algorithm(" "));
    {
      SourceScript ss =
          new SourceScript(languageUri, "./param." + scriptHandler.getFileExtension());
      simulation.addAnnotation(new Annotation(ss));
    }
    sedml.addSimulation(simulation);

    // Add selected simulation index
    {
      org.jdom.Element selectedSimulation = new org.jdom.Element("SelectedSimulation");
      selectedSimulation.addContent(Integer.toString(portObj.selectedSimulationIndex));
      sedml.addAnnotation(new Annotation(selectedSimulation));
    }

    for (FskSimulation fskSimulation : portObj.simulations) {

      // Add model
      org.jlibsedml.Model model = new org.jlibsedml.Model(fskSimulation.getName(), "", languageUri,
          "./model." + scriptHandler.getFileExtension());
      sedml.addModel(model);

      // Add task
      {
        String taskId = "task" + sedml.getTasks().size();
        String taskName = "";
        sedml.addTask(new Task(taskId, taskName, model.getId(), simulation.getId()));
      }

      // Add changes to model
      for (Map.Entry<String, String> entry : fskSimulation.getParameters().entrySet()) {

        String parameterName = entry.getKey();
        String parameterValue = entry.getValue().toString();

        ChangeAttribute change =
            new ChangeAttribute(new XPathTarget(parameterName), parameterValue);
        model.addChange(change);
      }
    }

    // Add plot
    {
      SourceScript ss =
          new SourceScript(languageUri, "./visualization." + scriptHandler.getFileExtension());

      Plot2D plot = new Plot2D("plot1", "");
      plot.addAnnotation(new Annotation(ss));
      sedml.addOutput(plot);
    }

    return doc;
  }

  private static void addVersion(CombineArchive archive) {

    DefaultJDOMFactory factory = new DefaultJDOMFactory();
    Namespace dcTermsNamespace = Namespace.getNamespace("dcterms", "http://purl.org/dc/terms/");

    Element conformsToNode = factory.element("conformsTo", dcTermsNamespace);
    conformsToNode.setText("2.0");

    Element element = factory.element("element");
    element.addContent(conformsToNode);

    MetaDataObject metaDataObject = new DefaultMetaDataObject(element);
    archive.addDescription(metaDataObject);
  }

  private static void addWorkspace(CombineArchive archive, Path workspace, String filePrefix)
      throws IOException {

    // Get length of file in bytes
    long fileSizeInBytes = Files.size(workspace);

    // Convert the bytes to Kilobytes (1 KB = 1024 Bytes)
    long fileSizeInKB = fileSizeInBytes / 1024;

    // Convert the KB to MegaBytes (1 MB = 1024 KBytes)
    long fileSizeInMB = fileSizeInKB / 1024;

    // Only save R workspace smaller than 100 MB
    if (fileSizeInMB < 100) {
      final ArchiveEntry workspaceEntry = archive.addEntry(workspace.toFile(),
          filePrefix + "workspace.RData", FSKML.getURIS(1, 0, 12).get(RDATA_EXTENSION));
      workspaceEntry.addDescription(new FskMetaDataObject(ResourceType.workspace).metaDataObject);
    } else {
      LOGGER.warn("Results file larger than 100 MB -> Skipping file");
    }
  }


  private static void addParameterScript(CombineArchive archive, FskSimulation simulation,
      String filePrefix, ScriptHandler scriptHandler) throws IOException {

    String script = scriptHandler.buildParameterScript(simulation);

    File tempFile = File.createTempFile("temp", "." + scriptHandler.getFileExtension());
    FileUtils.writeStringToFile(tempFile, script, "UTF-8");

    String targetName =
        filePrefix + "simulations/" + simulation.getName() + "." + scriptHandler.getFileExtension();
    archive.addEntry(tempFile, targetName,
        FSKML.getURIS(1, 0, 12).get(scriptHandler.getFileExtension()));

    tempFile.delete();
  }

  private static void addReadme(CombineArchive archive, String readme, String filePrefix)
      throws IOException {

    File readmeFile = File.createTempFile("README", ".txt");
    FileUtils.writeStringToFile(readmeFile, readme, "UTF-8");

    ArchiveEntry readmeEntry = archive.addEntry(readmeFile, filePrefix + "README.txt",
        FSKML.getURIS(1, 0, 12).get("plain"));

    readmeFile.delete();

    // Add annotation to readmeEntry
    readmeEntry.addDescription(new FskMetaDataObject(ResourceType.readme).metaDataObject);
  }
}