SiLeBAT/FSK-Lab

View on GitHub
de.bund.bfr.knime.fsklab.nodes/src/de/bund/bfr/knime/fsklab/v2_0/joiner/JoinerNodeModel.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.joiner;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.knime.base.data.xml.SvgCell;
import org.knime.base.data.xml.SvgImageContent;
import org.knime.core.node.CanceledExecutionException;
import org.knime.core.node.ExecutionContext;
import org.knime.core.node.InvalidSettingsException;
import org.knime.core.node.NodeSettingsRO;
import org.knime.core.node.NodeSettingsWO;
import org.knime.core.node.port.PortObject;
import org.knime.core.node.port.PortObjectHolder;
import org.knime.core.node.port.PortObjectSpec;
import org.knime.core.node.port.PortType;
import org.knime.core.node.port.image.ImagePortObject;
import org.knime.core.node.port.image.ImagePortObjectSpec;
import org.knime.core.node.web.ValidationError;
import org.knime.core.node.workflow.FlowVariable;
import org.knime.core.node.workflow.NodeContainer;
import org.knime.core.node.workflow.NodeContext;
import org.knime.core.util.FileUtil;
import org.knime.js.core.node.AbstractWizardNodeModel;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.bund.bfr.knime.fsklab.FskPlugin;
import de.bund.bfr.knime.fsklab.nodes.NodeUtils;
import de.bund.bfr.knime.fsklab.nodes.environment.EnvironmentManager;
import de.bund.bfr.knime.fsklab.v2_0.CombinedFskPortObject;
import de.bund.bfr.knime.fsklab.v2_0.CombinedFskPortObjectSpec;
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.knime.fsklab.v2_0.editor.FSKEditorJSNodeDialog.ModelType;
import de.bund.bfr.knime.fsklab.v2_0.reader.ReaderNodeUtil;
import de.bund.bfr.metadata.swagger.DoseResponseModel;
import de.bund.bfr.metadata.swagger.GenericModel;
import de.bund.bfr.metadata.swagger.Model;
import de.bund.bfr.metadata.swagger.Parameter;
import metadata.ConversionUtils;
import metadata.SwaggerUtil;

/**
 * Fsk Joiner node model.
 */
