Skip to content

Commit cc23f6e

Browse files
committed
Fix class definition registrations with the same class id
When class definitions for two different classes with the same class id but different factory ids are registered, we were throwing an error indicating that there are duplicate registrations. However, we should allow such cases. Apart from the fix for that, the PR also includes a test case for null portable serialization. Closes #199
1 parent 2292776 commit cc23f6e

File tree

3 files changed

+133
-13
lines changed

3 files changed

+133
-13
lines changed

hazelcast/serialization/portable/classdef.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,8 @@ def build(self):
225225

226226
def _add_field_by_type(self, field_name, field_type, version, factory_id=0, class_id=0):
227227
self._check()
228-
self._field_defs.append(FieldDefinition(self._index, field_name, field_type, version, factory_id, class_id))
228+
fd = FieldDefinition(self._index, field_name, field_type, version, factory_id, class_id)
229+
self._field_defs.append(fd)
229230
self._index += 1
230231

231232
def _check(self):

hazelcast/serialization/service.py

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -78,24 +78,40 @@ def _register_constant_serializers(self):
7878
self._registry.safe_register_serializer(self._registry._python_serializer)
7979

8080
def register_class_definitions(self, class_definitions, check_error):
81-
class_defs = dict()
81+
factories = dict()
8282
for cd in class_definitions:
83-
if cd in class_defs:
84-
raise HazelcastSerializationError("Duplicate registration found for class-id: %s" % cd.class_id)
85-
class_defs[cd.class_id] = cd
83+
factory_id = cd.factory_id
84+
class_defs = factories.get(factory_id, None)
85+
if class_defs is None:
86+
class_defs = dict()
87+
factories[factory_id] = class_defs
88+
89+
class_id = cd.class_id
90+
if class_id in class_defs:
91+
raise HazelcastSerializationError("Duplicate registration found for class-id: %s" % class_id)
92+
class_defs[class_id] = cd
93+
8694
for cd in class_definitions:
87-
self.register_class_definition(cd, class_defs, check_error)
95+
self.register_class_definition(cd, factories, check_error)
8896

89-
def register_class_definition(self, cd, class_defs, check_error):
97+
def register_class_definition(self, cd, factories, check_error):
9098
field_names = cd.get_field_names()
9199
for field_name in field_names:
92100
fd = cd.get_field(field_name)
93101
if fd.field_type == FieldType.PORTABLE or fd.field_type == FieldType.PORTABLE_ARRAY:
94-
nested_cd = class_defs.get(fd.class_id, None)
95-
if nested_cd is not None:
96-
self.register_class_definition(nested_cd, class_defs, check_error)
97-
self._portable_context.register_class_definition(nested_cd)
98-
elif check_error:
102+
factory_id = fd.factory_id
103+
class_id = fd.class_id
104+
class_defs = factories.get(factory_id, None)
105+
if class_defs is not None:
106+
nested_cd = class_defs.get(class_id, None)
107+
if nested_cd is not None:
108+
self.register_class_definition(nested_cd, factories, check_error)
109+
self._portable_context.register_class_definition(nested_cd)
110+
continue
111+
112+
if check_error:
99113
raise HazelcastSerializationError(
100-
"Could not find registered ClassDefinition for class-id: %s" % fd.class_id)
114+
"Could not find registered ClassDefinition for factory-id: %s, class-id: %s"
115+
% (factory_id, class_id))
116+
101117
self._portable_context.register_class_definition(cd)

tests/serialization/portable_test.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,51 @@ def create_portable():
231231
the_factory = {SerializationV1Portable.CLASS_ID: SerializationV1Portable, InnerPortable.CLASS_ID: InnerPortable}
232232

233233

234+
class MyPortable1(Portable):
235+
def __init__(self, str_field=None):
236+
self.str_field = str_field
237+
238+
def write_portable(self, writer):
239+
writer.write_utf("str_field", self.str_field)
240+
241+
def read_portable(self, reader):
242+
self.str_field = reader.read_utf("str_field")
243+
244+
def get_factory_id(self):
245+
return 1
246+
247+
def get_class_id(self):
248+
return 1
249+
250+
def __eq__(self, other):
251+
return isinstance(other, MyPortable1) and self.str_field == other.str_field
252+
253+
def __ne__(self, other):
254+
return not self.__eq__(other)
255+
256+
257+
class MyPortable2(Portable):
258+
def __init__(self, int_field=0):
259+
self.int_field = int_field
260+
261+
def write_portable(self, writer):
262+
writer.write_int("int_field", self.int_field)
263+
264+
def read_portable(self, reader):
265+
self.int_field = reader.read_int("int_field")
266+
267+
def get_factory_id(self):
268+
return 2
269+
270+
def get_class_id(self):
271+
return 1
272+
273+
def __eq__(self, other):
274+
return isinstance(other, MyPortable2) and self.int_field == other.int_field
275+
276+
def __ne__(self, other):
277+
return not self.__eq__(other)
278+
234279
class PortableSerializationTestCase(unittest.TestCase):
235280
def test_encode_decode(self):
236281
config = _Config()
@@ -370,3 +415,61 @@ def test_nested_portable_serialization(self):
370415
data = ss1.to_data(p)
371416

372417
self.assertEqual(p, ss2.to_object(data))
418+
419+
def test_nested_null_portable_serialization(self):
420+
config = _Config()
421+
422+
config.portable_factories = {
423+
1: {
424+
1: Parent,
425+
2: Child
426+
}
427+
}
428+
429+
child_class_def = ClassDefinitionBuilder(FACTORY_ID, 2).add_utf_field("name").build()
430+
parent_class_def = ClassDefinitionBuilder(FACTORY_ID, 1).add_portable_field("child", child_class_def).build()
431+
432+
config.class_definitions = [child_class_def, parent_class_def]
433+
434+
ss = SerializationServiceV1(config)
435+
436+
p = Parent(None)
437+
data = ss.to_data(p)
438+
439+
self.assertEqual(p, ss.to_object(data))
440+
441+
def test_duplicate_class_definition(self):
442+
config = _Config()
443+
444+
class_def1 = ClassDefinitionBuilder(1, 1).add_utf_field("str_field").build()
445+
class_def2 = ClassDefinitionBuilder(1, 1).add_int_field("int_field").build()
446+
447+
config.class_definitions = [class_def1, class_def2]
448+
449+
with self.assertRaises(HazelcastSerializationError):
450+
SerializationServiceV1(config)
451+
452+
def test_classes_with_same_class_id_in_different_factories(self):
453+
config = _Config()
454+
config.portable_factories = {
455+
1: {
456+
1: MyPortable1
457+
},
458+
2: {
459+
1: MyPortable2
460+
}
461+
}
462+
463+
class_def1 = ClassDefinitionBuilder(1, 1).add_utf_field("str_field").build()
464+
class_def2 = ClassDefinitionBuilder(2, 1).add_int_field("int_field").build()
465+
466+
config.class_definitions = [class_def1, class_def2]
467+
ss = SerializationServiceV1(config)
468+
469+
portable1 = MyPortable1("test")
470+
data1 = ss.to_data(portable1)
471+
self.assertEqual(portable1, ss.to_object(data1))
472+
473+
portable2 = MyPortable2(1)
474+
data2 = ss.to_data(portable2)
475+
self.assertEqual(portable2, ss.to_object(data2))

0 commit comments

Comments
 (0)