diff --git a/org.knime.base.filehandling.tests/files/node_settings/BinaryObjectsToStringsNodeParameters.xml b/org.knime.base.filehandling.tests/files/node_settings/BinaryObjectsToStringsNodeParameters.xml new file mode 100644 index 00000000..bd9a5e44 --- /dev/null +++ b/org.knime.base.filehandling.tests/files/node_settings/BinaryObjectsToStringsNodeParameters.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.knime.base.filehandling.tests/files/test_snapshots/org.knime.base.filehandling.binaryobjectstostrings.BinaryObjectsToStringsNodeParametersTest.snap b/org.knime.base.filehandling.tests/files/test_snapshots/org.knime.base.filehandling.binaryobjectstostrings.BinaryObjectsToStringsNodeParametersTest.snap new file mode 100644 index 00000000..794c7987 --- /dev/null +++ b/org.knime.base.filehandling.tests/files/test_snapshots/org.knime.base.filehandling.binaryobjectstostrings.BinaryObjectsToStringsNodeParametersTest.snap @@ -0,0 +1,138 @@ +{ + "data" : { + "model" : { + "columnSelection" : "", + "decoding" : "UTF_8", + "outputMode" : "REPLACE", + "newColumnName" : "String" + } + }, + "schema" : { + "type" : "object", + "properties" : { + "model" : { + "type" : "object", + "properties" : { + "columnSelection" : { + "type" : "string", + "title" : "Column selection", + "description" : "Column containing the binary objects that will be converted to strings.", + "default" : "" + }, + "decoding" : { + "oneOf" : [ { + "const" : "US_ASCII", + "title" : "US-ASCII" + }, { + "const" : "ISO_8859_1", + "title" : "ISO-8859-1" + }, { + "const" : "UTF_8", + "title" : "UTF-8" + }, { + "const" : "UTF_16BE", + "title" : "UTF-16BE" + }, { + "const" : "UTF_16LE", + "title" : "UTF-16LE" + }, { + "const" : "UTF_16", + "title" : "UTF-16" + } ], + "title" : "Decoding", + "description" : "Character set used to decode the binary object bytes into a string. Common choices are UTF-8 (the default), UTF-16, and US-ASCII.\n
    \n
  • US-ASCII: Seven-bit ASCII (ISO646-US / Basic Latin block of Unicode).
  • \n
  • ISO-8859-1: ISO Latin Alphabet No. 1 (ISO-LATIN-1).
  • \n
  • UTF-8: Eight-bit UCS Transformation Format.
  • \n
  • UTF-16BE: Sixteen-bit UCS Transformation Format, big-endian byte order.
  • \n
  • UTF-16LE: Sixteen-bit UCS Transformation Format, little-endian byte order.
  • \n
  • UTF-16: Sixteen-bit UCS Transformation Format, byte order identified by an optional byte-order mark.
  • \n
", + "default" : "UTF_8" + }, + "newColumnName" : { + "type" : "string", + "title" : "New column name", + "description" : "Name of the appended column. Only applicable when \"Append\" is selected.", + "default" : "String" + }, + "outputMode" : { + "oneOf" : [ { + "const" : "REPLACE", + "title" : "Replace" + }, { + "const" : "APPEND", + "title" : "Append" + } ], + "title" : "Output handling", + "description" : "Determines whether to append a new string column or to replace the selected binary object column.\n
    \n
  • Replace: Replaces the selected binary object column with the string column.
  • \n
  • Append: Appends a new string column to the table.
  • \n
", + "default" : "REPLACE" + } + } + } + } + }, + "ui_schema" : { + "elements" : [ { + "type" : "Control", + "scope" : "#/properties/model/properties/columnSelection", + "options" : { + "format" : "dropDown" + }, + "providedOptions" : [ "possibleValues" ] + }, { + "type" : "Control", + "scope" : "#/properties/model/properties/decoding" + }, { + "type" : "Control", + "scope" : "#/properties/model/properties/outputMode", + "options" : { + "format" : "valueSwitch" + } + }, { + "type" : "Control", + "scope" : "#/properties/model/properties/newColumnName", + "rule" : { + "effect" : "SHOW", + "condition" : { + "scope" : "#/properties/model/properties/outputMode", + "schema" : { + "oneOf" : [ { + "const" : "APPEND" + } ] + } + } + } + } ] + }, + "persist" : { + "type" : "object", + "properties" : { + "model" : { + "type" : "object", + "properties" : { + "columnSelection" : { + "configKey" : "columnselection" + }, + "decoding" : { + "configPaths" : [ [ "decoding" ] ] + }, + "outputMode" : { + "configPaths" : [ [ "replace" ] ] + }, + "newColumnName" : { + "configKey" : "columnname" + } + } + } + } + }, + "initialUpdates" : [ { + "scope" : "#/properties/model/properties/columnSelection", + "providedOptionName" : "possibleValues", + "values" : [ { + "indices" : [ ], + "value" : [ { + "id" : "Binary data", + "text" : "Binary data", + "type" : { + "id" : "org.knime.core.data.blob.BinaryObjectDataValue", + "text" : "Binary Object" + } + } ] + } ] + } ] +} \ No newline at end of file diff --git a/org.knime.base.filehandling.tests/files/test_snapshots/org.knime.base.filehandling.binaryobjectstostrings.BinaryObjectsToStringsNodeParametersTest0.settings.xml.snap b/org.knime.base.filehandling.tests/files/test_snapshots/org.knime.base.filehandling.binaryobjectstostrings.BinaryObjectsToStringsNodeParametersTest0.settings.xml.snap new file mode 100644 index 00000000..1c970341 --- /dev/null +++ b/org.knime.base.filehandling.tests/files/test_snapshots/org.knime.base.filehandling.binaryobjectstostrings.BinaryObjectsToStringsNodeParametersTest0.settings.xml.snap @@ -0,0 +1,7 @@ + + + + + + + diff --git a/org.knime.base.filehandling.tests/files/test_snapshots/org.knime.base.filehandling.binaryobjectstostrings.BinaryObjectsToStringsNodeParametersTest1.snap b/org.knime.base.filehandling.tests/files/test_snapshots/org.knime.base.filehandling.binaryobjectstostrings.BinaryObjectsToStringsNodeParametersTest1.snap new file mode 100644 index 00000000..de97e4d0 --- /dev/null +++ b/org.knime.base.filehandling.tests/files/test_snapshots/org.knime.base.filehandling.binaryobjectstostrings.BinaryObjectsToStringsNodeParametersTest1.snap @@ -0,0 +1,138 @@ +{ + "data" : { + "model" : { + "columnSelection" : "Image", + "decoding" : "UTF_8", + "outputMode" : "REPLACE", + "newColumnName" : "String" + } + }, + "schema" : { + "type" : "object", + "properties" : { + "model" : { + "type" : "object", + "properties" : { + "columnSelection" : { + "type" : "string", + "title" : "Column selection", + "description" : "Column containing the binary objects that will be converted to strings.", + "default" : "" + }, + "decoding" : { + "oneOf" : [ { + "const" : "US_ASCII", + "title" : "US-ASCII" + }, { + "const" : "ISO_8859_1", + "title" : "ISO-8859-1" + }, { + "const" : "UTF_8", + "title" : "UTF-8" + }, { + "const" : "UTF_16BE", + "title" : "UTF-16BE" + }, { + "const" : "UTF_16LE", + "title" : "UTF-16LE" + }, { + "const" : "UTF_16", + "title" : "UTF-16" + } ], + "title" : "Decoding", + "description" : "Character set used to decode the binary object bytes into a string. Common choices are UTF-8 (the default), UTF-16, and US-ASCII.\n
    \n
  • US-ASCII: Seven-bit ASCII (ISO646-US / Basic Latin block of Unicode).
  • \n
  • ISO-8859-1: ISO Latin Alphabet No. 1 (ISO-LATIN-1).
  • \n
  • UTF-8: Eight-bit UCS Transformation Format.
  • \n
  • UTF-16BE: Sixteen-bit UCS Transformation Format, big-endian byte order.
  • \n
  • UTF-16LE: Sixteen-bit UCS Transformation Format, little-endian byte order.
  • \n
  • UTF-16: Sixteen-bit UCS Transformation Format, byte order identified by an optional byte-order mark.
  • \n
", + "default" : "UTF_8" + }, + "newColumnName" : { + "type" : "string", + "title" : "New column name", + "description" : "Name of the appended column. Only applicable when \"Append\" is selected.", + "default" : "String" + }, + "outputMode" : { + "oneOf" : [ { + "const" : "REPLACE", + "title" : "Replace" + }, { + "const" : "APPEND", + "title" : "Append" + } ], + "title" : "Output handling", + "description" : "Determines whether to append a new string column or to replace the selected binary object column.\n
    \n
  • Replace: Replaces the selected binary object column with the string column.
  • \n
  • Append: Appends a new string column to the table.
  • \n
", + "default" : "REPLACE" + } + } + } + } + }, + "ui_schema" : { + "elements" : [ { + "type" : "Control", + "scope" : "#/properties/model/properties/columnSelection", + "options" : { + "format" : "dropDown" + }, + "providedOptions" : [ "possibleValues" ] + }, { + "type" : "Control", + "scope" : "#/properties/model/properties/decoding" + }, { + "type" : "Control", + "scope" : "#/properties/model/properties/outputMode", + "options" : { + "format" : "valueSwitch" + } + }, { + "type" : "Control", + "scope" : "#/properties/model/properties/newColumnName", + "rule" : { + "effect" : "SHOW", + "condition" : { + "scope" : "#/properties/model/properties/outputMode", + "schema" : { + "oneOf" : [ { + "const" : "APPEND" + } ] + } + } + } + } ] + }, + "persist" : { + "type" : "object", + "properties" : { + "model" : { + "type" : "object", + "properties" : { + "columnSelection" : { + "configKey" : "columnselection" + }, + "decoding" : { + "configPaths" : [ [ "decoding" ] ] + }, + "outputMode" : { + "configPaths" : [ [ "replace" ] ] + }, + "newColumnName" : { + "configKey" : "columnname" + } + } + } + } + }, + "initialUpdates" : [ { + "scope" : "#/properties/model/properties/columnSelection", + "providedOptionName" : "possibleValues", + "values" : [ { + "indices" : [ ], + "value" : [ { + "id" : "Binary data", + "text" : "Binary data", + "type" : { + "id" : "org.knime.core.data.blob.BinaryObjectDataValue", + "text" : "Binary Object" + } + } ] + } ] + } ] +} \ No newline at end of file diff --git a/org.knime.base.filehandling/src/org/knime/base/filehandling/binaryobjectstostrings/BinaryObjectsToStringsNodeDialog.java b/org.knime.base.filehandling.tests/src/org/knime/base/filehandling/binaryobjectstostrings/BinaryObjectsToStringsNodeParametersTest.java similarity index 50% rename from org.knime.base.filehandling/src/org/knime/base/filehandling/binaryobjectstostrings/BinaryObjectsToStringsNodeDialog.java rename to org.knime.base.filehandling.tests/src/org/knime/base/filehandling/binaryobjectstostrings/BinaryObjectsToStringsNodeParametersTest.java index 995a4698..c06e87ac 100644 --- a/org.knime.base.filehandling/src/org/knime/base/filehandling/binaryobjectstostrings/BinaryObjectsToStringsNodeDialog.java +++ b/org.knime.base.filehandling.tests/src/org/knime/base/filehandling/binaryobjectstostrings/BinaryObjectsToStringsNodeParametersTest.java @@ -1,5 +1,6 @@ /* * ------------------------------------------------------------------------ + * * Copyright by KNIME AG, Zurich, Switzerland * Website: http://www.knime.com; Email: contact@knime.com * @@ -41,67 +42,68 @@ * may freely choose the license terms applicable to such Node, including * when such Node is propagated with or for interoperation with KNIME. * ------------------------------------------------------------------------ - * - * History - * Oct 30, 2012 (Patrick Winter): created */ + package org.knime.base.filehandling.binaryobjectstostrings; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; +import java.io.FileInputStream; +import java.io.IOException; -import org.knime.core.data.blob.BinaryObjectDataValue; -import org.knime.core.node.defaultnodesettings.DefaultNodeSettingsPane; -import org.knime.core.node.defaultnodesettings.DialogComponentButtonGroup; -import org.knime.core.node.defaultnodesettings.DialogComponentColumnNameSelection; -import org.knime.core.node.defaultnodesettings.DialogComponentString; -import org.knime.core.node.defaultnodesettings.DialogComponentStringSelection; -import org.knime.core.node.defaultnodesettings.SettingsModelString; +import org.knime.core.data.DataTableSpec; +import org.knime.core.data.DataType; +import org.knime.core.data.blob.BinaryObjectDataCell; +import org.knime.core.data.def.StringCell; +import org.knime.core.node.InvalidSettingsException; +import org.knime.core.node.NodeSettings; +import org.knime.core.node.port.PortObjectSpec; +import org.knime.core.webui.node.dialog.SettingsType; +import org.knime.core.webui.node.dialog.defaultdialog.NodeParametersUtil; +import org.knime.testing.node.dialog.DefaultNodeSettingsSnapshotTest; +import org.knime.testing.node.dialog.SnapshotTestConfiguration; /** - * NodeDialog for the node. - * - * - * @author Patrick Winter, KNIME AG, Zurich, Switzerland + * Snapshot test for {@link BinaryObjectsToStringsNodeParameters}. + * + * @author Tim Crundall, TNG Technology Consulting GmbH */ -public class BinaryObjectsToStringsNodeDialog extends DefaultNodeSettingsPane { - - private SettingsModelString m_columnselection; - - private SettingsModelString m_decoding; +@SuppressWarnings({"restriction", "javadoc"}) +final class BinaryObjectsToStringsNodeParametersTest extends DefaultNodeSettingsSnapshotTest { - private SettingsModelString m_columnname; + BinaryObjectsToStringsNodeParametersTest() { + super(getConfig()); + } - private SettingsModelString m_replace; + private static SnapshotTestConfiguration getConfig() { + return SnapshotTestConfiguration.builder() // + .withInputPortObjectSpecs(createInputPortSpecs()) // + .testJsonFormsForModel(BinaryObjectsToStringsNodeParameters.class) // + .testJsonFormsWithInstance(SettingsType.MODEL, () -> readSettings()) // + .testNodeSettingsStructure(() -> readSettings()) // + .build(); + } - /** - * New pane for configuring the node dialog. - */ - @SuppressWarnings("unchecked") - protected BinaryObjectsToStringsNodeDialog() { - super(); - m_columnselection = SettingsFactory.createColumnSelectionSettings(); - m_decoding = SettingsFactory.createDecodingSettings(); - m_replace = SettingsFactory.createReplacePolicySettings(); - m_columnname = SettingsFactory.createColumnNameSettings(m_replace); - m_replace.addChangeListener(new ChangeListener() { - @Override - public void stateChanged(final ChangeEvent e) { - boolean append = m_replace.getStringValue().equals(ReplacePolicy.APPEND.getName()); - m_columnname.setEnabled(append); + private static BinaryObjectsToStringsNodeParameters readSettings() { + try { + var path = getSnapshotPath(BinaryObjectsToStringsNodeParameters.class).getParent().resolve("node_settings") + .resolve("BinaryObjectsToStringsNodeParameters.xml"); + try (var fis = new FileInputStream(path.toFile())) { + var nodeSettings = NodeSettings.loadFromXML(fis); + return NodeParametersUtil.loadSettings(nodeSettings.getNodeSettings(SettingsType.MODEL.getConfigKey()), + BinaryObjectsToStringsNodeParameters.class); } - }); - // Column selection - addDialogComponent(new DialogComponentColumnNameSelection(m_columnselection, "Column selection", 0, - BinaryObjectDataValue.class)); - // Decoding - addDialogComponent(new DialogComponentStringSelection(m_decoding, "Decoding", Decodings.getAllDecodings())); - createNewGroup("New column..."); - // Replace - addDialogComponent(new DialogComponentButtonGroup(m_replace, false, "", ReplacePolicy.getAllSettings())); - // Column name - addDialogComponent(new DialogComponentString(m_columnname, "Name", true, 20)); - closeCurrentGroup(); + } catch (IOException | InvalidSettingsException e) { + throw new IllegalStateException(e); + } } + private static PortObjectSpec[] createInputPortSpecs() { + return new PortObjectSpec[]{createDefaultTestTableSpec()}; + } + + private static DataTableSpec createDefaultTestTableSpec() { + return new DataTableSpec( + new String[]{"Binary data", "Other_Column"}, + new DataType[]{DataType.getType(BinaryObjectDataCell.class), DataType.getType(StringCell.class)} + ); + } } diff --git a/org.knime.base.filehandling/src/org/knime/base/filehandling/binaryobjectstostrings/BinaryObjectsToStringsNodeFactory.java b/org.knime.base.filehandling/src/org/knime/base/filehandling/binaryobjectstostrings/BinaryObjectsToStringsNodeFactory.java index 0a953636..daca01d7 100644 --- a/org.knime.base.filehandling/src/org/knime/base/filehandling/binaryobjectstostrings/BinaryObjectsToStringsNodeFactory.java +++ b/org.knime.base.filehandling/src/org/knime/base/filehandling/binaryobjectstostrings/BinaryObjectsToStringsNodeFactory.java @@ -41,63 +41,112 @@ * may freely choose the license terms applicable to such Node, including * when such Node is propagated with or for interoperation with KNIME. * ------------------------------------------------------------------------ - * + * * History * Sep 5, 2012 (Patrick Winter): created */ package org.knime.base.filehandling.binaryobjectstostrings; +import static org.knime.node.impl.description.PortDescription.fixedPort; + +import java.util.List; +import java.util.Map; + +import org.knime.core.node.NodeDescription; import org.knime.core.node.NodeDialogPane; import org.knime.core.node.NodeFactory; import org.knime.core.node.NodeView; +import org.knime.core.webui.node.dialog.NodeDialog; +import org.knime.core.webui.node.dialog.NodeDialogFactory; +import org.knime.core.webui.node.dialog.NodeDialogManager; +import org.knime.core.webui.node.dialog.SettingsType; +import org.knime.core.webui.node.dialog.defaultdialog.DefaultKaiNodeInterface; +import org.knime.core.webui.node.dialog.defaultdialog.DefaultNodeDialog; +import org.knime.core.webui.node.dialog.kai.KaiNodeInterface; +import org.knime.core.webui.node.dialog.kai.KaiNodeInterfaceFactory; +import org.knime.node.impl.description.DefaultNodeDescriptionUtil; +import org.knime.node.impl.description.PortDescription; /** * NodeFactory for node. - * - * + * + * * @author Patrick Winter, KNIME AG, Zurich, Switzerland + * @author Tim Crundall, TNG Technology Consulting GmbH + * @author AI Migration Pipeline v1.2 */ -public class BinaryObjectsToStringsNodeFactory extends NodeFactory { +@SuppressWarnings({"restriction", "removal"}) +public class BinaryObjectsToStringsNodeFactory extends NodeFactory + implements NodeDialogFactory, KaiNodeInterfaceFactory { - /** - * {@inheritDoc} - */ @Override public BinaryObjectsToStringsNodeModel createNodeModel() { return new BinaryObjectsToStringsNodeModel(); } - /** - * {@inheritDoc} - */ @Override public int getNrNodeViews() { return 0; } - /** - * {@inheritDoc} - */ @Override public NodeView createNodeView(final int viewIndex, - final BinaryObjectsToStringsNodeModel nodeModel) { + final BinaryObjectsToStringsNodeModel nodeModel) { return null; } - /** - * {@inheritDoc} - */ @Override public boolean hasDialog() { return true; } - /** - * {@inheritDoc} - */ + private static final String NODE_NAME = "Binary Objects to Strings"; + + private static final String NODE_ICON = "./binaryobjectstostrings16x16.png"; + + private static final String SHORT_DESCRIPTION = "Converts the binary objects of a column to strings."; + + private static final String FULL_DESCRIPTION = "This node converts the binary object cells to string cells."; + + private static final List INPUT_PORTS = List.of( // + fixedPort("Input table", "Table that contains the binary objects that will be converted.") // + ); + + private static final List OUTPUT_PORTS = List.of( // + fixedPort("Output table", "Input table with string column either appended or replacing the binary objects.") // + ); + @Override public NodeDialogPane createNodeDialogPane() { - return new BinaryObjectsToStringsNodeDialog(); + return NodeDialogManager.createLegacyFlowVariableNodeDialog(createNodeDialog()); + } + + @Override + public NodeDialog createNodeDialog() { + return new DefaultNodeDialog(SettingsType.MODEL, BinaryObjectsToStringsNodeParameters.class); + } + + @Override + public NodeDescription createNodeDescription() { + return DefaultNodeDescriptionUtil.createNodeDescription( // + NODE_NAME, // + NODE_ICON, // + INPUT_PORTS, // + OUTPUT_PORTS, // + SHORT_DESCRIPTION, // + FULL_DESCRIPTION, // + List.of(), // + BinaryObjectsToStringsNodeParameters.class, // + null, // + NodeType.Manipulator, // + List.of(), // + null // + ); + } + + @Override + public KaiNodeInterface createKaiNodeInterface() { + return new DefaultKaiNodeInterface(Map.of(SettingsType.MODEL, BinaryObjectsToStringsNodeParameters.class)); } } diff --git a/org.knime.base.filehandling/src/org/knime/base/filehandling/binaryobjectstostrings/BinaryObjectsToStringsNodeFactory.xml b/org.knime.base.filehandling/src/org/knime/base/filehandling/binaryobjectstostrings/BinaryObjectsToStringsNodeFactory.xml deleted file mode 100644 index 5485b88f..00000000 --- a/org.knime.base.filehandling/src/org/knime/base/filehandling/binaryobjectstostrings/BinaryObjectsToStringsNodeFactory.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - Binary Objects to Strings - - - Converts the binary objects of a column to - strings. - - - - - This node converts the binary object cells to string - cells. - - - - - - - - - - - - Table that contains the - binary - objects that will be converted. - - - Input table with - string - column either appended or replacing the binary - objects. - - - diff --git a/org.knime.base.filehandling/src/org/knime/base/filehandling/binaryobjectstostrings/BinaryObjectsToStringsNodeModel.java b/org.knime.base.filehandling/src/org/knime/base/filehandling/binaryobjectstostrings/BinaryObjectsToStringsNodeModel.java index 6601ae1e..326ee7a7 100644 --- a/org.knime.base.filehandling/src/org/knime/base/filehandling/binaryobjectstostrings/BinaryObjectsToStringsNodeModel.java +++ b/org.knime.base.filehandling/src/org/knime/base/filehandling/binaryobjectstostrings/BinaryObjectsToStringsNodeModel.java @@ -50,6 +50,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.util.Optional; import org.apache.commons.io.IOUtils; import org.knime.base.filehandling.NodeUtils; @@ -218,6 +219,15 @@ protected void reset() { */ @Override protected DataTableSpec[] configure(final DataTableSpec[] inSpecs) throws InvalidSettingsException { + // Auto-guess column selection (last BinaryObjectData-compatible column) if not set + if (m_columnselection.getStringValue() == null || m_columnselection.getStringValue().isEmpty()) { + Optional.ofNullable(inSpecs[0]).stream().flatMap(DataTableSpec::stream) // + .filter(col -> col.getType().isCompatible(BinaryObjectDataValue.class)) // + .reduce((first, second) -> second) // + .map(DataColumnSpec::getName) // + .ifPresent(m_columnselection::setStringValue); // + } + // createColumnRearranger will check the settings DataTableSpec outSpec = createColumnRearranger(inSpecs[0]).createSpec(); return new DataTableSpec[]{outSpec}; diff --git a/org.knime.base.filehandling/src/org/knime/base/filehandling/binaryobjectstostrings/BinaryObjectsToStringsNodeParameters.java b/org.knime.base.filehandling/src/org/knime/base/filehandling/binaryobjectstostrings/BinaryObjectsToStringsNodeParameters.java new file mode 100644 index 00000000..283a3b16 --- /dev/null +++ b/org.knime.base.filehandling/src/org/knime/base/filehandling/binaryobjectstostrings/BinaryObjectsToStringsNodeParameters.java @@ -0,0 +1,242 @@ +/* + * ------------------------------------------------------------------------ + * + * Copyright by KNIME AG, Zurich, Switzerland + * Website: http://www.knime.com; Email: contact@knime.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, Version 3, as + * published by the Free Software Foundation. + * + * 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 . + * + * Additional permission under GNU GPL version 3 section 7: + * + * KNIME interoperates with ECLIPSE solely via ECLIPSE's plug-in APIs. + * Hence, KNIME and ECLIPSE are both independent programs and are not + * derived from each other. Should, however, the interpretation of the + * GNU GPL Version 3 ("License") under any applicable laws result in + * KNIME and ECLIPSE being a combined program, KNIME AG herewith grants + * you the additional permission to use and propagate KNIME together with + * ECLIPSE with only the license terms in place for ECLIPSE applying to + * ECLIPSE and the GNU GPL Version 3 applying for KNIME, provided the + * license terms of ECLIPSE themselves allow for the respective use and + * propagation of ECLIPSE together with KNIME. + * + * Additional permission relating to nodes for KNIME that extend the Node + * Extension (and in particular that are based on subclasses of NodeModel, + * NodeDialog, and NodeView) and that only interoperate with KNIME through + * standard APIs ("Nodes"): + * Nodes are deemed to be separate and independent programs and to not be + * covered works. Notwithstanding anything to the contrary in the + * License, the License does not apply to Nodes, you are not required to + * license Nodes under the License, and you are granted a license to + * prepare and propagate Nodes, in each case even if such Nodes are + * propagated with or for interoperation with KNIME. The owner of a Node + * may freely choose the license terms applicable to such Node, including + * when such Node is propagated with or for interoperation with KNIME. + * ------------------------------------------------------------------------ + */ + +package org.knime.base.filehandling.binaryobjectstostrings; + +import java.util.Optional; + +import org.knime.core.data.DataColumnSpec; +import org.knime.core.data.blob.BinaryObjectDataValue; +import org.knime.core.node.InvalidSettingsException; +import org.knime.core.node.NodeSettingsRO; +import org.knime.core.node.NodeSettingsWO; +import org.knime.node.parameters.NodeParameters; +import org.knime.node.parameters.NodeParametersInput; +import org.knime.node.parameters.Widget; +import org.knime.node.parameters.migration.LoadDefaultsForAbsentFields; +import org.knime.node.parameters.migration.Migrate; +import org.knime.node.parameters.persistence.NodeParametersPersistor; +import org.knime.node.parameters.persistence.Persist; +import org.knime.node.parameters.persistence.Persistor; +import org.knime.node.parameters.updates.Effect; +import org.knime.node.parameters.updates.Effect.EffectType; +import org.knime.node.parameters.updates.EffectPredicate; +import org.knime.node.parameters.updates.EffectPredicateProvider; +import org.knime.node.parameters.updates.ParameterReference; +import org.knime.node.parameters.updates.ValueProvider; +import org.knime.node.parameters.updates.ValueReference; +import org.knime.node.parameters.updates.legacy.ColumnNameAutoGuessValueProvider; +import org.knime.node.parameters.widget.choices.ChoicesProvider; +import org.knime.node.parameters.widget.choices.Label; +import org.knime.node.parameters.widget.choices.ValueSwitchWidget; +import org.knime.node.parameters.widget.choices.util.ColumnSelectionUtil; +import org.knime.node.parameters.widget.choices.util.CompatibleColumnsProvider; + +/** + * Node parameters for Binary Objects to Strings. + * + * @author Tim Crundall, TNG Technology Consulting GmbH + * @author AI Migration Pipeline v1.2 + */ +@LoadDefaultsForAbsentFields +@SuppressWarnings("restriction") +final class BinaryObjectsToStringsNodeParameters implements NodeParameters { + + @Widget(title = "Column selection", + description = "Column containing the binary objects that will be converted to strings.") + @ChoicesProvider(BinaryObjectColumnsProvider.class) + @ValueProvider(InputColumnProvider.class) + @Persist(configKey = "columnselection") + @ValueReference(InputColumnRef.class) + String m_columnSelection = ""; + + private interface InputColumnRef extends ParameterReference { + } + + private static final class BinaryObjectColumnsProvider extends CompatibleColumnsProvider { + protected BinaryObjectColumnsProvider() { + super(BinaryObjectDataValue.class); + } + } + + private static final class InputColumnProvider extends ColumnNameAutoGuessValueProvider { + protected InputColumnProvider() { + super(InputColumnRef.class); + } + + @Override + protected Optional autoGuessColumn(final NodeParametersInput parametersInput) { + final var compatibleColumns = ColumnSelectionUtil.getCompatibleColumnsOfFirstPort( // + parametersInput, BinaryObjectDataValue.class // + ); + return compatibleColumns.isEmpty() // + ? Optional.empty() // + : Optional.of(compatibleColumns.get(compatibleColumns.size() - 1)); + } + } + + @Widget(title = "Decoding", description = """ + Character set used to decode the binary object bytes into a string. Common choices are \ + UTF-8 (the default), UTF-16, and US-ASCII.\ + """) + @Persistor(DecodingPersistor.class) + @Migrate(loadDefaultIfAbsent = true) + Decoding m_decoding = Decoding.UTF_8; + + private enum Decoding { + @Label(value = "US-ASCII", description = "Seven-bit ASCII (ISO646-US / Basic Latin block of Unicode).") + US_ASCII(Decodings.US_ASCII), // + @Label(value = "ISO-8859-1", description = "ISO Latin Alphabet No. 1 (ISO-LATIN-1).") + ISO_8859_1(Decodings.ISO_8859_1), // + @Label(value = "UTF-8", description = "Eight-bit UCS Transformation Format.") + UTF_8(Decodings.UTF_8), // + @Label(value = "UTF-16BE", description = "Sixteen-bit UCS Transformation Format, big-endian byte order.") + UTF_16BE(Decodings.UTF_16BE), // + @Label(value = "UTF-16LE", description = "Sixteen-bit UCS Transformation Format, little-endian byte order.") + UTF_16LE(Decodings.UTF_16LE), // + @Label(value = "UTF-16", + description = "Sixteen-bit UCS Transformation Format, byte order identified by an optional byte-order mark.") + UTF_16(Decodings.UTF_16); + + private final String m_name; + + Decoding(final String name) { + m_name = name; + } + + String getName() { + return m_name; + } + } + + private static final class DecodingPersistor implements NodeParametersPersistor { + + private static final String CFG_KEY = "decoding"; + + @Override + public Decoding load(final NodeSettingsRO settings) throws InvalidSettingsException { + final String value = settings.getString(CFG_KEY); + for (final Decoding decoding : Decoding.values()) { + if (decoding.getName().equals(value)) { + return decoding; + } + } + throw new InvalidSettingsException("Unknown decoding: \"" + value + "\"."); + } + + @Override + public void save(final Decoding obj, final NodeSettingsWO settings) { + settings.addString(CFG_KEY, obj.getName()); + } + + @Override + public String[][] getConfigPaths() { + return new String[][]{{CFG_KEY}}; + } + } + + @Widget(title = "Output handling", description = """ + Determines whether to append a new string column or to replace the selected binary object column.\ + """) + @ValueSwitchWidget + @ValueReference(OutputModeRef.class) + @Persistor(OutputModePersistor.class) + @Migrate(loadDefaultIfAbsent = true) + OutputMode m_outputMode = OutputMode.REPLACE; + + private interface OutputModeRef extends ParameterReference { + } + + private enum OutputMode { + @Label(value = "Replace", + description = "Replaces the selected binary object column with the string column.") + REPLACE, // + @Label(value = "Append", description = "Appends a new string column to the table.") + APPEND; + } + + /** + * Persists the OutputMode enum as the legacy string values "Append" / "Replace" under key "replace". + */ + private static final class OutputModePersistor implements NodeParametersPersistor { + + private static final String CFG_KEY = "replace"; + + @Override + public OutputMode load(final NodeSettingsRO settings) throws InvalidSettingsException { + final String value = settings.getString(CFG_KEY, ""); + if (value.isBlank() || ReplacePolicy.APPEND.getName().equals(value)) { + return OutputMode.APPEND; + } + return OutputMode.REPLACE; + } + + @Override + public void save(final OutputMode obj, final NodeSettingsWO settings) { + final var policy = obj == OutputMode.APPEND ? ReplacePolicy.APPEND : ReplacePolicy.REPLACE; + settings.addString(CFG_KEY, policy.getName()); + } + + @Override + public String[][] getConfigPaths() { + return new String[][]{{CFG_KEY}}; + } + } + + @Widget(title = "New column name", + description = "Name of the appended column. Only applicable when \"Append\" is selected.") + @Effect(predicate = IsAppend.class, type = EffectType.SHOW) + @Persist(configKey = "columnname") + String m_newColumnName = "String"; + + private static final class IsAppend implements EffectPredicateProvider { + @Override + public EffectPredicate init(final PredicateInitializer i) { + return i.getEnum(OutputModeRef.class).isOneOf(OutputMode.APPEND); + } + } + +} diff --git a/org.knime.ext.box.authenticator.tests/META-INF/MANIFEST.MF b/org.knime.ext.box.authenticator.tests/META-INF/MANIFEST.MF index 076d8e50..d18728fd 100644 --- a/org.knime.ext.box.authenticator.tests/META-INF/MANIFEST.MF +++ b/org.knime.ext.box.authenticator.tests/META-INF/MANIFEST.MF @@ -2,10 +2,10 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Unit-Tests for org.knime.ext.box.authenticator Bundle-SymbolicName: org.knime.ext.box.authenticator.tests;singleton:=true -Bundle-Version: 5.9.0.qualifier +Bundle-Version: 5.12.0.qualifier Bundle-Vendor: KNIME AG, Zurich, Switzerland Bundle-RequiredExecutionEnvironment: JavaSE-17 -Fragment-Host: org.knime.ext.box.authenticator;bundle-version="[5.6.0,6.0.0)" +Fragment-Host: org.knime.ext.box.authenticator;bundle-version="[5.12.0,6.0.0)" Eclipse-BundleShape: dir Automatic-Module-Name: org.knime.ext.box.authenticator.tests Require-Bundle: org.knime.core.ui.testing;bundle-version="[5.6.0,6.0.0)", diff --git a/org.knime.ext.box.authenticator.tests/files/test_snapshots/org.knime.ext.box.authenticator.node.BoxAuthenticatorSettingsTest.snap b/org.knime.ext.box.authenticator.tests/files/test_snapshots/org.knime.ext.box.authenticator.node.BoxAuthenticatorSettingsTest.snap index 17433db6..3dbc59aa 100644 --- a/org.knime.ext.box.authenticator.tests/files/test_snapshots/org.knime.ext.box.authenticator.node.BoxAuthenticatorSettingsTest.snap +++ b/org.knime.ext.box.authenticator.tests/files/test_snapshots/org.knime.ext.box.authenticator.node.BoxAuthenticatorSettingsTest.snap @@ -1,7 +1,15 @@ { "data" : { "model" : { - "boxApp" : { }, + "boxApp" : { + "secret" : { + "credentials" : { + "isHiddenPassword" : false, + "isHiddenSecondFactor" : false, + "username" : "" + } + } + }, "authType" : "OAUTH", "redirectUrl" : "http://localhost:33749/" } @@ -27,13 +35,55 @@ "boxApp" : { "type" : "object", "properties" : { - "flowVariable" : { - "type" : "string", - "title" : "App ID and secret (flow variable)", - "description" : "Specifies a credentials flow variable with the app/client ID and secret of the custom Box app.\nThese fields can be found in the configuration settings of your custom Box app.\n" + "secret" : { + "type" : "object", + "properties" : { + "credentials" : { + "type" : "object", + "properties" : { + "password" : { + "type" : "string", + "default" : "" + }, + "secondFactor" : { + "type" : "string", + "default" : "" + }, + "username" : { + "type" : "string", + "default" : "" + } + }, + "default" : { + "isHiddenPassword" : false, + "isHiddenSecondFactor" : false, + "username" : "" + } + }, + "flowVarName" : { + "type" : "string" + } + }, + "title" : "App ID and secret", + "description" : "Specifies the app/client ID and secret of the custom Box app.\nThese fields can be found in the configuration settings of your custom Box app.\n", + "default" : { + "credentials" : { + "isHiddenPassword" : false, + "isHiddenSecondFactor" : false, + "username" : "" + } + } } }, - "default" : { } + "default" : { + "secret" : { + "credentials" : { + "isHiddenPassword" : false, + "isHiddenSecondFactor" : false, + "username" : "" + } + } + } }, "enterpriseId" : { "type" : "string", @@ -62,11 +112,12 @@ "type" : "Section", "elements" : [ { "type" : "Control", - "scope" : "#/properties/model/properties/boxApp/properties/flowVariable", + "scope" : "#/properties/model/properties/boxApp/properties/secret", "options" : { - "format" : "dropDown" - }, - "providedOptions" : [ "possibleValues" ] + "passwordLabel" : "Secret", + "usernameLabel" : "ID", + "format" : "legacyCredentials" + } } ] }, { "label" : "Authentication method", @@ -131,10 +182,10 @@ } ], "displayErrorMessage" : true, "showTitleAndDescription" : false, - "dependencies" : [ "#/properties/model/properties/boxApp/properties/flowVariable", "#/properties/model/properties/authType", "#/properties/model/properties/enterpriseId", "#/properties/model/properties/redirectUrl" ], + "dependencies" : [ "#/properties/model/properties/boxApp/properties/secret", "#/properties/model/properties/authType", "#/properties/model/properties/enterpriseId", "#/properties/model/properties/redirectUrl" ], "updateOptions" : { "updateHandler" : "org.knime.ext.box.authenticator.node.BoxAuthenticatorSettings$LoginUpdateHandler", - "dependencies" : [ "#/properties/model/properties/boxApp/properties/flowVariable", "#/properties/model/properties/authType", "#/properties/model/properties/enterpriseId", "#/properties/model/properties/redirectUrl" ] + "dependencies" : [ "#/properties/model/properties/boxApp/properties/secret", "#/properties/model/properties/authType", "#/properties/model/properties/enterpriseId", "#/properties/model/properties/redirectUrl" ] } }, "rule" : { @@ -160,7 +211,18 @@ "boxApp" : { "type" : "object", "properties" : { - "flowVariable" : { } + "secret" : { + "type" : "object", + "properties" : { + "credentials" : { }, + "flowVarName" : { + "configPaths" : [ ] + } + }, + "deprecatedConfigKeys" : [ { + "deprecated" : [ [ "flowVariable" ] ] + } ] + } } }, "authType" : { }, @@ -173,12 +235,16 @@ } } }, - "initialUpdates" : [ { - "scope" : "#/properties/model/properties/boxApp/properties/flowVariable", - "providedOptionName" : "possibleValues", - "values" : [ { - "indices" : [ ], - "value" : [ ] - } ] + "globalUpdates" : [ { + "trigger" : { + "id" : "after-open-dialog" + }, + "triggerInitially" : true, + "dependencies" : [ "#/properties/model/properties/boxApp" ] + }, { + "trigger" : { + "scope" : "#/properties/model/properties/boxApp" + }, + "dependencies" : [ "#/properties/model/properties/boxApp" ] } ] } \ No newline at end of file diff --git a/org.knime.ext.box.authenticator.tests/pom.xml b/org.knime.ext.box.authenticator.tests/pom.xml index 53362e9a..2a9c5e5d 100644 --- a/org.knime.ext.box.authenticator.tests/pom.xml +++ b/org.knime.ext.box.authenticator.tests/pom.xml @@ -15,7 +15,7 @@ eclipse-test-plugin - 5.9.0 + 5.12.0 src/eclipse diff --git a/org.knime.ext.box.authenticator/META-INF/MANIFEST.MF b/org.knime.ext.box.authenticator/META-INF/MANIFEST.MF index cd9934d2..c97acb87 100644 --- a/org.knime.ext.box.authenticator/META-INF/MANIFEST.MF +++ b/org.knime.ext.box.authenticator/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Plugin for the Box Authenticator node Bundle-SymbolicName: org.knime.ext.box.authenticator;singleton:=true -Bundle-Version: 5.9.0.qualifier +Bundle-Version: 5.12.0.qualifier Bundle-Vendor: KNIME AG, Zurich, Switzerland Require-Bundle: org.knime.workbench.repository;bundle-version="[5.9.0,6.0.0)", org.knime.core;bundle-version="[5.9.0,6.0.0)", diff --git a/org.knime.ext.box.authenticator/src-deprecated/org/knime/ext/box/authenticator/node/BoxAppSettings.java b/org.knime.ext.box.authenticator/src-deprecated/org/knime/ext/box/authenticator/node/BoxAppSettings.java index fc3088e7..3ea181de 100644 --- a/org.knime.ext.box.authenticator/src-deprecated/org/knime/ext/box/authenticator/node/BoxAppSettings.java +++ b/org.knime.ext.box.authenticator/src-deprecated/org/knime/ext/box/authenticator/node/BoxAppSettings.java @@ -50,46 +50,71 @@ import org.knime.core.node.InvalidSettingsException; import org.knime.core.node.workflow.CredentialsProvider; +import org.knime.core.webui.node.dialog.defaultdialog.setting.credentials.LegacyCredentials; +import org.knime.core.webui.node.dialog.defaultdialog.widget.validation.custom.CustomValidation; +import org.knime.credentials.base.node.CredentialVariableMigration; import org.knime.credentials.base.node.CredentialsSettings; import org.knime.node.parameters.Widget; -import org.knime.node.parameters.widget.choices.ChoicesProvider; +import org.knime.node.parameters.migration.Migration; +import org.knime.node.parameters.updates.ParameterReference; +import org.knime.node.parameters.widget.credentials.Credentials; +import org.knime.node.parameters.widget.credentials.CredentialsWidget; /** * Implementation of {@link CredentialsSettings} to supply the ID and secret of * a confidential OAuth2 app. * + * Any instance must be annotated with a {@code ValueReference} to {@link Ref} + * for the validation to work. + * + * * @author Bjoern Lohrmann, KNIME GmbH */ @SuppressWarnings("restriction") public class BoxAppSettings implements CredentialsSettings { /** - * The name of the Credentials flow variable. + * The credential */ - @Widget(title = "App ID and secret (flow variable)", // + @Widget(title = "App ID and secret", // description = """ - Specifies a credentials flow variable with the app/client ID and secret of the custom Box app. + Specifies the app/client ID and secret of the custom Box app. These fields can be found in the configuration settings of your custom Box app. """) - @ChoicesProvider(CredentialsFlowVarChoicesProvider.class) - public String m_flowVariable; + @Migration(CredentialVariableMigration.class) + @CustomValidation(BoxCredentialValidator.class) + @CredentialsWidget(usernameLabel = "ID", passwordLabel = "Secret") // NOSONAR not a password + + public LegacyCredentials m_secret = new LegacyCredentials(new Credentials()); + + /** + * Used to reference this class for the validation. + */ + public interface Ref extends ParameterReference { + } + + static final class BoxCredentialValidator extends AbstractCredentialsValidator { + BoxCredentialValidator() { + super(Ref.class); + } + } @Override - public String flowVariableName() { - return m_flowVariable; + public LegacyCredentials getCredential() { + return m_secret; } /** - * If a flow variable has been specified, this method validates that a username - * and password are present in the flow variable. This method should be used - * during the configure phase, to reduce logspam if the credentials flow - * variable is not there yet. + * If a credential has been specified, this method validates that a username and + * password are present. This method should be used during the configure phase, + * to reduce logspam if the credential is not there yet. * * @param credsProvider - * Used to access the flow variable. + * Used to access the credential. * @throws InvalidSettingsException - * when username or password was not present in the flow variable. + * when username or password was not present in the credential. */ + @Override public void validateOnConfigure(final CredentialsProvider credsProvider) throws InvalidSettingsException { if (retrieve(credsProvider).isPresent()) { validateLogin(credsProvider, "Client/App ID is required"); @@ -98,16 +123,15 @@ public void validateOnConfigure(final CredentialsProvider credsProvider) throws } /** - * This method validates both presence and validity of a credentials flow - * variable. This method should be used during the execute phase. + * This method validates both presence and validity of a credential. This method + * should be used during the execute phase. * * @param credsProvider - * Used to access the flow variable. + * Used to access the credential. * @throws InvalidSettingsException - * when flow variable was not present or invalid. + * when credential was not present or invalid. */ public void validateOnExecute(final CredentialsProvider credsProvider) throws InvalidSettingsException { - validateFlowVariable(credsProvider); validateLogin(credsProvider, "Client/App ID is required"); validateSecret(credsProvider, "Client/App secret is required"); } diff --git a/org.knime.ext.box.authenticator/src-deprecated/org/knime/ext/box/authenticator/node/BoxAuthenticatorSettings.java b/org.knime.ext.box.authenticator/src-deprecated/org/knime/ext/box/authenticator/node/BoxAuthenticatorSettings.java index 26f3846f..0aa8cdb1 100644 --- a/org.knime.ext.box.authenticator/src-deprecated/org/knime/ext/box/authenticator/node/BoxAuthenticatorSettings.java +++ b/org.knime.ext.box.authenticator/src-deprecated/org/knime/ext/box/authenticator/node/BoxAuthenticatorSettings.java @@ -136,6 +136,7 @@ public EffectPredicate init(final PredicateInitializer i) { } @Layout(BoxAppSection.class) + @ValueReference(BoxAppSettings.Ref.class) BoxAppSettings m_boxApp = new BoxAppSettings(); @Widget(title = "Type", description = "Authentication method to use.")