public final class JoinerNodeModel extends
    AbstractWizardNodeModel<JoinerViewRepresentation, JoinerViewValue> implements PortObjectHolder {

  private final JoinerNodeSettings m_config = new JoinerNodeSettings();

  private FskPortObject firstInputPort;
  private FskPortObject secondInputPort;
  private FskPortObject thirdInputPort;
  private FskPortObject fourthInputPort;

  // public final static String SUFFIX = "_dup";
  public static final String SUFFIX_FIRST = "1";
  public static final String SUFFIX_SECOND = "2";
  // public static final String SUFFIX = "_";
  private Map<String, FskPortObject> fskID_to_fskObject = new HashMap<>();
  private Map<String, Map<String, String>> modelsParamsOriginalNames = new HashMap<>();
  Map<String, List<String>> unModifiedParamsNames = new HashMap<>();
  Map<String, String> originals = new LinkedHashMap<String, String>();

  private int inputModelNumber;
  private CombinedFskPortObject outObj;
  private static final ObjectMapper MAPPER = FskPlugin.getDefault().MAPPER104;

  // Input and output port types
  private static final PortType[] IN_TYPES = {FskPortObject.TYPE_OPTIONAL,
      FskPortObject.TYPE_OPTIONAL, FskPortObject.TYPE_OPTIONAL, FskPortObject.TYPE_OPTIONAL};
  private static final PortType[] OUT_TYPES =
      {CombinedFskPortObject.TYPE_OPTIONAL, ImagePortObject.TYPE_OPTIONAL};

  private static final String VIEW_NAME = new JoinerNodeFactory().getInteractiveViewName();

  public JoinerNodeModel() {
    super(IN_TYPES, OUT_TYPES, VIEW_NAME);
  }

  @Override
  public JoinerViewRepresentation createEmptyViewRepresentation() {
    return new JoinerViewRepresentation();
  }

  @Override
  public JoinerViewValue createEmptyViewValue() {
    return new JoinerViewValue();
  }

  @Override
  public String getJavascriptObjectID() {
    return "de.bund.bfr.knime.fsklab.v2.0.joiner.component";
  }

  @Override
  public boolean isHideInWizard() {
    return false;
  }

  @Override
  public ValidationError validateViewValue(JoinerViewValue viewContent) {
    return null;
  }

  @Override
  public void saveCurrentValue(NodeSettingsWO content) {}

  @Override
  public JoinerViewValue getViewValue() {
    JoinerViewValue val;
    synchronized (getLock()) {
      val = super.getViewValue();
      if (val == null) {
        val = createEmptyViewValue();
      } else if (val.isEmpty()) {
        copyConfigToView(val);
      }

    }
    if (val.joinerModelsData.numberOfModels > 0) {
      inputModelNumber = val.joinerModelsData.numberOfModels;
    }
    return val;
  }

  @Override
  public JoinerViewRepresentation getViewRepresentation() {

    JoinerViewRepresentation representation;

    synchronized (getLock()) {
      representation = super.getViewRepresentation();
      if (representation == null) {
        representation = createEmptyViewRepresentation();
      }

      JoinerModelsData joinerModelsData = representation.joinerModelsData;

      // Set first model parameters
      if (joinerModelsData.firstModelParameters == null && firstInputPort != null) {
        List<Parameter> firstModelParams = SwaggerUtil.getParameter(firstInputPort.modelMetadata);
        if (firstModelParams != null && !firstModelParams.isEmpty()) {
          joinerModelsData.firstModelParameters =
              firstModelParams.toArray(new Parameter[firstModelParams.size()]);
        }
      }

      // Set second model parameters
      if (joinerModelsData.secondModelParameters == null && secondInputPort != null) {
        List<Parameter> secondModelParams = SwaggerUtil.getParameter(secondInputPort.modelMetadata);
        if (secondModelParams != null && !secondModelParams.isEmpty()) {
          joinerModelsData.secondModelParameters =
              secondModelParams.toArray(new Parameter[secondModelParams.size()]);
        }
      }

      // Set third model parameters
      if (joinerModelsData.thirdModelParameters == null && thirdInputPort != null) {
        List<Parameter> thirdModelParams = SwaggerUtil.getParameter(thirdInputPort.modelMetadata);
        if (thirdModelParams != null && !thirdModelParams.isEmpty()) {
          joinerModelsData.thirdModelParameters =
              thirdModelParams.toArray(new Parameter[thirdModelParams.size()]);
        }
      }

      // Set fourth model parameters
      if (joinerModelsData.fourthModelParameters == null && fourthInputPort != null) {
        List<Parameter> fourthModelParams = SwaggerUtil.getParameter(fourthInputPort.modelMetadata);
        if (fourthModelParams != null && !fourthModelParams.isEmpty()) {
          joinerModelsData.fourthModelParameters =
              fourthModelParams.toArray(new Parameter[fourthModelParams.size()]);
        }
      }
      if (firstInputPort != null) {
        if (joinerModelsData.firstModelName == null) {
          joinerModelsData.firstModelName = SwaggerUtil.getModelName(firstInputPort.modelMetadata);
          joinerModelsData.firstModelType = firstInputPort.modelMetadata.getModelType();
        }
        if (joinerModelsData.firstModel == null) {
          joinerModelsData.firstModel = getFSKObjectAsStringArray(firstInputPort);
          joinerModelsData.firstModelType = "GenericModel";
        }
      }
      if (secondInputPort != null) {
        if (joinerModelsData.secondModelName == null) {
          joinerModelsData.secondModelName =
              SwaggerUtil.getModelName(secondInputPort.modelMetadata);
          // TODO To be changed to a generic type
          joinerModelsData.secondModelType = secondInputPort.modelMetadata.getModelType();
        }
        if (joinerModelsData.secondModel == null) {
          joinerModelsData.secondModel = getFSKObjectAsStringArray(secondInputPort);
          joinerModelsData.secondModelType = "GenericModel";
        }
      }

      if (thirdInputPort != null) {
        if (joinerModelsData.thirdModelName == null) {
          joinerModelsData.thirdModelName = SwaggerUtil.getModelName(thirdInputPort.modelMetadata);
          joinerModelsData.thirdModelType = thirdInputPort.modelMetadata.getModelType();
        }
        if (joinerModelsData.thirdModel == null) {
          joinerModelsData.thirdModel = getFSKObjectAsStringArray(thirdInputPort);
          joinerModelsData.thirdModelType = "GenericModel";
        }
      }
      if (fourthInputPort != null) {
        if (joinerModelsData.fourthModelName == null) {
          joinerModelsData.fourthModelName =
              SwaggerUtil.getModelName(fourthInputPort.modelMetadata);
          joinerModelsData.fourthModelType = fourthInputPort.modelMetadata.getModelType();
        }
        if (joinerModelsData.fourthModel == null) {
          joinerModelsData.fourthModel = getFSKObjectAsStringArray(fourthInputPort);
          joinerModelsData.fourthModelType = "GenericModel";
        }
      }
    }
    modelsParamsOriginalNames.remove(null);
    if (this.modelsParamsOriginalNames != null && !this.modelsParamsOriginalNames.isEmpty()) {
      representation.joinerModelsData.modelsParamsOriginalNames = this.modelsParamsOriginalNames;
    } else if (m_config.joinerModelsData != null) {
      representation.joinerModelsData.modelsParamsOriginalNames =
          m_config.joinerModelsData.modelsParamsOriginalNames;
    }
    return representation;
  }



  @Override
  protected PortObjectSpec[] configure(PortObjectSpec[] inSpecs) throws InvalidSettingsException {
    ImagePortObjectSpec imageSpec = new ImagePortObjectSpec(SvgCell.TYPE);
    return new PortObjectSpec[] {CombinedFskPortObjectSpec.INSTANCE, imageSpec};
  }

  private FskPortObject mergeParameterForJoinedObject(CombinedFskPortObject portObject)
      throws JsonProcessingException, IOException {
    FskPortObject first = portObject.getFirstFskPortObject();
    FskPortObject second = portObject.getSecondFskPortObject();
    if (first instanceof CombinedFskPortObject
        && !fskID_to_fskObject.containsKey(SwaggerUtil.getModelName(first.modelMetadata))) {
      first = mergeParameterForJoinedObject((CombinedFskPortObject) first);
    }
    if (second instanceof CombinedFskPortObject
        && !fskID_to_fskObject.containsKey(SwaggerUtil.getModelName(second.modelMetadata))) {
      second = mergeParameterForJoinedObject((CombinedFskPortObject) second);
    }
    // SwaggerUtil.setParameter(portObject.modelMetadata,
    // JoinerNodeUtil.combineParameters(SwaggerUtil.getParameter(first.modelMetadata),
    // SwaggerUtil.getParameter(second.modelMetadata)));

    SwaggerUtil.setParameter(portObject.modelMetadata, JoinerNodeUtil.combineParameters(
        MAPPER.readValue(MAPPER.writeValueAsString(SwaggerUtil.getParameter(first.modelMetadata)),
            new TypeReference<List<Parameter>>() {}),
        MAPPER.readValue(MAPPER.writeValueAsString(SwaggerUtil.getParameter(second.modelMetadata)),
            new TypeReference<List<Parameter>>() {})));
    // TODO join metadata instead of code here
    return portObject;
  }

  @Override
  protected void performReset() {
    createEmptyViewValue();
    setViewRepresentation(null);

    firstInputPort = null;
    secondInputPort = null;
    thirdInputPort = null;
    fourthInputPort = null;
    modelsParamsOriginalNames.clear();
  }

  private void copyValueToConfig() {
    JoinerViewValue value = getViewValue();
    m_config.modelMetaData = value.modelMetaData;
    m_config.connections = value.joinRelations;
    m_config.jsonRepresentation = value.jsonRepresentation;
    m_config.joinerModelsData = value.joinerModelsData;

  }

  private void copyConfigToView(JoinerViewValue value) {
    value.modelMetaData = m_config.modelMetaData;
    value.joinRelations = m_config.connections;
    value.jsonRepresentation = m_config.jsonRepresentation;
    JoinerViewRepresentation representation = getViewRepresentation();
    representation.joinerModelsData =
        m_config.joinerModelsData != null ? m_config.joinerModelsData : new JoinerModelsData();
    value.joinerModelsData = representation.joinerModelsData;

  }


  @Override
  protected void useCurrentValueAsDefault() {
    synchronized (getLock()) {
      copyValueToConfig();
    }
  }

  protected void loadJsonSetting() throws IOException, CanceledExecutionException {

    File directory =
        NodeContext.getContext().getWorkflowManager().getContext().getCurrentLocation();
    File settingFolder = new File(buildContainerName());

    // Get flow variables
    Map<String, FlowVariable> flowVariables;
    if (NodeContext.getContext().getNodeContainer().getFlowObjectStack() != null) {
      flowVariables = NodeContext.getContext().getNodeContainer().getFlowObjectStack()
          .getAvailableFlowVariables();
    } else {
      flowVariables = Collections.emptyMap();
    }

    if (flowVariables.containsKey("JoinRelations.json")) {
      String connectionString = flowVariables.get("JoinRelations.json").getStringValue();
      m_config.connections = MAPPER.readValue(connectionString, JoinRelation[].class);
    } else {
      File configFile = new File(settingFolder, "JoinRelations.json");
      if (configFile.exists()) {
        m_config.connections = MAPPER.readValue(configFile, JoinRelation[].class);
      }
    }

    if (flowVariables.containsKey("modelMetaData.json")) {
      m_config.modelMetaData = flowVariables.get("modelMetaData.json").getStringValue();
    } else {
      File configFile = new File(settingFolder, "modelMetaData.json");
      if (configFile.exists()) {
        m_config.modelMetaData = FileUtils.readFileToString(configFile, StandardCharsets.UTF_8);
      }
    }

    if (flowVariables.containsKey("firstModelParameters.json")) {
      String parametersString = flowVariables.get("firstModelParameters.json").getStringValue();
      m_config.joinerModelsData.firstModelParameters =
          MAPPER.readValue(parametersString, Parameter[].class);
    } else {
      File configFile = new File(settingFolder, "firstModelParameters.json");
      if (configFile.exists()) {
        m_config.joinerModelsData.firstModelParameters =
            MAPPER.readValue(configFile, Parameter[].class);
      }
    }

    if (flowVariables.containsKey("secondModelParameters.json")) {
      String parametersString = flowVariables.get("secondModelParameters.json").getStringValue();
      m_config.joinerModelsData.secondModelParameters =
          MAPPER.readValue(parametersString, Parameter[].class);
    } else {
      File configFile = new File(settingFolder, "secondModelParameters.json");
      if (configFile.exists()) {
        m_config.joinerModelsData.secondModelParameters =
            MAPPER.readValue(configFile, Parameter[].class);
      }
    }

    String sourceTree;
    if (flowVariables.containsKey("sourceTree.json")) {
      sourceTree = flowVariables.get("sourceTree.json").getStringValue();
    } else {
      File configFile = new File(settingFolder, "sourceTree.json");
      if (configFile.exists()) {
        sourceTree = FileUtils.readFileToString(configFile, StandardCharsets.UTF_8);
      } else {
        sourceTree = null;
      }
    }

    String visualizationScript;
    if (flowVariables.containsKey("visualization.txt")) {
      visualizationScript = flowVariables.get("visualization.txt").getStringValue();
    } else {
      File configFile = new File(settingFolder, "visualization.txt");
      if (configFile.exists()) {
        visualizationScript = FileUtils.readFileToString(configFile, StandardCharsets.UTF_8);
      } else {
        visualizationScript = null;
      }
    }

    JoinerViewValue viewValue = getViewValue();
    if (m_config.connections != null)
      viewValue.joinRelations = m_config.connections;
    if (m_config.modelMetaData != null)
      viewValue.modelMetaData = m_config.modelMetaData;

    JoinerViewRepresentation representation = getViewRepresentation();
    if (m_config.joinerModelsData != null
        && m_config.joinerModelsData.firstModelParameters != null) {
      representation.joinerModelsData.firstModelParameters =
          m_config.joinerModelsData.firstModelParameters;
    }
    if (m_config.joinerModelsData != null
        && m_config.joinerModelsData.secondModelParameters != null) {
      representation.joinerModelsData.firstModelParameters =
          m_config.joinerModelsData.secondModelParameters;
    }
  }

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

  @Override
  protected void loadValidatedSettingsFrom(NodeSettingsRO settings)
      throws InvalidSettingsException {
    m_config.load(settings);
  }

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

  @Override
  public PortObject[] getInternalPortObjects() {
    return new PortObject[] {firstInputPort, secondInputPort, thirdInputPort, fourthInputPort};
  }

  @Override
  public void setInternalPortObjects(PortObject[] portObjects) {
    // remove null port objects to handle a case of first or second port in the node are not fed.
    try {
      fixNullPortsToDefault(portObjects);
    } catch (IOException e) {
      e.printStackTrace();
    }
    if (portObjects.length < 2) {
      setWarningMessage("This node created an empty model port object");
    }
  }


  public void setHideInWizard(boolean hide) {}


  /** @return string with node name and id with format "{name} (#{id}) setting". */
  private static String buildContainerName() {
    final NodeContainer nodeContainer = NodeContext.getContext().getNodeContainer();
    return nodeContainer.getNodeContainerDirectory() + " setting";
  }

  private static Map<String, List<String>> getParameterMap(FskPortObject jFirstInputPort,
      FskPortObject jSecondInputPort, FskPortObject jThirdInputPort, FskPortObject jFourthInputPort,
      Map<String, FskPortObject> fskID_to_fskObject) {
    Map<String, List<String>> tempCopyOfParams = new HashMap<>();
    String firstModelName = SwaggerUtil.getModelName(jFirstInputPort.modelMetadata);
    String secondModelName = SwaggerUtil.getModelName(jSecondInputPort.modelMetadata);
    String thirdModelName = SwaggerUtil.getModelName(jThirdInputPort.modelMetadata);
    String fourthModelName = SwaggerUtil.getModelName(jFourthInputPort.modelMetadata);
    if (StringUtils.isNotBlank(firstModelName))
      tempCopyOfParams.put(firstModelName, SwaggerUtil.getParameter(jFirstInputPort.modelMetadata)
          .stream().map(param -> param.getId()).collect(Collectors.toList()));
    if (StringUtils.isNotBlank(secondModelName))
      tempCopyOfParams.put(secondModelName, SwaggerUtil.getParameter(jSecondInputPort.modelMetadata)
          .stream().map(param -> param.getId()).collect(Collectors.toList()));
    if (StringUtils.isNotBlank(thirdModelName))
      tempCopyOfParams.put(thirdModelName, SwaggerUtil.getParameter(jThirdInputPort.modelMetadata)
          .stream().map(param -> param.getId()).collect(Collectors.toList()));
    if (StringUtils.isNotBlank(fourthModelName))
      tempCopyOfParams.put(fourthModelName, SwaggerUtil.getParameter(jFourthInputPort.modelMetadata)
          .stream().map(param -> param.getId()).collect(Collectors.toList()));

    return tempCopyOfParams;
  }



  private PortObject[] fixNullPortsToDefault(PortObject[] inObjects) throws IOException {
    PortObject[] inPorts =
        Arrays.stream(inObjects).filter(s -> (s != null)).toArray(PortObject[]::new);
    inputModelNumber = inPorts.length;
    switch (inputModelNumber) {
      case 1:
        firstInputPort = (FskPortObject) inPorts[0];
        secondInputPort = createEmptyFSKObject();
        thirdInputPort = createEmptyFSKObject();
        fourthInputPort = createEmptyFSKObject();
        break;
      case 2:
        firstInputPort = (FskPortObject) inPorts[0];
        secondInputPort = (FskPortObject) inPorts[1];
        thirdInputPort = createEmptyFSKObject();
        fourthInputPort = createEmptyFSKObject();
        break;
      case 3:
        firstInputPort = (FskPortObject) inPorts[0];
        secondInputPort = (FskPortObject) inPorts[1];
        thirdInputPort = (FskPortObject) inPorts[2];
        fourthInputPort = createEmptyFSKObject();
        break;
      case 4:
        firstInputPort = (FskPortObject) inPorts[0];
        secondInputPort = (FskPortObject) inPorts[1];
        thirdInputPort = (FskPortObject) inPorts[2];
        fourthInputPort = (FskPortObject) inPorts[3];
        break;
      default:
        firstInputPort = createEmptyFSKObject();
        secondInputPort = createEmptyFSKObject();
        thirdInputPort = createEmptyFSKObject();
        fourthInputPort = createEmptyFSKObject();
    }
    fskID_to_fskObject.put(SwaggerUtil.getModelName(firstInputPort.modelMetadata), firstInputPort);
    fskID_to_fskObject.put(SwaggerUtil.getModelName(secondInputPort.modelMetadata),
        secondInputPort);
    fskID_to_fskObject.put(SwaggerUtil.getModelName(thirdInputPort.modelMetadata), thirdInputPort);
    fskID_to_fskObject.put(SwaggerUtil.getModelName(fourthInputPort.modelMetadata),
        fourthInputPort);
    return inPorts;
  }

  private static CombinedFskPortObject createSimpleCombinedFskPortObject(FskPortObject first,
      FskPortObject second) throws IOException {
    CombinedFskPortObject out =
        new CombinedFskPortObject(Optional.empty(), new ArrayList<>(), first, second);
    out.modelMetadata = MAPPER.readValue(MAPPER.writeValueAsString(second.modelMetadata),
        SwaggerUtil.modelClasses.get(second.modelMetadata.getModelType()));
    SwaggerUtil.setModelName(out.modelMetadata, SwaggerUtil.getModelName(first.modelMetadata)
        + SwaggerUtil.getModelName(second.modelMetadata));

    SwaggerUtil.setParameter(out.modelMetadata, JoinerNodeUtil.combineParameters(
        MAPPER.readValue(MAPPER.writeValueAsString(SwaggerUtil.getParameter(first.modelMetadata)),
            new TypeReference<List<Parameter>>() {}),
        MAPPER.readValue(MAPPER.writeValueAsString(SwaggerUtil.getParameter(second.modelMetadata)),
            new TypeReference<List<Parameter>>() {})));
    Set<String> packageSet = new HashSet<>();
    packageSet.addAll(first.packages);
    packageSet.addAll(second.packages);
    out.packages.addAll(packageSet);
    return out;
  }

  private CombinedFskPortObject createCombinedFskPortObject(FskPortObject jFirstInputPort,
      FskPortObject jSecondInputPort, FskPortObject jThirdInputPort, FskPortObject jFourthInputPort)
      throws IOException {
    CombinedFskPortObject outObj;
    switch (inputModelNumber) {
      case 1:
      case 2:
        outObj = createSimpleCombinedFskPortObject(jFirstInputPort, jSecondInputPort);
        break;
      case 3:
        CombinedFskPortObject case3OutObj =
            createSimpleCombinedFskPortObject(jFirstInputPort, jSecondInputPort);
        outObj = createSimpleCombinedFskPortObject(case3OutObj, jThirdInputPort);
        break;
      default:
        CombinedFskPortObject defaultOutObj1 =
            createSimpleCombinedFskPortObject(jFirstInputPort, jSecondInputPort);
        CombinedFskPortObject defaultOutObj2 =
            createSimpleCombinedFskPortObject(defaultOutObj1, jThirdInputPort);
        outObj = createSimpleCombinedFskPortObject(defaultOutObj2, jFourthInputPort);
        break;
    }
    return outObj;
  }

  private static FskPortObject createEmptyFSKObject() throws IOException {

    FskPortObject fskPortObject = new FskPortObject(Optional.empty(), "", Collections.emptyList());
    fskPortObject.setModel("");
    fskPortObject.setViz("");
    fskPortObject.modelMetadata = NodeUtils.initializeModel(ModelType.genericModel);
    return fskPortObject;
  }

  public void convertMetadataToGenericVersion(PortObject[] inObjects)
      throws JsonMappingException, JsonProcessingException, IOException {
    ConversionUtils converter = new ConversionUtils();
    for (PortObject pO : inObjects) {
      FskPortObject fskpo = (FskPortObject) pO;
      if (fskpo != null && !fskpo.modelMetadata.getModelType().equalsIgnoreCase("GenericModel")) {
        Model model =
            converter.convertModel(MAPPER.readTree(MAPPER.writeValueAsString(fskpo.modelMetadata)),
                ConversionUtils.ModelClass.valueOf("GenericModel"));
        if (fskpo.modelMetadata instanceof DoseResponseModel) {
          String modelName = SwaggerUtil.getModelName(((FskPortObject) fskpo).modelMetadata);
          ((GenericModel) model).getGeneralInformation().setName(modelName);
        }
        fskpo.modelMetadata = model;
      }
    }
  }

  @Override
  protected PortObject[] performExecute(PortObject[] inObjects, ExecutionContext exec)
      throws Exception {
    convertMetadataToGenericVersion(inObjects);
    PortObject svgImageFromView = null;
    JoinRelation[] connections = new JoinRelation[0];

    setInternalPortObjects(inObjects);
    outObj = createCombinedFskPortObject(firstInputPort, secondInputPort, thirdInputPort,
        fourthInputPort);
    unModifiedParamsNames = getParameterMap(firstInputPort, secondInputPort, thirdInputPort,
        fourthInputPort, fskID_to_fskObject);
    JoinerNodeUtil.addIdentifierToParametersForCombinedObject(outObj, "", 0, unModifiedParamsNames,
        modelsParamsOriginalNames);
    synchronized (getLock()) {

      JoinerViewValue value = getViewValue();
      if (value.modelMetaData == null) {
        loadJsonSetting();

        if (value.modelMetaData == null && fixNullPortsToDefault(inObjects).length >= 2) {
          value.modelMetaData =
              MAPPER.writeValueAsString(mergeParameterForJoinedObject(outObj).modelMetadata);
        }
        exec.setProgress(1);
      }

      if (value.joinRelations != null) {
        connections = value.joinRelations;
      } else if (m_config.connections != null) {
        connections = m_config.connections;
      }



      // Give CombinedModel some metadata (for now: from second portObject)
      // Consider Here that the model type is the same as the second model
      // if (value.modelMetaData != null) {
      // outObj.modelMetadata = MAPPER.readValue(value.modelMetaData,
      // SwaggerUtil.modelClasses.get(firstInputPort.modelMetadata.getModelType()));
      // }
      JoinerModelsData joinerModelsData = value.joinerModelsData;
      if (joinerModelsData.firstModel != null && joinerModelsData.firstModel.length > 0
          && StringUtils.isNotEmpty(joinerModelsData.firstModel[0])) {


        FskPortObject jFirstInputPort =
            getFSKObjectFromStringArray(firstInputPort.getEnvironmentManager(),
                joinerModelsData.firstModel, joinerModelsData.firstModelType, fskID_to_fskObject);

        FskPortObject jSecondInputPort;
        FskPortObject jThirdInputPort;
        FskPortObject jFourthInputPort;
        if (joinerModelsData.secondModel != null && joinerModelsData.secondModel.length > 0
            && StringUtils.isNotEmpty(joinerModelsData.secondModel[0])) {
          jSecondInputPort = getFSKObjectFromStringArray(secondInputPort.getEnvironmentManager(),
              joinerModelsData.secondModel, joinerModelsData.secondModelType, fskID_to_fskObject);
        } else {
          jSecondInputPort = createEmptyFSKObject();
        }
        if (joinerModelsData.thirdModel != null && joinerModelsData.thirdModel.length > 0
            && StringUtils.isNotEmpty(joinerModelsData.thirdModel[0])) {
          jThirdInputPort = getFSKObjectFromStringArray(thirdInputPort.getEnvironmentManager(),
              joinerModelsData.thirdModel, joinerModelsData.thirdModelType, fskID_to_fskObject);
        } else {
          jThirdInputPort = createEmptyFSKObject();
        }

        if (joinerModelsData.fourthModel != null && joinerModelsData.fourthModel.length > 0
            && StringUtils.isNotEmpty(joinerModelsData.fourthModel[0])) {
          jFourthInputPort = getFSKObjectFromStringArray(fourthInputPort.getEnvironmentManager(),
              joinerModelsData.fourthModel, joinerModelsData.fourthModelType, fskID_to_fskObject);
        } else {
          jFourthInputPort = createEmptyFSKObject();
        }
        fskID_to_fskObject.put(SwaggerUtil.getModelName(jFirstInputPort.modelMetadata),
            jFirstInputPort);
        fskID_to_fskObject.put(SwaggerUtil.getModelName(jSecondInputPort.modelMetadata),
            jSecondInputPort);
        fskID_to_fskObject.put(SwaggerUtil.getModelName(jThirdInputPort.modelMetadata),
            jThirdInputPort);
        fskID_to_fskObject.put(SwaggerUtil.getModelName(jFourthInputPort.modelMetadata),
            jFourthInputPort);
        fskID_to_fskObject.remove(null);
        outObj = createCombinedFskPortObject(jFirstInputPort, jSecondInputPort, jThirdInputPort,
            jFourthInputPort);

        if (!value.joinerModelsData.interactiveMode)
          resetParameterIdForObjectsFromJSON(outObj, 0);

        outObj = createCombinedFskPortObject(jFirstInputPort, jSecondInputPort, jThirdInputPort,
            jFourthInputPort);

        if (value.modelMetaData != null) {
          outObj.modelMetadata = MAPPER.readValue(value.modelMetaData,
              SwaggerUtil.modelClasses.get(firstInputPort.modelMetadata.getModelType()));
        } else {
          outObj.modelMetadata = jFirstInputPort.modelMetadata;
        }

        unModifiedParamsNames = getParameterMap(jFirstInputPort, jSecondInputPort, jThirdInputPort,
            jFourthInputPort, fskID_to_fskObject);

        JoinerNodeUtil.addIdentifierToParametersForCombinedObject(outObj, "", 0,
            unModifiedParamsNames, modelsParamsOriginalNames);
        mergeParameterForJoinedObject(outObj);

      }

      // outObj.setJoinerRelation(connections);
      setBackJoinConnection(outObj, connections, fskID_to_fskObject);
      // change default values for CombinedModel to those of the currently selected simulations

      createDefaultParameterValues(outObj, outObj, 0, fskID_to_fskObject);


      // give the new combined model a name:
      // suggestion: model1.name + model2.name

      JoinerNodeUtil.removeJoinedParameters(connections, outObj);


      // Create default simulation out of parameters metadata
      if (!joinerModelsData.joinedSimulation.isEmpty()) {
        outObj.simulations.addAll(joinerModelsData.joinedSimulation);
      } else {
        JoinerNodeUtil.createDefaultSimulation(outObj);
      }



      // remove suffix from original parameters since they are needed with their original id for the
      // scripts
      // remove the suffix for four models joining
      resetParameterId(outObj, 0);
      svgImageFromView = createImagePortObjectFromView(value.svgRepresentation, "");
    }

    return new PortObject[] {outObj, svgImageFromView};
  }

  /**
   * Creates the port object from the retrieved image content string.
   *
   * @param imageContent the string retrieved from the view, representing the image, may be null
   * @param errorText an error string in case view retrieval failed, may be null
   * @return A {@link PortObject} containing the image created by the view.
   * @throws IOException if an I/O error occurs
   */

  protected final ImagePortObject createImagePortObjectFromView(final String imageData,
      final String error) throws IOException {
    String xmlPrimer = "<?xml version=\"1.0\" encoding=\"utf-8\"?>";
    String svgPrimer = xmlPrimer
        + "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">";
    String image = imageData;
    if (image != null && (image.length() < 4 || !image.substring(0, 4).equalsIgnoreCase("<svg"))) {
      image = null;
    }
    String errorText = error;
    if (StringUtils.isEmpty(image)) {
      if (StringUtils.isEmpty(errorText)) {
        errorText = "JavaScript returned nothing. Possible implementation error.";
      }
      image = "<svg width=\"600px\" height=\"40px\">"
          + "<text x=\"0\" y=\"20\" font-family=\"sans-serif;\" font-size=\"10\">"
          + "SVG retrieval failed: " + errorText + "</text></svg>";
    }
    image = svgPrimer + image;
    InputStream is = new ByteArrayInputStream(image.getBytes("UTF-8"));
    ImagePortObjectSpec imageSpec = new ImagePortObjectSpec(SvgCell.TYPE);
    return new ImagePortObject(new SvgImageContent(is), imageSpec);
  }

  /** A helper method to set the join information to the suitable level */
  private static void setBackJoinConnection(FskPortObject outObj, JoinRelation[] connections,
      Map<String, FskPortObject> fskID_to_fskObject) {
    FskPortObject firstModel = ((CombinedFskPortObject) outObj).getFirstFskPortObject();
    Set<JoinRelation> subConnections = new HashSet<>();
    SwaggerUtil.getParameter(firstModel.modelMetadata).forEach((parameter) -> {
      for (JoinRelation connection : connections) {
        if (connection.getSourceParam().startsWith(parameter.getId())) {
          subConnections.add(connection);
        }
      }
    });
    ((CombinedFskPortObject) outObj)
        .setJoinerRelation(subConnections.stream().toArray(JoinRelation[]::new));
    if (firstModel instanceof CombinedFskPortObject
        && !fskID_to_fskObject.containsKey(SwaggerUtil.getModelName(firstModel.modelMetadata))) {
      setBackJoinConnection(firstModel, connections, fskID_to_fskObject);
    }

  }

  private static void createDefaultParameterValues(FskPortObject outObj, FskPortObject subObj,
      int suffixIndex, Map<String, FskPortObject> fskID_to_fskObject) {
    String modelName = SwaggerUtil.getModelName(subObj.modelMetadata);
    if (subObj instanceof CombinedFskPortObject) {
      ++suffixIndex;
      if (modelName != null && fskID_to_fskObject.containsKey(modelName)
          && !subObj.simulations.isEmpty()) {
        JoinerNodeUtil.createDefaultParameterValues(
            subObj.simulations.get(subObj.selectedSimulationIndex),
            SwaggerUtil.getParameter(subObj.modelMetadata), --suffixIndex);
      } else {
        createDefaultParameterValues(outObj,
            ((CombinedFskPortObject) subObj).getFirstFskPortObject(), suffixIndex,
            fskID_to_fskObject);
        createDefaultParameterValues(outObj,
            ((CombinedFskPortObject) subObj).getSecondFskPortObject(), suffixIndex,
            fskID_to_fskObject);
      }
    } else {
      if (subObj.simulations.size() > 0 && fskID_to_fskObject.containsKey(modelName)) {
        JoinerNodeUtil.createDefaultParameterValues(
            subObj.simulations.get(subObj.selectedSimulationIndex),
            SwaggerUtil.getParameter(subObj.modelMetadata), suffixIndex);
      }
    }
  }

  /** reset the parameters ID of all simple models to their original name without suffix */
  private void resetParameterId(FskPortObject outObj1, int suffixIndex) {
    if (outObj1 instanceof CombinedFskPortObject) {
      FskPortObject first = ((CombinedFskPortObject) outObj1).getFirstFskPortObject();
      FskPortObject second = ((CombinedFskPortObject) outObj1).getSecondFskPortObject();
      ++suffixIndex;
      if (first instanceof CombinedFskPortObject
          && fskID_to_fskObject.containsKey(SwaggerUtil.getModelName(first.modelMetadata))) {
        int suffixspacial = suffixIndex - 1;
        resetParameterIdToOriginal(SwaggerUtil.getParameter(first.modelMetadata), suffixspacial,
            false);
      } else
        resetParameterId(first, suffixIndex);

      if (second instanceof CombinedFskPortObject
          && fskID_to_fskObject.containsKey(SwaggerUtil.getModelName(second.modelMetadata)))
        resetParameterIdToOriginal(SwaggerUtil.getParameter(second.modelMetadata), --suffixIndex,
            false);
      else
        resetParameterId(second, suffixIndex);

    } else {
      resetParameterIdToOriginal(SwaggerUtil.getParameter(outObj1.modelMetadata), suffixIndex,
          false);
    }
  }

  /** reset the parameters ID of all simple models to their original name without suffix */
  private void resetParameterIdForObjectsFromJSON(FskPortObject outObj1, int suffixIndex) {
    String modelName = SwaggerUtil.getModelName(outObj1.modelMetadata);
    if (outObj1 instanceof CombinedFskPortObject) {
      ++suffixIndex;
      if (fskID_to_fskObject.containsKey(modelName) && suffixIndex > 1) {
        resetParameterIdToOriginal(
            SwaggerUtil.getParameter(fskID_to_fskObject.get(modelName).modelMetadata),
            --suffixIndex, false);
      } else {
        FskPortObject firstObject = ((CombinedFskPortObject) outObj1).getFirstFskPortObject();
        String firstModelName = SwaggerUtil.getModelName(firstObject.modelMetadata);
        FskPortObject secondObject = ((CombinedFskPortObject) outObj1).getSecondFskPortObject();
        String secondModelName = SwaggerUtil.getModelName(secondObject.modelMetadata);

        if (firstObject instanceof CombinedFskPortObject
            && unModifiedParamsNames.containsKey(firstModelName)) {
          resetParameterIdToOriginal(
              SwaggerUtil.getParameter(fskID_to_fskObject.get(firstModelName).modelMetadata),
              suffixIndex, true);
        } else {
          resetParameterIdForObjectsFromJSON(firstObject, suffixIndex);

        }
        if (secondObject instanceof CombinedFskPortObject
            && unModifiedParamsNames.containsKey(secondModelName)) {
          resetParameterIdToOriginal(
              SwaggerUtil.getParameter(fskID_to_fskObject.get(secondModelName).modelMetadata),
              suffixIndex, true);
        } else {
          resetParameterIdForObjectsFromJSON(secondObject, suffixIndex);
        }
      }
    } else {
      if (fskID_to_fskObject.containsKey(modelName)) {
        resetParameterIdToOriginal(
            SwaggerUtil.getParameter(fskID_to_fskObject.get(modelName).modelMetadata), suffixIndex,
            false);
      }
    }
  }

  /**
   * Parameters of individual model must be reverted to original id values without suffix
   * 
   * @param parameter list of parameters
   * @param suffixIndex
   */
  private void resetParameterIdToOriginal(List<Parameter> parameters, int suffixIndex,
      boolean replace) {
    for (Parameter p : parameters) {
        p.setId(
            new StringBuilder(p.getId()).delete(p.getId().length() - suffixIndex, p.getId().length()).toString());
    }
  }



  /**
   * converts Object into JSON string to be passed to the JS View used together
   * with @getFSKObjectFromStringArray for the opposite conversion.
   * 
   * @param object
   * @return JSON String
   */
  private static String getObjectAsJSONString(Object object) {
    String jsonStr = "";

    if (object != null) {
      try {
        ObjectMapper mapper = FskPlugin.getDefault().OBJECT_MAPPER;
        jsonStr = mapper.writeValueAsString(object);
      } catch (IOException e) {
        e.printStackTrace();
      }
    }

    return jsonStr;
  }

  private static String[] getFSKObjectAsStringArray(FskPortObject portObject) {
    return new String[] {getObjectAsJSONString(portObject.modelMetadata),
        getObjectAsJSONString(portObject.getModel()), getObjectAsJSONString(portObject.getViz()),
        getObjectAsJSONString(portObject.simulations), getObjectAsJSONString(portObject.packages),
        "", ""};
  }

  /**
   * 
   * @param url of the remote resource to be downloaded.
   * @param fileName the name of the file to be created.
   * @throws IOException
   */
  private static void downloadFile(URL url, String fileName) throws IOException {
    ReadableByteChannel readableByteChannel = Channels.newChannel(url.openStream());
    try (FileOutputStream fileOutputStream = new FileOutputStream(fileName)) {
      fileOutputStream.getChannel().transferFrom(readableByteChannel, 0, Long.MAX_VALUE);
    }
  }

  public static String makeValidFileName(String original) {
    return original.replaceAll("[^a-zA-Z0-9\\.\\-]", "_");
  }

  /**
   * 
   * @param manager
   * @param model array of strings [Metadata JSON string, Model script, Model visualization script,
   *        Simulation list, Libraries, local file location, File download's URL, Model Name ]
   * @param modelType used to deserialize the model metadata
   * @return
   * @throws IOException
   */
  private static FskPortObject getFSKObjectFromStringArray(Optional<EnvironmentManager> manager,
      String[] model, String modelType, Map<String, FskPortObject> fskID_to_fskObject)
      throws IOException {
    FskPortObject portObject = null;

    if (StringUtils.isNotEmpty(model[5])) {
      String fileLocation = model[5].substring(1, model[5].length() - 1);
      fileLocation = fileLocation.replace("file:", "");
      try {
        portObject = ReaderNodeUtil.readArchive(new File(fileLocation));
      } catch (Exception e) {
        e.printStackTrace();
      }
    } else if (StringUtils.isNotEmpty(model[6])) {
      String fileZip =
          FileUtil.getWorkflowTempDir() + File.separator + makeValidFileName(model[7]) + ".fskx";
      File f = new File(fileZip);
      if (!f.exists()) {
        downloadFile(new URL(model[6]), fileZip);
      }
      try {
        portObject = ReaderNodeUtil.readArchive(new File(fileZip));
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
    Model modelMetadata = MAPPER.readValue(model[0], SwaggerUtil.modelClasses.get(modelType));
    String modelName = SwaggerUtil.getModelName(modelMetadata);
    if (portObject == null) {
      if (fskID_to_fskObject.containsKey(modelName)) {
        portObject = fskID_to_fskObject.get(modelName);
      } else {
        portObject = new FskPortObject(manager, "",
            MAPPER.readValue(model[4], new TypeReference<List<String>>() {}));
        portObject.simulations.clear();
        portObject.simulations
            .addAll(MAPPER.readValue(model[3], new TypeReference<List<FskSimulation>>() {}));
      }
    }

    portObject.modelMetadata = modelMetadata;
    if (StringUtils.isNotEmpty(model[1]))
      portObject.setModel(MAPPER.readValue(model[1], String.class));
    if (StringUtils.isNotEmpty(model[2]))
      portObject.setViz(MAPPER.readValue(model[2], String.class));



    return portObject;
  }

}