SiLeBAT/FSK-Lab

View on GitHub
de.bund.bfr.knime.fsklab.nodes/src/de/bund/bfr/knime/fsklab/v2_0/fskdbview/FSKDBViewNodeModel.java

Summary

Maintainability
D
1 day
Test Coverage
/*
 ***************************************************************************************************
 * Copyright (c) 2020 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.fskdbview;

import de.bund.bfr.knime.fsklab.v2_0.editor.FSKEditorJSNodeFactory;
import java.util.Arrays;
import java.util.List;
import org.json.JSONArray;
import org.knime.core.data.DataCell;
import org.knime.core.data.DataRow;
import org.knime.core.data.DataTable;
import org.knime.core.data.DataTableSpec;
import org.knime.core.data.DataTableSpecCreator;
import org.knime.core.data.DataType;
import org.knime.core.data.def.BooleanCell;
import org.knime.core.data.def.DefaultRow;
import org.knime.core.data.def.StringCell;
import org.knime.core.node.BufferedDataContainer;
import org.knime.core.node.BufferedDataTable;
import org.knime.core.node.ExecutionContext;
import org.knime.core.node.InvalidSettingsException;
import org.knime.core.node.NodeLogger;
import org.knime.core.node.NodeSettingsRO;
import org.knime.core.node.NodeSettingsWO;
import org.knime.core.node.defaultnodesettings.SettingsModelIntegerBounded;
import org.knime.core.node.defaultnodesettings.SettingsModelNumber;
import org.knime.core.node.defaultnodesettings.SettingsModelString;
import org.knime.core.node.defaultnodesettings.SettingsModelStringArray;
import org.knime.core.node.port.PortObject;
import org.knime.core.node.port.PortType;
import org.knime.core.node.util.CheckUtils;
import org.knime.core.node.web.ValidationError;
import org.knime.js.core.JSONDataTable;
import org.knime.js.core.JSONDataTable.JSONDataTableRow;
import org.knime.js.core.node.AbstractWizardNodeModel;


/**
 * This is an implementation of the node model of the "FSKDBView" node.
 * 
 * This node visualizes FSK models as a HTML table
 */
