Client-server communication peculiarities with GWT and App Engine DataNucleus

I just had to fight with a strange exception which got raised after a GWT-RPC call to the App Engine back-end server which had the purpose to persistently store an entity into the data store.
The exception:

com.google.gwt.user.client.rpc.SerializationException: Type 'org.datanucleus.sco.backed.List' was not assignable to 'com.google.gwt.user.client.rpc.IsSerializable' and did not have a custom field serializer.For security purposes, this type will not be serialized.: instance = []
at com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter.serialize(ServerSerializationStreamWriter.java:610)
at com.google.gwt.user.client.rpc.impl.AbstractSerializationStreamWriter.writeObject(AbstractSerializationStreamWriter.java:129)
at com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter$ValueWriter$8.write(ServerSerializationStreamWriter.java:152)
at com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter.serializeValue(ServerSerializationStreamWriter.java:534)
at com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter.serializeClass(ServerSerializationStreamWriter.java:700)
at com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter.serializeImpl(ServerSerializationStreamWriter.java:730)
at com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter.serialize(ServerSerializationStreamWriter.java:612)
at com.google.gwt.user.client.rpc.impl.AbstractSerializationStreamWriter.writeObject(AbstractSerializationStreamWriter.java:129)
at com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter$ValueWriter$8.write(ServerSerializationStreamWriter.java:152)
at com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter.serializeValue(ServerSerializationStreamWriter.java:534)
at com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter.serializeClass(ServerSerializationStreamWriter.java:700)
at com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter.serializeImpl(ServerSerializationStreamWriter.java:730)
at com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter.serialize(ServerSerializationStreamWriter.java:612)
at com.google.gwt.user.client.rpc.impl.AbstractSerializationStreamWriter.writeObject(AbstractSerializationStreamWriter.java:129)
at com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter$ValueWriter$8.write(ServerSerializationStreamWriter.java:152)
at com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter.serializeValue(ServerSerializationStreamWriter.java:534)
at com.google.gwt.user.server.rpc.RPC.encodeResponse(RPC.java:609)
at com.google.gwt.user.server.rpc.RPC.encodeResponseForSuccess(RPC.java:467)
at com.google.gwt.user.server.rpc.RPC.invokeAndEncodeResponse(RPC.java:564)
at com.google.gwt.user.server.rpc.RPC.invokeAndEncodeResponse(RPC.java:544)
at com.google.gwt.user.server.rpc.RPC.invokeAndEncodeResponse(RPC.java:504)

The exception happened during the (de)serialization of the RPC call. But I didn't use any org.datanucleus.sco.backed.List type in my entities which I transfer over the network. The reason however is the persist operation on App Engine. The code is like
@Override
public void saveOrUpdate(SourceCodeItem item) {
PersistenceManager pm = getPersistenceManager();
try {
pm.currentTransaction().begin();
pm.makePersistent(item);
pm.currentTransaction().commit();
} catch (Exception ex) {
if (pm.currentTransaction().isActive())
pm.currentTransaction().rollback();
} finally {
pm.close();
}
}
It takes an object of type SourceCodeItem and makes it persistent by calling makePersistent(...). This call to makePersistent changes the state of the passed object. The SourceCodeItem object has the following structure (cut out details):
@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class SourceCodeItem implements IsSerializable {
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
@Extension(vendorName = "datanucleus", key = "gae.encoded-pk", value = "true")
private String id;

@Persistent
private String title;

...

private List<Label> labels;

public SourceCodeItem() {
...
labels = new ArrayList<Label>();
}
...
}
The highlighted member variable declaration is a list which is not persistent and it gets initialized in the constructor. However after a call to the JDO's makePersistent(..) this list will be instantiated with the above mentioned org.datanucleus.sco.backed.List type. If you don't "detach" the object from JDOs manager it will be transferred with that instance type of the list which cannot be serialized and you'll get the exception I described in the beginning.

Now you have two choices. Change your implementation of saveOrUpdate(SourceCodeItem) and explicitly detach your object. This would result in the following code:
@Override
public SourceCodeItem saveOrUpdate(SourceCodeItem item) {
SourceCodeItem result = null;
PersistenceManager pm = getPersistenceManager();
try {
pm.currentTransaction().begin();
pm.makePersistent(item);
pm.currentTransaction().commit();
result = pm.detachCopy(item);
} catch (Exception ex) {
if (pm.currentTransaction().isActive())
pm.currentTransaction().rollback();
} finally {
pm.close();
}

return result;
}
Alternatively, which is my favorite option is to add the following to your jdoconfig.xml:
<property name="datanucleus.DetachAllOnCommit" value="true"/>
In this way the detaching will be done automatically after a commit and you can leave the code as it was initially. In such a way you'd get the following lifecycle:
Kindle

Comments

0

Your ad here?