Repository: ignite Updated Branches: refs/heads/master ec12824a5 -> 93bf555a9
IGNITE-6627 .NET: Fix serialization of enums within generic collections * Fix EnumEqualityComparer serialization * Fix enum arrays serialization * Fix empty objects missing metadata This closes #2864 Project: http://git-wip-us.apache.org/repos/asf/ignite/repo Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/93bf555a Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/93bf555a Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/93bf555a Branch: refs/heads/master Commit: 93bf555a98c472ff7028a641b32ef5d8ba8df7cd Parents: ec12824 Author: Alexey Popov <tank2.a...@gmail.com> Authored: Tue Oct 17 14:45:42 2017 +0300 Committer: Pavel Tupitsyn <ptupit...@apache.org> Committed: Tue Oct 17 14:45:42 2017 +0300 ---------------------------------------------------------------------- .../Apache.Ignite.Core.Tests.csproj | 2 + .../Serializable/GenericCollectionsTest.cs | 112 +++++++++++++++++++ .../Client/Cache/CacheTest.cs | 76 +++++++++++++ .../Client/Cache/EmptyObject.cs | 54 +++++++++ .../Impl/Binary/BinarySystemHandlers.cs | 16 +-- .../Impl/Binary/BinaryWriter.cs | 7 ++ .../Impl/Binary/SerializableSerializer.cs | 11 +- .../Binary/Structure/BinaryStructureTracker.cs | 12 +- 8 files changed, 274 insertions(+), 16 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ignite/blob/93bf555a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.csproj ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.csproj b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.csproj index ec85ca2..7ec75af 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.csproj +++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.csproj @@ -78,6 +78,7 @@ <Compile Include="Binary\BinaryReaderWriterTest.cs" /> <Compile Include="Binary\BinarySelfTestSimpleName.cs" /> <Compile Include="Binary\EnumsTestOnline.cs" /> + <Compile Include="Binary\Serializable\GenericCollectionsTest.cs" /> <Compile Include="Cache\PersistentStoreTest.cs" /> <Compile Include="Cache\Query\Linq\CacheLinqTest.CompiledQuery.cs" /> <Compile Include="Cache\Query\Linq\CacheLinqTest.DateTime.cs" /> @@ -94,6 +95,7 @@ <Compile Include="Cache\Store\CacheStoreSessionTestSharedFactory.cs" /> <Compile Include="Client\Cache\CacheTest.cs" /> <Compile Include="Client\Cache\CacheTestNoMeta.cs" /> + <Compile Include="Client\Cache\EmptyObject.cs" /> <Compile Include="Client\Cache\ScanQueryTest.cs" /> <Compile Include="Client\Cache\Person.cs" /> <Compile Include="Client\ClientTestBase.cs" /> http://git-wip-us.apache.org/repos/asf/ignite/blob/93bf555a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/Serializable/GenericCollectionsTest.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/Serializable/GenericCollectionsTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/Serializable/GenericCollectionsTest.cs new file mode 100644 index 0000000..cfbe824d --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/Serializable/GenericCollectionsTest.cs @@ -0,0 +1,112 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Apache.Ignite.Core.Tests.Binary.Serializable +{ + using System.Collections.Generic; + using NUnit.Framework; + + /// <summary> + /// Tests Generic collections serializtion/deserialization scenarios. + /// </summary> + public class GenericCollectionsTest + { + /// <summary> + /// Tests Dictionary. + /// </summary> + [Test] + public void TestDictionary() + { + TestCollection(new Dictionary<int, int> {{1, 1}, {2, 2}}); + TestCollection(new Dictionary<ByteEnum, int> {{ByteEnum.One, 1}, {ByteEnum.Two, 2}}); + TestCollection(new Dictionary<IntEnum, int> {{IntEnum.One, 1}, {IntEnum.Two, 2}}); + } + + /// <summary> + /// Tests SortedDictionary. + /// </summary> + [Test] + public void TestSortedDictionary() + { + TestCollection(new SortedDictionary<int, int> {{1, 1}, {2, 2}}); + TestCollection(new SortedDictionary<ByteEnum, int> {{ByteEnum.One, 1}, {ByteEnum.Two, 2}}); + TestCollection(new SortedDictionary<IntEnum, int> {{IntEnum.One, 1}, {IntEnum.Two, 2}}); + } + + /// <summary> + /// Tests List. + /// </summary> + [Test] + public void TestList() + { + TestCollection(new List<int> {1, 2}); + TestCollection(new List<ByteEnum> {ByteEnum.One, ByteEnum.Two}); + TestCollection(new List<IntEnum> {IntEnum.One, IntEnum.Two}); + } + + /// <summary> + /// Tests LinkedList. + /// </summary> + [Test] + public void TestLinkedList() + { + TestCollection(new LinkedList<int>(new List<int> { 1, 2 })); + TestCollection(new LinkedList<ByteEnum>(new List<ByteEnum> {ByteEnum.One, ByteEnum.Two})); + TestCollection(new LinkedList<IntEnum>(new List<IntEnum> {IntEnum.One, IntEnum.Two})); + } + + /// <summary> + /// Tests HashSet. + /// </summary> + [Test] + public void TestHashSet() + { + TestCollection(new HashSet<int> {1, 2}); + TestCollection(new HashSet<ByteEnum> {ByteEnum.One, ByteEnum.Two}); + TestCollection(new HashSet<IntEnum> {IntEnum.One, IntEnum.Two}); + } + + /// <summary> + /// Tests SortedSet. + /// </summary> + [Test] + public void TestSortedSet() + { + TestCollection(new SortedSet<int> {1, 2}); + TestCollection(new SortedSet<ByteEnum> {ByteEnum.One, ByteEnum.Two}); + TestCollection(new SortedSet<IntEnum> {IntEnum.One, IntEnum.Two}); + } + + private static void TestCollection<T>(ICollection<T> collection) + { + var res = TestUtils.SerializeDeserialize(collection); + Assert.AreEqual(collection, res); + } + + private enum ByteEnum : byte + { + One = 1, + Two = 2, + } + + private enum IntEnum + { + One = 1, + Two = 2, + } + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/93bf555a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/CacheTest.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/CacheTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/CacheTest.cs index 083038a..f2dd1de 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/CacheTest.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/CacheTest.cs @@ -68,6 +68,22 @@ namespace Apache.Ignite.Core.Tests.Client.Cache } /// <summary> + /// Tests the cache put / get for Empty object type. + /// </summary> + [Test] + public void TestPutGetEmptyObject() + { + using (var client = GetClient()) + { + var serverCache = GetCache<EmptyObject>(); + var clientCache = client.GetCache<int, EmptyObject>(CacheName); + + serverCache.Put(1, new EmptyObject()); + Assert.IsNotNull(clientCache.Get(1)); + } + } + + /// <summary> /// Tests the cache put / get with user data types. /// </summary> [Test] @@ -116,6 +132,60 @@ namespace Apache.Ignite.Core.Tests.Client.Cache } /// <summary> + /// Tests the cache put / get for Dictionary with Enum keys. + /// </summary> + [Test] + public void TestPutGetDictionary([Values(true, false)] bool compactFooter) + { + var cfg = GetClientConfiguration(); + + cfg.BinaryConfiguration = new BinaryConfiguration + { + CompactFooter = compactFooter + }; + + using (var client = Ignition.StartClient(cfg)) + { + var dict = new Dictionary<ByteEnum, int> { { ByteEnum.One, 1 }, { ByteEnum.Two, 2 } }; + + var serverCache = GetCache<Dictionary<ByteEnum, int>>(); + var clientCache = client.GetCache<int, Dictionary<ByteEnum, int>>(CacheName); + + serverCache.Put(1, dict); + var res = clientCache.Get(1); + + Assert.AreEqual(dict, res); + } + } + + /// <summary> + /// Tests the cache put / get for HashSet with Enum keys. + /// </summary> + [Test] + public void TestPutGetHashSet([Values(true, false)] bool compactFooter) + { + var cfg = GetClientConfiguration(); + + cfg.BinaryConfiguration = new BinaryConfiguration + { + CompactFooter = compactFooter + }; + + using (var client = Ignition.StartClient(cfg)) + { + var hashSet = new HashSet<ByteEnum> { ByteEnum.One, ByteEnum.Two }; + + var serverCache = GetCache<HashSet<ByteEnum>>(); + var clientCache = client.GetCache<int, HashSet<ByteEnum>>(CacheName); + + serverCache.Put(1, hashSet); + var res = clientCache.Get(1); + + Assert.AreEqual(hashSet, res); + } + } + + /// <summary> /// Tests the TryGet method. /// </summary> [Test] @@ -779,5 +849,11 @@ namespace Apache.Ignite.Core.Tests.Client.Cache { public Container Inner; } + + public enum ByteEnum : byte + { + One = 1, + Two = 2, + } } } http://git-wip-us.apache.org/repos/asf/ignite/blob/93bf555a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/EmptyObject.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/EmptyObject.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/EmptyObject.cs new file mode 100644 index 0000000..47db939 --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/Cache/EmptyObject.cs @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Apache.Ignite.Core.Tests.Client.Cache +{ + using System; + using System.Runtime.Serialization; + using NUnit.Framework; + + /// <summary> + /// Object with no fields. + /// </summary> + [Serializable] + public class EmptyObject : ISerializable + { + /// <summary> + /// Initializes a new instance of the EmptyObject class. + /// </summary> + public EmptyObject() + { + // No-op. + } + + /// <summary> + /// Initializes a new instance of the EmptyObject class. + /// </summary> + private EmptyObject(SerializationInfo info, StreamingContext context) + { + Assert.AreEqual(StreamingContextStates.All, context.State); + Assert.IsNull(context.Context); + } + + /** <inheritdoc /> */ + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + Assert.AreEqual(StreamingContextStates.All, context.State); + Assert.IsNull(context.Context); + } + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/93bf555a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinarySystemHandlers.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinarySystemHandlers.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinarySystemHandlers.cs index f55a11f..3f16bc0 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinarySystemHandlers.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinarySystemHandlers.cs @@ -110,9 +110,9 @@ namespace Apache.Ignite.Core.Impl.Binary // 13. Arbitrary dictionary. ReadHandlers[BinaryTypeId.Dictionary] = new BinarySystemReader(ReadDictionary); - - // 14. Enum. - ReadHandlers[BinaryTypeId.ArrayEnum] = new BinarySystemReader(ReadEnumArray); + + // 14. Enum. Should be read as Array, see WriteEnumArray implementation. + ReadHandlers[BinaryTypeId.ArrayEnum] = new BinarySystemReader(ReadArray); } /// <summary> @@ -473,16 +473,6 @@ namespace Apache.Ignite.Core.Impl.Binary ctx.WriteInt(binEnum.EnumValue); } - /** - * <summary>Read enum array.</summary> - */ - private static object ReadEnumArray(BinaryReader ctx, Type type) - { - var elemType = type.GetElementType() ?? typeof(object); - - return BinaryUtils.ReadTypedArray(ctx, true, elemType); - } - /// <summary> /// Reads the array. /// </summary> http://git-wip-us.apache.org/repos/asf/ignite/blob/93bf555a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryWriter.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryWriter.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryWriter.cs index f59f17c..b98ad5f 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryWriter.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryWriter.cs @@ -1493,6 +1493,13 @@ namespace Apache.Ignite.Core.Impl.Binary { Debug.Assert(desc != null); + if (!desc.UserType && (fields == null || fields.Count == 0)) + { + // System types with no fields (most of them) do not need to be sent. + // AffinityKey is an example of system type with metadata. + return; + } + if (_metas == null) { _metas = new Dictionary<int, BinaryType>(1) http://git-wip-us.apache.org/repos/asf/ignite/blob/93bf555a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/SerializableSerializer.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/SerializableSerializer.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/SerializableSerializer.cs index e660cff..80f267a 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/SerializableSerializer.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/SerializableSerializer.cs @@ -304,9 +304,16 @@ namespace Apache.Ignite.Core.Impl.Binary { return new TypeResolver().ResolveType(serInfo.FullTypeName, serInfo.AssemblyName); } - - if (serInfo.ObjectType != serializable.GetType()) + + if (serInfo.ObjectType != serializable.GetType() && + typeof(ISerializable).IsAssignableFrom(serInfo.ObjectType)) { + // serInfo.ObjectType should be ISerializable. There is a known case for generic collections: + // serializable is EnumEqualityComparer : ISerializable + // and serInfo.ObjectType is ObjectEqualityComparer (does not implement ISerializable interface). + // Please read a possible explanation here: + // http://dotnetstudio.blogspot.ru/2012/06/net-35-to-net-40-enum.html + return serInfo.ObjectType; } http://git-wip-us.apache.org/repos/asf/ignite/blob/93bf555a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/Structure/BinaryStructureTracker.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/Structure/BinaryStructureTracker.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/Structure/BinaryStructureTracker.cs index 8f44e00..3517342 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/Structure/BinaryStructureTracker.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/Structure/BinaryStructureTracker.cs @@ -110,11 +110,21 @@ namespace Apache.Ignite.Core.Impl.Binary.Structure var fields = metaHnd.OnObjectWriteFinished(); - // A new schema may be added, but no new fields. + // A new schema may be added, but no new fields. // In this case, we should still call SaveMetadata even if fields are null writer.SaveMetadata(_desc, fields); } } + else + { + // Special case when the object is with no properties. + // Save meta to Marshaller. + writer.Marshaller.GetBinaryTypeHandler(_desc); + + // Save meta to cluster. + writer.SaveMetadata(_desc, null); + return; + } } /// <summary>