NoCalssDefFoundError PersistentRecord

I am trying to create an RPC in my module that allows CRUD of persistent records in a client component.

I have a simple PersistentRecord class KeyValueRecord.

package com.mymodule.common;

import com.inductiveautomation.ignition.gateway.localdb.persistence.*;
import simpleorm.dataset.SFieldFlags;

public class KeyValueRecord extends PersistentRecord {
    public static final RecordMeta<KeyValueRecord> META = new RecordMeta<KeyValueRecord>(KeyValueRecord.class,"KeyValue");
    public static final IdentityField Id = new IdentityField(META);
    public static final StringField Key = new StringField(META, "Key", SFieldFlags.SMANDATORY, SFieldFlags.SDESCRIPTIVE);
    public static final StringField Value = new StringField(META, "Value", SFieldFlags.SMANDATORY, SFieldFlags.SDESCRIPTIVE);

    public RecordMeta<KeyValueRecord> getMeta() {
      return META;
    }

    public Long getId(){
        return getLong(Id);
    }

    public String getKey(){
        return getString(Key);
    }

    public void setKey(String key){
        setString(Key, key);
    }

    public String getValue(){
        return getString(Value);
    }

    public void setValue(String value){
        setString(Value, value);
    }

}

I already have the following in my module rpc implementation

public class ModuleRPCImpl implements ModuleRPC {
  private GatewayContext context;

  public ModuleRPCImpl(GatewayContext context) {
    this.context = context;
  }

  public Boolean create(String key, String value) {
    try {

      KeyValueRecord r = this.context.getPersistenceInterface().createNew(KeyValueRecord. META);
      r.setKey(key);
      r.setValue(value);
      this.context.getPersistenceInterface().save(r);
      return true;

    } catch (Exception e) {

      return false;

    }

  }

  public List<KeyValueRecord> list() {
    SQuery<KeyValueRecord> query = new SQuery<KeyValueRecord>(KeyValueRecord.META);
    List<KeyValueRecord> results = this.context.getPersistenceInterface().query(query);

    return results;
  }
}

It appears when i call the create function in my client scope, it is working. There are no exceptions and it is returning true. However, when I call my list function I am met with this error:

Exception in thread "AWT-EventQueue-0" java.lang.NoClassDefFoundError: com/inductiveautomation/ignition/gateway/localdb/persistence/PersistentRecord
	at java.lang.ClassLoader.defineClass1(Native Method)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
	at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
	at java.net.URLClassLoader.defineClass(URLClassLoader.java:467)
	at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
	at java.net.URLClassLoader$1.run(URLClassLoader.java:368)
	at java.net.URLClassLoader$1.run(URLClassLoader.java:362)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	at java.lang.Class.forName0(Native Method)
	at java.lang.Class.forName(Class.java:348)
	at java.io.ObjectInputStream.resolveClass(ObjectInputStream.java:682)
	at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1859)
	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1745)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2033)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1567)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:427)
	at java.util.ArrayList.readObject(ArrayList.java:797)
	at sun.reflect.GeneratedMethodAccessor3.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1158)
	at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2169)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2060)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1567)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:427)
	at com.inductiveautomation.ignition.common.Base64.decodeToObjectFragile(Base64.java:964)
	at com.inductiveautomation.ignition.common.Base64.decodeToObjectFragile(Base64.java:942)
	at com.inductiveautomation.ignition.client.gateway_interface.ResponseParser.endElement(ResponseParser.java:155)
	at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.endElement(AbstractSAXParser.java:609)
	at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanEndElement(XMLDocumentFragmentScannerImpl.java:1782)
	at

The only place I am referencing PersistentRecord is in my KeyValueRecord class definition. What am I missing?

Your package spec implies that your KeyValueRecord class is to be used in client and designer scope. This is not allowed. Type parameters are elided from the common classfile so it only fails when trying to deserialize the list in the client/designer.

You must use a key/value class that doesn’t extend PersistentRecord for the actual response from list(). In fact, including any gateway class in a common module is a recipe for failure. “gateway” in the package name is a good hint you won’t find it elsewhere.

1 Like

Thanks for the reply, makes sense. I’ll move my KeyValueRecord to the gateway scope, and serialize it to something else before passing it back to the client.

With your help, I can now create persistent records from a client which is really cool. Thanks!

Now I am trying to query these persistent records through the RPC and display them in a JTable, but I am running into a seemingly similar serialization error:

I have tried these functions in my ModuleRPCImpl

private List<KeyValueRecord> list() {
    SQuery<KeyValueRecord> query = new SQuery<KeyValueRecord>(KeyValueRecord.META);
    List<KeyValueRecord> results = this.context.getPersistenceInterface().query(query);

    return results;
  }

public DefaultTableModel tableModel() {
    List<KeyValueRecord> raw = list();
    DefaultTableModel model = new DefaultTableModel(new String[]{"Key", "Value"}, 0);

    int index = 0;
    for(KeyValueRecord record :raw) {
      String key = record.getKey();
      String value = record.getValue();
      model.addRow(new Object[]{key, value});

      index++;
    }

    return model;
  }

  public Object[][] tableData() {
    List<KeyValueRecord> raw = list();
    Object[][] tableData = new Object[raw.size()][2];

    int index = 0;
    for(KeyValueRecord record :raw) {
      tableData[index][0] = record.getKey();
      tableData[index][1] = record.getValue();

      index++;
    }

    return tableData;
  }

and in the client scope

Object[][] data = moduleRPC.tableData();
DefaultTableModel model = moduleRPC.tableModel();

However I am met with:

SerializationException: Error deserializing element "<c"
	caused by NullPointerException

and can’t open the window my component is on…

What is strange is if I but this code outside of where I’m initializing my components and put it in a button press script (a refresh button) these calls work and I can iterate through the data or call something like getColumnName on the table model.

Do you have any input on what might be causing this? Thank you.

Don’t construct your TableModel in your RPC class. It’s not supplying anything that you can’t get from tableData() plus the column name constants. TableModel is intrinsically a GUI class, so I would not use it in the gateway.

What are acceptable return types from a module RPC? I get the same error when returning List<HashMap<String, String>>.

Sorry for the newbie questions…

Every actual class and every field within (public or private) must implement Serializable. Which actual class is your List<> ? (HashMap and String are Serializable.)

BTW, your Object[][] from tableData() contains all of your keys and values. Simply generate your table model in the client from that.

So I think I figured it out, in my component constructor my ModuleRPCFactory function was being called after my initComponents(); and addComponents(); script :rolling_eyes:

Thanks for your help.