diff --git a/modules/core/src/test/java/org/apache/ignite/platform/PlatformDeployServiceTask.java b/modules/core/src/test/java/org/apache/ignite/platform/PlatformDeployServiceTask.java index 4e04131fb212f..cb3f8d7c91024 100644 --- a/modules/core/src/test/java/org/apache/ignite/platform/PlatformDeployServiceTask.java +++ b/modules/core/src/test/java/org/apache/ignite/platform/PlatformDeployServiceTask.java @@ -19,14 +19,17 @@ import java.sql.Timestamp; import java.util.ArrayList; +import java.util.Calendar; import java.util.Collection; import java.util.Collections; +import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.UUID; import org.apache.ignite.Ignite; +import org.apache.ignite.IgniteCache; import org.apache.ignite.IgniteException; import org.apache.ignite.binary.BinaryObject; import org.apache.ignite.cluster.ClusterNode; @@ -44,6 +47,7 @@ import static java.util.Calendar.JANUARY; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; /** @@ -94,6 +98,9 @@ private PlatformDeployServiceJob(String serviceName) { * Test service. */ public static class PlatformTestService implements Service { + @IgniteInstanceResource + private Ignite ignite; + /** */ private boolean isCancelled; @@ -494,6 +501,35 @@ public Map testMap(Map map) { return m; } + /** */ + public void testDateArray(Timestamp[] dates) { + assertNotNull(dates); + assertEquals(2, dates.length); + assertEquals(new Timestamp(new Date(82, Calendar.APRIL, 1, 0, 0, 0).getTime()), dates[0]); + assertEquals(new Timestamp(new Date(91, Calendar.OCTOBER, 1, 0, 0, 0).getTime()), dates[1]); + } + + /** */ + public Timestamp testDate(Timestamp date) { + if (date == null) + return null; + + assertEquals(new Timestamp(new Date(82, Calendar.APRIL, 1, 0, 0, 0).getTime()), date); + + return new Timestamp(new Date(91, Calendar.OCTOBER, 1, 0, 0, 0).getTime()); + } + + /** */ + public void testUTCDateFromCache() { + IgniteCache cache = ignite.cache("net-dates"); + + cache.put(3, new Timestamp(new Date(82, Calendar.APRIL, 1, 0, 0, 0).getTime())); + cache.put(4, new Timestamp(new Date(91, Calendar.OCTOBER, 1, 0, 0, 0).getTime())); + + assertEquals(new Timestamp(new Date(82, Calendar.APRIL, 1, 0, 0, 0).getTime()), cache.get(1)); + assertEquals(new Timestamp(new Date(91, Calendar.OCTOBER, 1, 0, 0, 0).getTime()), cache.get(2)); + } + /** */ public void sleep(long delayMs) { try { diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/IJavaService.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/IJavaService.cs index 8c9e6b6755b3e..dfcaaff51e38b 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/IJavaService.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/IJavaService.cs @@ -176,7 +176,16 @@ public interface IJavaService /** */ IDictionary testMap(IDictionary dict); - + + /** */ + void testDateArray(DateTime?[] dates); + + /** */ + DateTime testDate(DateTime date); + + /** */ + void testUTCDateFromCache(); + /** */ void sleep(long delayMs); } diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/JavaServiceDynamicProxy.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/JavaServiceDynamicProxy.cs index 3276b838500ba..26c06373a44e0 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/JavaServiceDynamicProxy.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/JavaServiceDynamicProxy.cs @@ -331,6 +331,24 @@ public IDictionary testMap(IDictionary dict) return _svc.testMap(dict); } + /** */ + public void testDateArray(DateTime?[] dates) + { + _svc.testDateArray(dates); + } + + /** */ + public DateTime testDate(DateTime date) + { + return _svc.testDate(date); + } + + /** */ + public void testUTCDateFromCache() + { + _svc.testDateFromCache(); + } + /** */ public void sleep(long delayMs) { diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/Model.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/Model.cs index 584511cd06528..0793e874de466 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/Model.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/Model.cs @@ -15,6 +15,7 @@ * limitations under the License. */ +// ReSharper disable once CheckNamespace namespace org.apache.ignite.platform { /// @@ -28,7 +29,7 @@ public class Address /** */ public string Addr { get; set; } } - + /// /// A class is a clone of Java class Department with the same namespace. /// @@ -37,7 +38,7 @@ public class Department /** */ public string Name { get; set; } } - + /// /// A class is a clone of Java class Employee with the same namespace. /// @@ -72,6 +73,7 @@ public override bool Equals(object obj) public override int GetHashCode() { + // ReSharper disable once NonReadonlyMemberInGetHashCode return Id.GetHashCode(); } } diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTest.cs index ee0bdea7417c2..b2748aec0eb0c 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTest.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTest.cs @@ -956,6 +956,23 @@ public void TestCallJavaService() Assert.AreEqual(new[] {11, 12, 13}, binSvc.testBinaryObjectArray(binArr) .Select(x => x.GetField("Field"))); + DateTime dt1 = new DateTime(1982, 4, 1, 0, 0, 0, 0, DateTimeKind.Utc); + DateTime dt2 = new DateTime(1991, 10, 1, 0, 0, 0, 0, DateTimeKind.Utc); + + Assert.AreEqual(dt2, svc.testDate(dt1)); + + svc.testDateArray(new DateTime?[] {dt1, dt2}); + + var cache = Grid1.GetOrCreateCache("net-dates"); + + cache.Put(1, dt1); + cache.Put(2, dt2); + + svc.testUTCDateFromCache(); + + Assert.AreEqual(dt1, cache.Get(3)); + Assert.AreEqual(dt2, cache.Get(4)); + Services.Cancel(javaSvcName); } @@ -1038,6 +1055,21 @@ public void TestCallJavaServiceDynamicProxy() Assert.AreEqual(guid, svc.testNullUUID(guid)); Assert.IsNull(svc.testNullUUID(null)); Assert.AreEqual(guid, svc.testArray(new Guid?[] { guid })[0]); + + DateTime dt1 = new DateTime(1982, 4, 1, 0, 0, 0, 0, DateTimeKind.Utc); + DateTime dt2 = new DateTime(1991, 10, 1, 0, 0, 0, 0, DateTimeKind.Utc); + + Assert.AreEqual(dt2, svc.testDate(dt1)); + + var cache = Grid1.GetOrCreateCache("net-dates"); + + cache.Put(1, dt1); + cache.Put(2, dt2); + + svc.testUTCDateFromCache(); + + Assert.AreEqual(dt1, cache.Get(3)); + Assert.AreEqual(dt2, cache.Get(4)); } /// @@ -1123,7 +1155,8 @@ private IgniteConfiguration GetConfiguration(string springConfigUrl) typeof (PlatformComputeBinarizable), typeof (BinarizableObject)) { - NameMapper = BinaryBasicNameMapper.SimpleNameInstance + NameMapper = BinaryBasicNameMapper.SimpleNameInstance, + ForceTimestamp = true } }; } diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Binary/BinaryConfiguration.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Binary/BinaryConfiguration.cs index 32d2c9d6f5d15..2a2bb9579512e 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Binary/BinaryConfiguration.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Binary/BinaryConfiguration.cs @@ -40,6 +40,11 @@ public class BinaryConfiguration /// public const bool DefaultKeepDeserialized = true; + /// + /// Default setting. + /// + public const bool DefaultForceTimestamp = false; + /** Footer setting. */ private bool? _compactFooter; @@ -49,6 +54,7 @@ public class BinaryConfiguration public BinaryConfiguration() { KeepDeserialized = DefaultKeepDeserialized; + ForceTimestamp = DefaultForceTimestamp; } /// @@ -72,6 +78,7 @@ internal void CopyLocalProperties(BinaryConfiguration cfg) IdMapper = cfg.IdMapper; NameMapper = cfg.NameMapper; KeepDeserialized = cfg.KeepDeserialized; + ForceTimestamp = cfg.ForceTimestamp; if (cfg.Serializer != null) { @@ -106,7 +113,7 @@ public BinaryConfiguration(params Type[] binaryTypes) public ICollection TypeConfigurations { get; set; } /// - /// Gets or sets a collection of assembly-qualified type names + /// Gets or sets a collection of assembly-qualified type names /// (the result of ) for binarizable types. /// /// Shorthand for creating . @@ -137,12 +144,12 @@ public BinaryConfiguration(params Type[] binaryTypes) /// /// Gets or sets a value indicating whether to write footers in compact form. - /// When enabled, Ignite will not write fields metadata when serializing objects, + /// When enabled, Ignite will not write fields metadata when serializing objects, /// because internally metadata is distributed inside cluster. /// This increases serialization performance. /// /// WARNING! This mode should be disabled when already serialized data can be taken from some external - /// sources (e.g.cache store which stores data in binary form, data center replication, etc.). + /// sources (e.g.cache store which stores data in binary form, data center replication, etc.). /// Otherwise binary objects without any associated metadata could could not be deserialized. /// [DefaultValue(DefaultCompactFooter)] @@ -152,6 +159,21 @@ public bool CompactFooter set { _compactFooter = value; } } + /// + /// Gets or sets a value indicating whether all DateTime keys, values and object fields + /// should be written as a Timestamp. + /// + /// Timestamp format is required for values used in SQL and for interoperation with other platforms. + /// Only UTC values are supported in Timestamp format. Other values will cause an exception on write. + /// + /// Normally Ignite serializer uses for DateTime fields, + /// keys and values. + /// This attribute changes the behavior to . + /// + /// See also , . + /// + public bool ForceTimestamp { get; set; } + /// /// Gets the compact footer internal nullable value. /// diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Binary/BinaryReflectiveSerializer.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Binary/BinaryReflectiveSerializer.cs index f9874ba23484d..c97b0e61e0168 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Binary/BinaryReflectiveSerializer.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Binary/BinaryReflectiveSerializer.cs @@ -21,21 +21,21 @@ namespace Apache.Ignite.Core.Binary using Apache.Ignite.Core.Impl.Binary; /// - /// Binary serializer which reflectively writes all fields except of ones with + /// Binary serializer which reflectively writes all fields except of ones with /// . /// - /// Note that Java platform stores dates as a difference between current time - /// and predefined absolute UTC date. Therefore, this difference is always the - /// same for all time zones. .NET, in contrast, stores dates as a difference - /// between current time and some predefined date relative to the current time - /// zone. It means that this difference will be different as you change time zones. - /// To overcome this discrepancy Ignite always converts .Net date to UTC form - /// before serializing and allows user to decide whether to deserialize them - /// in UTC or local form using ReadTimestamp(..., true/false) methods in + /// Note that Java platform stores dates as a difference between current time + /// and predefined absolute UTC date. Therefore, this difference is always the + /// same for all time zones. .NET, in contrast, stores dates as a difference + /// between current time and some predefined date relative to the current time + /// zone. It means that this difference will be different as you change time zones. + /// To overcome this discrepancy Ignite always converts .Net date to UTC form + /// before serializing and allows user to decide whether to deserialize them + /// in UTC or local form using ReadTimestamp(..., true/false) methods in /// and . /// This serializer always read dates in UTC form. It means that if you have /// local date in any field/property, it will be implicitly converted to UTC - /// form after the first serialization-deserialization cycle. + /// form after the first serialization-deserialization cycle. /// public sealed class BinaryReflectiveSerializer : IBinarySerializer { @@ -94,7 +94,7 @@ public bool RawMode /// Normally serializer uses for DateTime fields. /// This attribute changes the behavior to . /// - /// See also . + /// See also , . /// public bool ForceTimestamp { diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/IgniteClientConfigurationSection.xsd b/modules/platforms/dotnet/Apache.Ignite.Core/IgniteClientConfigurationSection.xsd index f78508eb0cfa4..42aa56b691522 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/IgniteClientConfigurationSection.xsd +++ b/modules/platforms/dotnet/Apache.Ignite.Core/IgniteClientConfigurationSection.xsd @@ -185,6 +185,11 @@ Compact footer flag. + + + Force timestamp flag. + + diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/IgniteConfigurationSection.xsd b/modules/platforms/dotnet/Apache.Ignite.Core/IgniteConfigurationSection.xsd index 8bb6f877b9561..4bb0fef2b491c 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/IgniteConfigurationSection.xsd +++ b/modules/platforms/dotnet/Apache.Ignite.Core/IgniteConfigurationSection.xsd @@ -274,6 +274,11 @@ Compact footer flag. + + + Force timestamp flag. + + 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 cae0f5fcf9a50..3ffb1f978b680 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinarySystemHandlers.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinarySystemHandlers.cs @@ -122,9 +122,18 @@ static BinarySystemHandlers() /// Try getting write handler for type. /// /// + /// /// - public static IBinarySystemWriteHandler GetWriteHandler(Type type) + public static IBinarySystemWriteHandler GetWriteHandler(Type type, bool forceTimestamp) { + if (forceTimestamp) + { + if (type == typeof(DateTime)) + return new BinarySystemWriteHandler(WriteTimestamp, false); + if (type == typeof(DateTime?[])) + return new BinarySystemWriteHandler(WriteTimestampArray, true); + } + return WriteHandlers.GetOrAdd(type, t => { return FindWriteHandler(t); @@ -293,6 +302,18 @@ private static void WriteGuid(BinaryWriter ctx, Guid obj) BinaryUtils.WriteGuid(obj, ctx.Stream); } + /// + /// Write Date. + /// + /// Context. + /// Value. + private static void WriteTimestamp(BinaryWriter ctx, DateTime obj) + { + ctx.Stream.WriteByte(BinaryTypeId.Timestamp); + + BinaryUtils.WriteTimestamp(obj, ctx.Stream); + } + /// /// Write boolaen array. /// @@ -425,6 +446,18 @@ private static void WriteGuidArray(BinaryWriter ctx, Guid?[] obj) BinaryUtils.WriteGuidArray(obj, ctx.Stream); } + /// + /// Write nullable Date array. + /// + /// Context. + /// Value. + private static void WriteTimestampArray(BinaryWriter ctx, DateTime?[] obj) + { + ctx.Stream.WriteByte(BinaryTypeId.ArrayTimestamp); + + BinaryUtils.WriteTimestampArray(obj, ctx.Stream); + } + /// /// Writes the enum array. /// 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 d09bfad8585a8..9025b4df2d51d 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryWriter.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryWriter.cs @@ -875,7 +875,7 @@ public void WriteEnum(T val) throw new BinaryObjectException("Type is not an enum: " + type); } - var handler = BinarySystemHandlers.GetWriteHandler(type); + var handler = BinarySystemHandlers.GetWriteHandler(type, _marsh.ForceTimestamp); if (handler != null) { @@ -1180,7 +1180,7 @@ public void Write(T obj) return; // Are we dealing with a well-known type? - var handler = BinarySystemHandlers.GetWriteHandler(type); + var handler = BinarySystemHandlers.GetWriteHandler(type, _marsh.ForceTimestamp); if (handler != null) { diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/Marshaller.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/Marshaller.cs index 4a61cf5eda8d5..cc9aeff992d2f 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/Marshaller.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/Marshaller.cs @@ -136,6 +136,14 @@ public IIgniteInternal Ignite /// public bool RegistrationDisabled { get; set; } + /// + /// Gets force timestamp flag value. + /// + public bool ForceTimestamp + { + get { return _cfg.ForceTimestamp; } + } + /// /// Marshal object. /// @@ -376,7 +384,7 @@ private BinaryTypeHolder GetBinaryTypeHolder(IBinaryTypeDescriptor desc) { return holder; } - + lock (this) { if (!_metas.TryGetValue(desc.TypeId, out holder)) @@ -394,9 +402,9 @@ private BinaryTypeHolder GetBinaryTypeHolder(IBinaryTypeDescriptor desc) return holder; } - + /// - /// Updates or creates cached binary type holder. + /// Updates or creates cached binary type holder. /// private void UpdateOrCreateBinaryTypeHolder(BinaryType meta) { @@ -406,7 +414,7 @@ private void UpdateOrCreateBinaryTypeHolder(BinaryType meta) holder.Merge(meta); return; } - + lock (this) { if (_metas.TryGetValue(meta.TypeId, out holder)) @@ -414,7 +422,7 @@ private void UpdateOrCreateBinaryTypeHolder(BinaryType meta) holder.Merge(meta); return; } - + var metas0 = new Dictionary(_metas); holder = new BinaryTypeHolder(meta.TypeId, meta.TypeName, meta.AffinityKeyFieldName, meta.IsEnum, this); @@ -702,7 +710,10 @@ private static IBinarySerializerInternal GetSerializer(BinaryConfiguration cfg, return new SerializableSerializer(type); } - serializer = new BinaryReflectiveSerializer(); + serializer = new BinaryReflectiveSerializer + { + ForceTimestamp = cfg != null && cfg.ForceTimestamp + }; } var refSerializer = serializer as BinaryReflectiveSerializer; @@ -877,16 +888,16 @@ public void OnClientReconnected(bool clusterRestarted) { if (!clusterRestarted) return; - + // Reset all binary structures. Metadata must be sent again. // _idToDesc enumerator is thread-safe (returns a snapshot). // If there are new descriptors added concurrently, they are fine (we are already connected). - + // Race is possible when serialization is started before reconnect (or even before disconnect) // and finished after reconnect, meta won't be sent to cluster because it is assumed to be known, // but operation will succeed. // We don't support this use case. Users should handle reconnect events properly when cluster is restarted. - // Supporting this very rare use case will complicate the code a lot with little benefit. + // Supporting this very rare use case will complicate the code a lot with little benefit. foreach (var desc in _idToDesc) { desc.Value.ResetWriteStructure(); diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxySerializer.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxySerializer.cs index 66998cbb114fa..16e3c4794712f 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxySerializer.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxySerializer.cs @@ -267,7 +267,7 @@ private static Action GetPlatformArgWriter(Type paramType, return (writer, o) => writer.WriteTimestampArray((DateTime?[]) o); } - var handler = BinarySystemHandlers.GetWriteHandler(type); + var handler = BinarySystemHandlers.GetWriteHandler(type, true); if (handler != null) return null; @@ -281,9 +281,6 @@ private static Action GetPlatformArgWriter(Type paramType, if (arg is ICollection) return (writer, o) => writer.WriteCollection((ICollection) o); - if (arg is DateTime) - return (writer, o) => writer.WriteTimestamp((DateTime) o); - return null; } }