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
ModelContentobject. To theModeContentyou can add the raw types of Java, such asint,double,String, etc. AlsoDataCellsandsubconfigscan be stored. Subsequently, theNodeSettingscan be written to XML withsaveToXMLand loaded with the static methodloadFromXML. - If you have a
DataTable,DataArrayor 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 voidsaveTo(finalModelContentWO modelContent) { DataCell[] cellArray =newDataCell[m_containedRowIds.size()]; m_containedRowIds.toArray(cellArray); modelContent.addDataCellArray(CFG_KEY_CELLS, cellArray); } /** * Loads the contained row IDs. * * @param modelContent * @throws InvalidSettingsException */public voidloadFrom(finalModelContentRO modelContent)throwsInvalidSettingsException{ 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 voidsaveInternals(finalFile internDir,finalExecutionMonitor exec)throwsIOException, CanceledExecutionException { // create the main model content ModelContent modelContent =newModelContent(INTERNAL_MODEL);for(inti = 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 =newFile(internDir, FILE_NAME); FileOutputStream fos =newFileOutputStream(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 voidloadInternals(finalFile internDir,finalExecutionMonitor exec)throwsIOException, CanceledExecutionException { m_bins =newNumericBin[m_numberOfBins.getIntValue()]; File file =newFile(internDir, FILE_NAME); FileInputStream fis =newFileInputStream(file); ModelContentRO modelContent = ModelContent.loadFromXML(fis);try{for(inti = 0; i < m_numberOfBins.getIntValue(); i++) { NumericBin bin =newNumericBin(); ModelContentRO subModelContent = modelContent .getModelContent(NUMERIC_BIN + i); bin.loadFrom(subModelContent); m_bins[i] = bin; } }catch(InvalidSettingsException e) {throw newIOException(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.