I have a problem with Apache Ignite Serialization/Deserialization related to the order in which fields are deserialized. I need to put an instance of "B" as follow in the Ignite cache:
public class A {
private final String name;
public A(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public class B extends A {
private Map<B, String> mapOfB;
public B(String name) {
super(name);
mapOfB = new HashMap<>();
}
public void addB(B newB, String someString) {
mapOfB.put(newB, someString);
}
public Map<B, String> getMap() {
return mapOfB;
}
@Override
public boolean equals(Object obj) {
if( obj != null && obj instanceof B) {
if(this.getName() == null && ((B) obj).getName() == null && this == obj) {
return true;
} else if(this.getName().equals(((B) obj).getName())) {
return true;
}
}
return false;
}
@Override
public int hashCode() {
return this.getName()==null? System.identityHashCode(this):this.getName().hashCode();
}
}
The if I run the following code:
public static void main(String[] args) {
// write your code here
B b1 = new B("first");
b1.addB(b1, "some first string");
B b2 = new B("second");
b1.addB(b2, "some second string");
// init Ignite configuration
// force java.util.Hashtable to be binary serialized,
// it prevents infinite recursion and other problems
// occurring with the Optimized Serializer
IgniteConfiguration cfg = new IgniteConfiguration();
BinaryConfiguration binConf = new BinaryConfiguration();
Collection<String> binClassNames = new LinkedList<>();
binClassNames.add("java.util.Hashtable");
binConf.setClassNames(binClassNames);
cfg.setBinaryConfiguration(binConf);
Ignition.start(cfg);
// put b1 in cache
IgniteCache cache = Ignition.ignite().getOrCreateCache("MyCache");
cache.put(b1.hashCode(), b1);
//get b1 from cache
B b1FromCache= (B) cache.get(b1.hashCode());
// print map values
System.out.println("b1 map value: " + b1.getMap().get(b1));
System.out.println("b1 from cache map value: " + b1FromCache.getMap().get(b1));
}
Output is
b1 map value: some first string
b1 from cache map value: null
The problem is that fields from the child are deserialized before fields from the parent, so when Ignite deserializes B, it first creates an empty B object (with null "name" and "mapOfB"), then it tries to deserialize mapOfB. It creates the Hashtable and then deserialize each object it contains to fill it.
For b2 in the example above there is no problem as no reference to b2 exists yet when it is deserialized, so a new b2 object is created, b2 fields are populated (including the "name" field), then it is added to the Hashmap with correct hash.
For b1 however deserialization started, so the object already exists in Ignit's map of deserialized objects, but with null name (deserialization is in process for b1), and with a hashCode computed with this null name. The Hashtable puts b1 with the hashCode computed at that time, so when we try to find b1 with non null name in the map at the end it cannot be found.
I cannot change A and B classes and the way these objects are created (how the Hashmap is filled,...) so it has to be solved by changing the serialization. Is there a simple way to do that?
Remark: actual code is way more complicated than that, with may classes between actual B and the Hashmap.
b1.addB(new B("first"), "some first string")
but this is super flaky and only works because of howhasCode
andequals
are implemented...B
should not be used as keys of a map since it is not immutable. – Alexandre Dupriez