Skip to content

Commit 8782a4f

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.
1 parent ebaf1a5 commit 8782a4f

File tree

3 files changed

+144
-19
lines changed

3 files changed

+144
-19
lines changed

hazelcast/serialization/portable/classdef.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,16 +98,16 @@ def set_version_if_not_set(self, version):
9898

9999
def __eq__(self, other):
100100
return isinstance(other, self.__class__) and (self.factory_id, self.class_id, self.version, self.field_defs) == \
101-
(other.factory_id, other.class_id, other.version, other.field_defs)
101+
(other.factory_id, other.class_id, other.version, other.field_defs)
102102

103103
def __ne__(self, other):
104104
return not self.__eq__(other)
105105

106106
def __repr__(self):
107-
return "fid:{}, cid:{}, v:{}, fields:{}".format(self.factory_id, self.class_id, self.version, self.field_defs)
107+
return "fid:%s, cid:%s, v:%s, fields:%s" % (self.factory_id, self.class_id, self.version, self.field_defs)
108108

109109
def __hash__(self):
110-
return id(self)//16
110+
return hash((self.factory_id, self.class_id, self.version))
111111

112112

113113
class ClassDefinitionBuilder(object):
@@ -223,7 +223,8 @@ def build(self):
223223

224224
def _add_field_by_type(self, field_name, field_type, version, factory_id=0, class_id=0):
225225
self._check()
226-
self._field_defs.append(FieldDefinition(self._index, field_name, field_type, version, factory_id, class_id))
226+
fd = FieldDefinition(self._index, field_name, field_type, version, factory_id, class_id)
227+
self._field_defs.append(fd)
227228
self._index += 1
228229

229230
def _check(self):

hazelcast/serialization/service.py

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,17 @@ def default_partition_strategy(key):
1818

1919
class SerializationServiceV1(BaseSerializationService):
2020

21-
def __init__(self, serialization_config, properties=ClientProperties({}), version=1, global_partition_strategy=default_partition_strategy,
21+
def __init__(self, serialization_config, properties=ClientProperties({}), version=1,
22+
global_partition_strategy=default_partition_strategy,
2223
output_buffer_size=DEFAULT_OUT_BUFFER_SIZE):
2324
super(SerializationServiceV1, self).__init__(version, global_partition_strategy, output_buffer_size,
2425
serialization_config.is_big_endian,
2526
serialization_config.default_integer_type)
2627
self._portable_context = PortableContext(self, serialization_config.portable_version)
27-
self.register_class_definitions(serialization_config.class_definitions, serialization_config.check_class_def_errors)
28-
self._registry._portable_serializer = PortableSerializer(self._portable_context, serialization_config.portable_factories)
28+
self.register_class_definitions(serialization_config.class_definitions,
29+
serialization_config.check_class_def_errors)
30+
self._registry._portable_serializer = PortableSerializer(self._portable_context,
31+
serialization_config.portable_factories)
2932

3033
# merge configured factories with built in ones
3134
factories = {}
@@ -80,24 +83,40 @@ def _register_constant_serializers(self):
8083
self._registry.safe_register_serializer(self._registry._python_serializer)
8184

8285
def register_class_definitions(self, class_definitions, check_error):
83-
class_defs = dict()
86+
factories = dict()
8487
for cd in class_definitions:
85-
if cd in class_defs:
86-
raise HazelcastSerializationError("Duplicate registration found for class-id:{}".format(cd.class_id))
87-
class_defs[cd.class_id] = cd
88+
factory_id = cd.factory_id
89+
class_defs = factories.get(factory_id, None)
90+
if class_defs is None:
91+
class_defs = dict()
92+
factories[factory_id] = class_defs
93+
94+
class_id = cd.class_id
95+
if class_id in class_defs:
96+
raise HazelcastSerializationError("Duplicate registration found for class-id: %s" % class_id)
97+
class_defs[class_id] = cd
98+
8899
for cd in class_definitions:
89-
self.register_class_definition(cd, class_defs, check_error)
100+
self.register_class_definition(cd, factories, check_error)
90101

91-
def register_class_definition(self, cd, class_defs, check_error):
102+
def register_class_definition(self, cd, factories, check_error):
92103
field_names = cd.get_field_names()
93104
for field_name in field_names:
94105
fd = cd.get_field(field_name)
95106
if fd.field_type == FieldType.PORTABLE or fd.field_type == FieldType.PORTABLE_ARRAY:
96-
nested_cd = class_defs.get(fd.class_id, None)
97-
if nested_cd is not None:
98-
self.register_class_definition(nested_cd, class_defs, check_error)
99-
self._portable_context.register_class_definition(nested_cd)
100-
elif check_error:
107+
factory_id = fd.factory_id
108+
class_id = fd.class_id
109+
class_defs = factories.get(factory_id, None)
110+
if class_defs is not None:
111+
nested_cd = class_defs.get(class_id, None)
112+
if nested_cd is not None:
113+
self.register_class_definition(nested_cd, factories, check_error)
114+
self._portable_context.register_class_definition(nested_cd)
115+
continue
116+
117+
if check_error:
101118
raise HazelcastSerializationError(
102-
"Could not find registered ClassDefinition for class-id:{}".format(fd.class_id))
119+
"Could not find registered ClassDefinition for factory-id: %s, class-id: %s"
120+
% (factory_id, class_id))
121+
103122
self._portable_context.register_class_definition(cd)

