Now try the following: execute the node, save, close and re-open the workflow. Your node is still executed but when you open the view no data is available. This is because your internal representation, i.e. the bins, is not saved automatically. To ensure that your internal representation can be stored and loaded, the KNIME framework provides two methods in the NodeModel
: loadInternals
and saveInternals
. The following explains how to implement these methods for our numeric binner node.
- Create a
ModelContent
object. To theModeContent
you can add the raw types of Java, such asint
,double
,String
, etc. AlsoDataCells
andsubconfigs
can be stored. Subsequently, theNodeSettings
can be written to XML withsaveToXML
and loaded with the static methodloadFromXML
. - If you have a
DataTable
,DataArray
or similar as your internal model you can use the convenient static method:DataContainer.writeToZip(DataTable yourTable, File zipFile);
- If neither of the above-mentioned methods fit you can serialize your internal model and write it to file.
Since the serialization of objects is slow and prone to errors, we use the ModelContent
approach to store our internal model. Since a numeric bin is not a raw type, it cannot be stored directly by ModelContent
. However, each NumericBin
consists only of raw types. Moreover, it is enough to store the contained row IDs, because the visual representation can be restored from this information: the size of a drawn bin depends on the width and height of the panel (which is evaluated in the paint method) and the number of contained rows. We provide two methods to let each NumericBin
save and load itself from the ModelContent
and afterwards store the ModelContents
in the main ModelContent
in the NodeModel
. First of all each NumericBin
must provide methods to save itself to and load itself from ModelContent
:
/** * Adds the IDs of the contained rows to the settings. This is sufficient in order * to later on restore the visual representation, since that only depends on * the dimension of the panel and the number of contained rows per bin. * * @param modelContent the model content object to save to. */public void
saveTo(final
ModelContentWO modelContent) { DataCell[] cellArray =new
DataCell[m_containedRowIds.size()]; m_containedRowIds.toArray(cellArray); modelContent.addDataCellArray(CFG_KEY_CELLS, cellArray); } /** * Loads the contained row IDs. * * @param modelContent * @throws InvalidSettingsException */public void
loadFrom(final
ModelContentRO modelContent)throws
InvalidSettingsException{ DataCell[] cellArray = modelContent.getDataCellArray(CFG_KEY_CELLS); m_containedRowIds.addAll(Arrays.asList(cellArray)); }
Again, we need an internal key to identify the stored field - in this case the CFG_KEY_CELLS
. It only has to be unique in this class, as each object receives its own ModelContent
object. Now we can save and load our bins in the NodeModel
's saveInternals
and loadInternals
methods. In the saveInternals
we get the directory to store our files. We create a new ModelContent
object and for each bin we create a sub model content which is passed to the bin. The bin itself writes the necessary information in the sub model content. Afterwards we create a new file in the given directory, create an output stream and let the main model content write itself to XML.
/** * * @see org.knime.core.node.NodeModel#saveInternals(java.io.File, * org.knime.core.node.ExecutionMonitor) */protected void
saveInternals(final
File internDir,final
ExecutionMonitor exec)throws
IOException, CanceledExecutionException { // create the main model content ModelContent modelContent =new
ModelContent(INTERNAL_MODEL);for
(int
i = 0; i < m_bins.length; i++) { // for each bin create a sub model content ModelContentWO subContent = modelContent.addModelContent( NUMERIC_BIN + i); // save the bin to the sub model content m_bins[i].saveTo(subContent); } // now all bins are stored to the model content // but the model content must be written to XML // internDir is the directory for this node File file =new
File(internDir, FILE_NAME); FileOutputStream fos =new
FileOutputStream(file); modelContent.saveToXML(fos); }
The loading of the internal model works accordingly. Create the file and an input stream and let the main model content load from the XML file. Then fetch the sub model content for every bin and let each bin load itself from this sub model content. Add the bin to your field.
/** * * @see org.knime.core.node.NodeModel#loadInternals(java.io.File, * org.knime.core.node.ExecutionMonitor) */protected void
loadInternals(final
File internDir,final
ExecutionMonitor exec)throws
IOException, CanceledExecutionException { m_bins =new
NumericBin[m_numberOfBins.getIntValue()]; File file =new
File(internDir, FILE_NAME); FileInputStream fis =new
FileInputStream(file); ModelContentRO modelContent = ModelContent.loadFromXML(fis);try
{for
(int
i = 0; i < m_numberOfBins.getIntValue(); i++) { NumericBin bin =new
NumericBin(); ModelContentRO subModelContent = modelContent .getModelContent(NUMERIC_BIN + i); bin.loadFrom(subModelContent); m_bins[i] = bin; } }catch
(InvalidSettingsException e) {throw new
IOException(e.getMessage()); } }
When you now try again to execute, save, close and re-open the workflow, you will see, that the view displays the desired information.