public class FSKDBViewNodeModel
    extends AbstractWizardNodeModel<FSKDBViewRepresentation, FSKDBViewValue> {
  // Input and output port types
  private static final PortType[] IN_TYPES = {BufferedDataTable.TYPE_OPTIONAL};
  private static final PortType[] OUT_TYPES = {BufferedDataTable.TYPE};
  private static JSONDataTable emptyJSONTable;
  private static final String VIEW_NAME = new FSKEditorJSNodeFactory().getInteractiveViewName();

  protected FSKDBViewNodeModel() {
    super(IN_TYPES, OUT_TYPES, VIEW_NAME);
  }

  /**
   * The logger is used to print info/warning/error messages to the KNIME console and to the KNIME
   * log file. Retrieve it via 'NodeLogger.getLogger' providing the class of this node model.
   */
  private static final NodeLogger LOGGER = NodeLogger.getLogger(FSKDBViewNodeModel.class);

  /**
   * The settings key to retrieve and store settings shared between node dialog and node model. In
   * this case, the key for the online repository URL String that should be entered by the user in
   * the dialog.
   */
  static final String KEY_REPOSITORY_LOCATION = "repository_location";
  static final String KEY_SHOW_DOWNLOAD_BUTTON = "download_button";
  static final String KEY_SHOW_DETAILS_BUTTON = "details_button";
  static final String KEY_SHOW_EXECUTE_BUTTON = "execute_button";
  static final String KEY_SHOW_HEADER = "header";
  static final String KEY_SANDWICH_LIST = "sandwichList";
  static final String KEY_TITLE = "tilte";
  static final String KEY_TOKEN = "token";

  /**
   * The default online repository URL String.
   */
  private static final String DEFAULT_REPOSITORY_LOCATION = "https://knime.bfr.berlin/landingpage/DB/";

  /**
   * The settings model to manage the shared settings. This model will hold the value entered by the
   * user in the dialog and will update once the user changes the value. Furthermore, it provides
   * methods to easily load and save the value to and from the shared settings (see: <br>
   * {@link #loadValidatedSettingsFrom(NodeSettingsRO)}, {@link #saveSettingsTo(NodeSettingsWO)}).
   * <br>
   * Here, we use a SettingsModelString as the online repository URL is a String. Also have a look
   * at the comments in the constructor of the {@link FSKDBViewNodeDialog} as the settings models
   * are also used to create simple dialogs.
   */
  SettingsModelString m_repositoryLocationSettings = createRepositoryLocationSettingsModel();
  SettingsModelString m_TitleSettings = createTitleSettingsModel();
  SettingsModelString m_SandwichListSettings = createSandwichListnSettingsModel();


  /**
   * A convenience method to create a new settings model used for the online repository URL String.
   * This method will also be used in the {@link FSKDBViewNodeDialog}. The settings model will sync
   * via the above defined key.
   * 
   * @return a new SettingsModelString with the key for the online repository URL String
   */
  static SettingsModelString createRepositoryLocationSettingsModel() {
    return new SettingsModelString(KEY_REPOSITORY_LOCATION, DEFAULT_REPOSITORY_LOCATION);
  }

  /**
   * The settings key to retrieve and store settings shared between node dialog and node model. In
   * this case, the key for the maximum number of models that are allowed to be selected that should
   * be entered by the user in the dialog.
   */
  static final String MAX_SELECTION_NUMBER = "max_selection_number";
  static final String SELECTION = "selection";

  /**
   * The default the maximum number of models that are allowed to be selected.
   */
  private static final int DEFAULT_MAX_SELECTION_NUMBER = 2;

  /**
   * The settings model to manage the shared settings. This model will hold the value entered by the
   * user in the dialog and will update once the user changes the value. Furthermore, it provides
   * methods to easily load and save the value to and from the shared settings (see: <br>
   * {@link #loadValidatedSettingsFrom(NodeSettingsRO)}, {@link #saveSettingsTo(NodeSettingsWO)}).
   * <br>
   * Here, we use a SettingsModelNumber as the the maximum number of models that are allowed to be
   * selected is a number. Also have a look at the comments in the constructor of the
   * {@link FSKDBViewNodeDialog} as the settings models are also used to create simple dialogs.
   */
  SettingsModelNumber m_maxSelectionNumberSettings = createMaxSelectionNumberSettingsModel();

  SettingsModelString m_showDownloadSettings = createShowDownloadButtonSettingsModel();
  SettingsModelString m_showDetailsSettings = createShowDetailsButtonSettingsModel();
  SettingsModelString m_showExecuteSettings = createShowExecuteButtonSettingsModel();
  SettingsModelString m_showHeaderSettings = createShowHeaderButtonSettingsModel();
  SettingsModelString m_TokenSettings = createTokenSettingsModel();

  final SettingsModelStringArray selectionSettings = createSelectionSettingsModel();


  /**
   * A convenience method to create a new settings model used for the maximum number of models
   * allowed to be selected number. Minimum 2 models and maximum is 4 are allowed to be selected in
   * the JS View HTML table This method will also be used in the {@link FSKDBViewNodeDialog}. The
   * settings model will sync via the above defined key.
   * 
   * @return a new SettingsModelNumber with the key for the the maximum number of models allowed to
   *         be selected number (between 2 and 4)
   */
  static SettingsModelNumber createMaxSelectionNumberSettingsModel() {
    return new SettingsModelIntegerBounded(MAX_SELECTION_NUMBER, DEFAULT_MAX_SELECTION_NUMBER, 2,
        4);
  }

  static SettingsModelString createShowDownloadButtonSettingsModel() {
    return new SettingsModelString(KEY_SHOW_DOWNLOAD_BUTTON, "false");
  }

  static SettingsModelString createShowExecuteButtonSettingsModel() {
    return new SettingsModelString(KEY_SHOW_EXECUTE_BUTTON, "false");
  }

  static SettingsModelString createShowDetailsButtonSettingsModel() {
    return new SettingsModelString(KEY_SHOW_DETAILS_BUTTON, "false");
  }

  static SettingsModelString createShowHeaderButtonSettingsModel() {
    return new SettingsModelString(KEY_SHOW_HEADER, "false");
  }

  static SettingsModelStringArray createSelectionSettingsModel() {
    return new SettingsModelStringArray(SELECTION, new String[0]);
  }

  static SettingsModelString createSandwichListnSettingsModel() {
    return new SettingsModelString(KEY_SANDWICH_LIST,
        "[{\"title\":\"RAKIP Model Repository (login)\",\"href\":\"https://knime.bfr.berlin/knime/#/RAKIP-Web/7._FSK_Repository_Model_Runner&run\"},{\"title\":\"Infos\",\"href\":\"#\"},{\"title\":\"Imprint\",\"href\":\"#\"},{\"title\":\"Privacy Policy\",\"href\":\"#\"}]");
  }

  static SettingsModelString createTitleSettingsModel() {
    return new SettingsModelString(KEY_TITLE, "FSK-Web Landing Page");
  }
  static SettingsModelString createTokenSettingsModel() {
    return new SettingsModelString(KEY_TOKEN, "?repository=FSK-Web&status=Curated");
  }
  /**
   * {@inheritDoc}
   */
  @Override
  protected void saveSettingsTo(final NodeSettingsWO settings) {
    m_repositoryLocationSettings.saveSettingsTo(settings);
    m_SandwichListSettings.saveSettingsTo(settings);
    m_TitleSettings.saveSettingsTo(settings);
    m_maxSelectionNumberSettings.saveSettingsTo(settings);
    m_showDownloadSettings.saveSettingsTo(settings);
    m_showDetailsSettings.saveSettingsTo(settings);
    m_showExecuteSettings.saveSettingsTo(settings);
    m_showHeaderSettings.saveSettingsTo(settings);
    m_TokenSettings.saveSettingsTo(settings);
    FSKDBViewValue viewValue = getViewValue();
    if (viewValue != null && viewValue.getSelection() != null
        && viewValue.getSelection().length > 0) {
      selectionSettings.setStringArrayValue(viewValue.getSelection());

    }
    selectionSettings.saveSettingsTo(settings);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void loadValidatedSettingsFrom(final NodeSettingsRO settings)
      throws InvalidSettingsException {
    m_repositoryLocationSettings.loadSettingsFrom(settings);
    m_maxSelectionNumberSettings.loadSettingsFrom(settings);
    if (settings.containsKey(KEY_SHOW_DOWNLOAD_BUTTON)) {
      m_showDownloadSettings.loadSettingsFrom(settings);
      m_showDetailsSettings.loadSettingsFrom(settings);
      m_showExecuteSettings.loadSettingsFrom(settings);
      m_showHeaderSettings.loadSettingsFrom(settings);
      m_SandwichListSettings.loadSettingsFrom(settings);
      m_TitleSettings.loadSettingsFrom(settings);
    }
    if (settings.containsKey(SELECTION))
      selectionSettings.loadSettingsFrom(settings);
    if (settings.containsKey(KEY_TOKEN))
      m_TokenSettings.loadSettingsFrom(settings);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void validateSettings(final NodeSettingsRO settings) throws InvalidSettingsException {
    m_repositoryLocationSettings.validateSettings(settings);
    m_maxSelectionNumberSettings.validateSettings(settings);
    if (settings.containsKey(KEY_SHOW_DOWNLOAD_BUTTON)) {
      CheckUtils.checkState(
          settings.getString(KEY_SHOW_DOWNLOAD_BUTTON).equalsIgnoreCase("true")
              || settings.getString(KEY_SHOW_DOWNLOAD_BUTTON).equalsIgnoreCase("false"),
          "Only Boolean Values are allowed for show Download Button");
      CheckUtils.checkState(
          settings.getString(KEY_SHOW_DETAILS_BUTTON).equalsIgnoreCase("true")
              || settings.getString(KEY_SHOW_DETAILS_BUTTON).equalsIgnoreCase("false"),
          "Only Boolean Values are allowed for show Details Button");
      CheckUtils.checkState(
          settings.getString(KEY_SHOW_EXECUTE_BUTTON).equalsIgnoreCase("true")
              || settings.getString(KEY_SHOW_EXECUTE_BUTTON).equalsIgnoreCase("false"),
          "Only Boolean Values are allowed for show Execute Button");
      CheckUtils.checkState(
          settings.getString(KEY_SHOW_HEADER).equalsIgnoreCase("true")
              || settings.getString(KEY_SHOW_HEADER).equalsIgnoreCase("false"),
          "Only Boolean Values are allowed for show Header Button");
      boolean isSandwichListValid = false;
      try {

        new JSONArray(settings.getString(KEY_SANDWICH_LIST));
      } catch (Exception ex) {
        isSandwichListValid = true;
      }
      CheckUtils.checkState(isSandwichListValid == false, "Sandich List is not a valid JSON Array");
      m_SandwichListSettings.validateSettings(settings);
      m_TitleSettings.validateSettings(settings);
    }
    if (settings.containsKey(SELECTION))
      selectionSettings.validateSettings(settings);
  }

  @Override
  public FSKDBViewRepresentation getViewRepresentation() {
    FSKDBViewRepresentation representation;
    synchronized (getLock()) {
      representation = super.getViewRepresentation();
      if (representation == null) {
        representation = createEmptyViewRepresentation();
      }
    }
    representation
        .setSelection(selectionSettings.getStringArrayValue());
    if (representation.getTable() == null) {
      representation.setTable(emptyJSONTable);
    }
    return representation;
  }

  @Override
  public FSKDBViewRepresentation createEmptyViewRepresentation() {
    return new FSKDBViewRepresentation();

  }

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

  @Override
  public FSKDBViewValue getViewValue() {
    FSKDBViewValue val;
    synchronized (getLock()) {
      val = super.getViewValue();
      if (val == null) {
        val = createEmptyViewValue();
        String connectedNodeId = getTableId(0);
        val.setTableID(connectedNodeId);
      }
    }
    return val;
  }

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

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

  @Override
  public void setHideInWizard(boolean hide) {}

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

  @Override
  public void saveCurrentValue(NodeSettingsWO content) {}

  /**
   * {@inheritDoc}
   */
  @Override
  protected DataTableSpec[] configure(final DataTableSpec[] inSpecs)
      throws InvalidSettingsException {
    return inSpecs;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected PortObject[] performExecute(PortObject[] inObjects, ExecutionContext exec)
      throws Exception {
    PortObject inPort = inObjects[0];
    PortObject outputPort = null;

    synchronized (getLock()) {
      FSKDBViewRepresentation representation = getViewRepresentation();
      representation.setRemoteRepositoryURL(m_repositoryLocationSettings.getStringValue());
      representation.setSandwichList(m_SandwichListSettings.getStringValue());
      representation.setTitle(m_TitleSettings.getStringValue());
      representation.setMaxSelectionNumber(
          ((SettingsModelIntegerBounded) m_maxSelectionNumberSettings).getIntValue());
      representation.setShowDownloadButtonChecked(
          m_showDownloadSettings.getStringValue());
      representation.setShowDetailsButtonChecked(
          m_showDetailsSettings.getStringValue());
      representation.setShowExecuteButtonChecked(
          m_showExecuteSettings.getStringValue());
      representation.setShowHeaderButtonChecked(
          m_showHeaderSettings.getStringValue());
      representation
          .setSelection(selectionSettings.getStringArrayValue());
      representation.setToken(m_TokenSettings.getStringValue());
      FSKDBViewValue fskdbViewValue = getViewValue();
      if (fskdbViewValue != null && fskdbViewValue.getSelection() != null
          && fskdbViewValue.getSelection().length > 0) {
        selectionSettings.setStringArrayValue(fskdbViewValue.getSelection());
      }
      if (fskdbViewValue.getTable() != null) {
        outputPort = convertJSONTableToBufferedDataTable(exec);
      } else if (inPort == null && fskdbViewValue.getTable() == null) {
        // if the optional input port is not provided then
        outputPort = createEmptyTable(exec);
        emptyJSONTable =
            JSONDataTable.newBuilder().setDataTable((DataTable) outputPort).build(exec);
        representation.setTable(emptyJSONTable);
      } else if (fskdbViewValue.getTable() == null) {

        // construct a BufferedDataTable from the input object.
        BufferedDataTable table = (BufferedDataTable) inPort;
        JSONDataTable jsonTable = JSONDataTable.newBuilder().setDataTable(table).build(exec);
        representation.setTable(jsonTable);

        // set the table ID which will be used in broadcasting and receiving event in the component.
        String connectedNodeId = getTableId(0);
        representation.setTableID(connectedNodeId);

        outputPort = inPort;
      }
    }
    return new PortObject[] {outputPort};
  }

  /**
   * A helper method for convert the received JSON Table from the JS View to a BufferedDataTable
   * adding a new column for selection.
   * 
   * @param ExecutionContext exec.
   * @return a BufferedDataTable instance.
   */
  private BufferedDataTable convertJSONTableToBufferedDataTable(final ExecutionContext exec) {

    String[] colNames = new String[] {"JSON", "Selected (Table View)"};
    DataType[] colTypes = new DataType[] {StringCell.TYPE, BooleanCell.TYPE};
    DataTableSpec spec = new DataTableSpec(colNames, colTypes);
    BufferedDataContainer container = exec.createDataContainer(spec);


    List<String> selectionList = null;
    FSKDBViewValue viewValue = getViewValue();
    if (viewValue != null && viewValue.getSelection() != null) {
      selectionList = Arrays.asList(viewValue.getSelection());
    }
    for (JSONDataTableRow row : viewValue.getTable().getRows()) {
      String jsonRow = (String) row.getData()[0];
      String rowKey = row.getRowKey();
      DataCell jsonCell = new StringCell(jsonRow);
      BooleanCell booleanCell = BooleanCell.FALSE;
      if (selectionList != null && selectionList.contains(rowKey.toString())) {
        booleanCell = BooleanCell.TRUE;
      }

      DataRow convertedRow = new DefaultRow(rowKey, new DataCell[] {jsonCell, booleanCell});
      container.addRowToTable(convertedRow);
    }

    container.close();
    return container.getTable();
  }

  /**
   * A helper method for creating an empty table in the case of empty input port.
   * 
   * @param ExecutionContext exec.
   * @return an empty BufferedDataTable instance.
   */
  private static BufferedDataTable createEmptyTable(final ExecutionContext exec) {
    BufferedDataContainer container =
        exec.createDataContainer(new DataTableSpecCreator().createSpec());
    container.close();
    return container.getTable();
  }

  @Override
  protected void performReset() {
    selectionSettings.setStringArrayValue(new String[0]);
  }

  @Override
  protected void useCurrentValueAsDefault() {
    // TODO required to preset implementation.

  }
}