tests/serialization/portable_test.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,52 @@ def create_portable():
232232
the_factory = {SerializationV1Portable.CLASS_ID: SerializationV1Portable, InnerPortable.CLASS_ID: InnerPortable}
233233

234234

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

356402
self.assertEqual(p, ss2.to_object(data))
403+
404+
def test_nested_null_portable_serialization(self):
405+
serialization_config = hazelcast.SerializationConfig()
406+
407+
serialization_config.portable_factories = {
408+
1: {
409+
1: Parent,
410+
2: Child
411+
}
412+
}
413+
414+
child_class_def = ClassDefinitionBuilder(FACTORY_ID, 2).add_utf_field("name").build()
415+
parent_class_def = ClassDefinitionBuilder(FACTORY_ID, 1).add_portable_field("child", child_class_def).build()
416+
417+
serialization_config.class_definitions = [child_class_def, parent_class_def]
418+
419+
ss = SerializationServiceV1(serialization_config)
420+
421+
p = Parent(None)
422+
data = ss.to_data(p)
423+
424+
self.assertEqual(p, ss.to_object(data))
425+
426+
def test_duplicate_class_definition(self):
427+
serialization_config = hazelcast.SerializationConfig()
428+
429+
class_def1 = ClassDefinitionBuilder(1, 1).add_utf_field("str_field").build()
430+
class_def2 = ClassDefinitionBuilder(1, 1).add_int_field("int_field").build()
431+
432+
serialization_config.class_definitions = [class_def1, class_def2]
433+
434+
with self.assertRaises(HazelcastSerializationError):
435+
SerializationServiceV1(serialization_config)
436+
437+
def test_classes_with_same_class_id_in_different_factories(self):
438+
serialization_config = hazelcast.SerializationConfig()
439+
440+
serialization_config.portable_factories = {
441+
1: {
442+
1: MyPortable1
443+
},
444+
2: {
445+
1: MyPortable2
446+
}
447+
}
448+
449+
class_def1 = ClassDefinitionBuilder(1, 1).add_utf_field("str_field").build()
450+
class_def2 = ClassDefinitionBuilder(2, 1).add_int_field("int_field").build()
451+
452+
serialization_config.class_definitions = [class_def1, class_def2]
453+
ss = SerializationServiceV1(serialization_config)
454+
455+
portable1 = MyPortable1("test")
456+
data1 = ss.to_data(portable1)
457+
self.assertEqual(portable1, ss.to_object(data1))
458+
459+
portable2 = MyPortable2(1)
460+
data2 = ss.to_data(portable2)
461+
self.assertEqual(portable2, ss.to_object(data2))

0 commit comments

Comments
 (0)