PortObject[] inData exception during transmit between nodes

Hi, I have one exception when I transmit the my customized portObject from one node(let’s call it Reader) to another node(called Writer) as input. In Writer, it shows the method
protected PortObject execute(final PortObject inData, final ExecutionContext exec)
PortObject inData includes an initialized portObject which differs from the Reader output, since I have put some values into the portObject in Reader.

I debug in Ecli[se and found out some parts I don’t understand:
<1> execute method of NodeModel.class rawOutData is generated
PortObject rawOutData = new PortObject[getNrOutPorts() + 1];
rawOutData[0] = FlowVariablePortObject.INSTANCE;
System.arraycopy(outData, 0, rawOutData, 1, outData.length);
return rawOutData;
It adds one flow Variable connection into the rawOutData in the first place.

<2> execute method in Node.class, it creates newOutData again.
newOutData = Arrays.copyOf(rawOutData, rawOutData.length);

Even until this step, the values in newOutData is the expected portObject.

////////////////////////////////////
after this, I get lost in codes, but anyway, when I try to connect to Writer and execute it with inData[0], it is not expected…

SO any suggestions to find out the cause and solve it??

Regards
Kefang

Maybe this question is too specific, so no reply?? :roll_eyes:

I don’t completely follow what it is you’re doing. I’ve never needed to debug the abstract classes I think you walking through.

Are you finding your the value in the PortObject is not the correct type at execution?

This is how I handle custom ports:

In the constructor of your NodeModel:

super(new PortType[] { MyCustomPortObject.TYPE, BufferedDataTable.TYPE },
			new PortType[] { MyCustomPortObject.TYPE, BufferedDataTable.TYPE, BufferedDataTable.TYPE });

Here I have 1 input port of my own type and 1 of the standard BufferedDataTable.

Then make sure you override the PortObject execute method

protected PortObject[] execute(final PortObject[] inData, final ExecutionContext exec) throws Exception

and configure:

protected PortObjectSpec[] configure(final PortObjectSpec[] inSpecs) throws InvalidSettingsException
1 Like

@swebb Thanks for your reply at first. My explanation might be not good. The problem is, I think, about the transmit between the PortObject from one KNIME node to another KNIME node.
In Reader, I create the a PortObject in execute method by using the
/////////////// m_netPort is the PortObject
protected PortObject execute(final PortObject inData,
final ExecutionContext exec) throws Exception {

return new PortObject {m_netPort};
}
///////////////////
Also set the InSpec in the configure method
////////////////////
protected PortObjectSpec configure(final PortObjectSpec inSpecs)
throws InvalidSettingsException {
m_netPort = new PetriNetPortObject((PetriNetPortObjectSpec) inSpecs[0], m_fileName);
PetriNetPortObjectSpec spec = (PetriNetPortObjectSpec) m_netPort.getSpec();
return new PortObjectSpec{spec};
}

But I found out in Writer KNIME Node, I get the portObject named as pnObj in execute method
///////////////////////////
protected PortObject execute(final PortObject inData, final ExecutionContext exec) throws Exception {
PetriNetPortObject pnObj = (PetriNetPortObject) inData[0];
}
/////////////////////////
pnObj is not generated by Reader in execute method but just by default constructor.
Then I tackled the code and found the transmit is all right until
////////////////////////////////////////// shown above
<2> execute method in Node.class, it creates newOutData again.
newOutData = Arrays.copyOf(rawOutData, rawOutData.length);
///////////////////////////

I will try to add some standard output and check if they work.
Regards
Kefang

Could you explain what the actual issue is, I’m having trouble working this bit out. The object you make in your reader isn’t making it to the writer?

I believe KNIME copies the objects when you read them so this may be a serialization issue if that is the problem.

  1. Have you implemented the serialization of your port object?
  2. Do you need to create the PortObject to be able to get a PortObjectSpec? You may be better off being able to create the spec without needing to make the object
  3. What’s classes are in your inData array in your Writer node?

m_netPort is my customized port object of class PetriNetPortObject. It is generated by reading a file in Reader and should be passed as InData in Writer.
For the check list, you are probably right.

  1. serialization, I haven’t…
    doubt about it. Because the KNIME Writer is going to write the m_netPort object into file, I see this is serialization.
  2. PortObjectSpec . It is created in the PortObject and refered by getPortSpec() method in portObject.
    Doubt here: I don’t really use the PortObjectSpec in my code, main use in my code is to check if the port is compatible between nodes
  3. my inData array has only one element, which should be generated
    //////////////////
    return new PortObject { m_netPort };
    and used in
    //////////////////////
    PetriNetPortObject pnObj = (PetriNetPortObject) inData[0];

Regards

@swebb
finished the test by adding BufferedDataTable, it works fine, so now I will focus on my customized method… And try them out. But still, not really understand the concept of serialization.
If the serialization of port object has written port object into file, it seems to me no need to create a KNIME Writer node to export the port object.

You need to implement serialization of your port so you can save your workflow, close it, open it again and be able to execute the writer node without having to re execute the node that has your port as an output.

So I guess KNIME works in this way. After executing the node, for example A, KNIME stores the object of output port automatically in a temporary file. The other node B which connects node A just reads from the temporary file and then transfers the file into input port object…

So every customized port object needs serialization. Is that right??

Pretty much, it stores it in your workflows directory in your workspace or within the zip if you export the workflow with data.

If you want it to work properly yes.

Roughly you need to do the following:

  1. Create a concrete class extending PortObjectSerializer
  2. Implement the #savePortObject and #loadPortObject methods
  3. Add the serializer to the org.knime.core.PortType extension point you have for your custom port
  4. Do the same for your PortObjectSpec but extending PortObjectSpecSerializer this time

wish one example code about the customized port object, the connection of its object serialization and its Reader, Writer KNIME node…:thinking:

Unfortunately none of the nodes I’ve released have custom port types so here are some redacted examples:

  1. Extension point details:

The class names have been removed but you should get the gist.

  1. The package with my classes

image

  1. The serialization implementation

The port I’ve taken this from I convert the Java object in the port to an XML string then serialize this XML string

"
4. Do the same for your PortObjectSpec but extending PortObjectSpecSerializer this time
"
Good to know it, not thought it before. But is it also automatically?? Or I need to refer explicitly in code of PortObject, like??
/////////////////////////////Class :: public class PetriNetPortObjectSerializer extends PortObjectSerializer

public void savePortObject(PetriNetPortObject portObject, PortObjectZipOutputStream out, ExecutionMonitor exec)
throws IOException, CanceledExecutionException {
// TODO save port object into temporary file
out.putNextEntry(new ZipEntry(FILE_NAME));
portObject.save(out, exec);

}

////////////////////////////////////Class PortObject
public void save(PortObjectZipOutputStream out, ExecutionMonitor exec) throws IOException {
// m_spec means the PortObjectSpec for it, and also the same OutputStream out
m_spec.save(out, exec);

}

https://github.com/knime/knime-core <-- you will probably find various other examples here

In your method of the MyCustomPortObjectSerializer.jave
////////////////////////////
@Override
public MyCustomPortObject loadPortObject(PortObjectZipInputStream in, PortObjectSpec spec,
ExecutionMonitor exec) throws IOException, CanceledExecutionException
{
///////////////////////
PortObjectSpec is not used…

checked it and got lost there…found only one pmml as PortObject… That’s also why I proposed here, much simpler examples are better for me to understand.
You help a lot, Thanks very much.

This is a very trivial port and the spec can be calculated from the PortObject so I didn’t bother explicitly handling the PortObjectSpec in the serialization of the PortObject.

#getSpec() in the PortObject is simply (may not be the best way to implement it):

@Override
public PortObjectSpec getSpec()
{
	return new MyCustomPortObjectSpec(m_configuration);
}

I would assume if you use the PortObjectSpec provided in the #loadPortObject you would need a setter for the PortObjectSpec in your PortObject and you’d just do something like

myCustomPortObject.set(spec) in the #loadPortObject method

1 Like

Fix this problem, the reason for it is without serialization. After making serialization right, it works. Thanks a lot. What’s most important to know is:
The connection between nodes does not pass objects, and the whole environment do not save objects. So between nodes the objects are reloaded from temporary file in KNIME workflow directory.

The difference between KNIME Nodes, Reader and Writer, and the methods of save and loadfrom Serializer is only the visibility of saving path. For KNIME Nodes Reader, Writer the paths are visible.

Hope I’m right about it.

1 Like