This is an automated email from the ASF dual-hosted git repository.

ptupitsyn pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git


The following commit(s) were added to refs/heads/master by this push:
     new b0c5a15  IGNITE-12823 .NET: Fix service method calls with typed array 
args
b0c5a15 is described below

commit b0c5a15e91a64bf02c9ded5ae4eb051ff6a3215a
Author: Pavel Tupitsyn <[email protected]>
AuthorDate: Fri May 29 23:16:40 2020 +0300

    IGNITE-12823 .NET: Fix service method calls with typed array args
    
    **Bug:** Service calls don't work when one of the parameters is an array 
other than `Object[]` - both .NET -> Java and .NET -> .NET.
    
    **Reason:** array element type is lost when passing service arguments on 
Java side: `GridServiceProxy.invokeMethod()` takes `Object[] args`.
    
    **Fix:** convert `Object[]` to `T[]` right before service invocation by 
looking at the service method signature to determine the array element type.
---
 .../platform/services/PlatformServices.java        |  23 ++-
 .../ignite/platform/PlatformDeployServiceTask.java |  18 ++-
 .../Services/ServicesTest.cs                       | 173 ++++++++++++++++++++-
 .../Impl/Common/DelegateConverter.cs               |  59 +++++--
 4 files changed, 255 insertions(+), 18 deletions(-)

diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/services/PlatformServices.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/services/PlatformServices.java
index 6ad9397..24d473a 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/services/PlatformServices.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/services/PlatformServices.java
@@ -43,6 +43,7 @@ import org.apache.ignite.services.ServiceDeploymentException;
 import org.apache.ignite.services.ServiceDescriptor;
 import org.jetbrains.annotations.NotNull;
 
+import java.lang.reflect.Array;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -516,7 +517,7 @@ public class PlatformServices extends 
PlatformAbstractTarget {
     /**
      * Proxy holder.
      */
-    @SuppressWarnings("unchecked")
+    @SuppressWarnings({"unchecked", "rawtypes"})
     private static class ServiceProxyHolder extends PlatformAbstractTarget {
         /** */
         private final Object proxy;
@@ -581,6 +582,26 @@ public class PlatformServices extends 
PlatformAbstractTarget {
 
                 Method mtd = getMethod(serviceClass, mthdName, args);
 
+                // Convert Object[] to T[] when required:
+                // Ignite loses array item types when passing arguments 
through GridServiceProxy.
+                for (int i = 0; i < args.length; i++) {
+                    Object arg = args[i];
+
+                    if (arg instanceof Object[]) {
+                        Class<?> parameterType = mtd.getParameterTypes()[i];
+
+                        if (parameterType.isArray() && parameterType != 
Object[].class) {
+                            Object[] arr = (Object[])arg;
+                            Object newArg = 
Array.newInstance(parameterType.getComponentType(), arr.length);
+
+                            for (int j = 0; j < arr.length; j++)
+                                Array.set(newArg, j, arr[j]);
+
+                            args[i] = newArg;
+                        }
+                    }
+                }
+
                 try {
                     return ((GridServiceProxy)proxy).invokeMethod(mtd, args);
                 }
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 ce7da5a..6986ae4 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
@@ -364,7 +364,7 @@ public class PlatformDeployServiceTask extends 
ComputeTaskAdapter<String, Object
         }
 
         /** */
-        public Object[] testBinarizableArray(Object[] arg) {
+        public Object[] testBinarizableArrayOfObjects(Object[] arg) {
             if (arg == null)
                 return null;
 
@@ -377,6 +377,22 @@ public class PlatformDeployServiceTask extends 
ComputeTaskAdapter<String, Object
         }
 
         /** */
+        public PlatformComputeBinarizable[] 
testBinarizableArray(PlatformComputeBinarizable[] arg) {
+            return 
(PlatformComputeBinarizable[])testBinarizableArrayOfObjects(arg);
+        }
+
+        /** */
+        public BinaryObject[] testBinaryObjectArray(BinaryObject[] arg) {
+            for (int i = 0; i < arg.length; i++) {
+                int field = arg[i].field("Field");
+
+                arg[i] = arg[i].toBuilder().setField("Field", field + 
1).build();
+            }
+
+            return arg;
+        }
+
+        /** */
         public Collection testBinarizableCollection(Collection arg) {
             if (arg == null)
                 return null;
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 7038c84..aa8f8ea 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTest.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTest.cs
@@ -414,6 +414,63 @@ namespace Apache.Ignite.Core.Tests.Services
         }
 
         /// <summary>
+        /// Test call service proxy from remote node with a methods having an 
array of user types and objects.
+        /// </summary>
+        [Test]
+        public void TestCallServiceProxyWithTypedArrayParameters()
+        {
+            // Deploy to the remote node.
+            var nodeId = Grid2.GetCluster().GetLocalNode().Id;
+
+            var cluster = Grid1.GetCluster().ForNodeIds(nodeId);
+
+            cluster.GetServices().DeployNodeSingleton(SvcName, new 
TestIgniteServiceArraySerializable());
+
+            var typedArray = new[] {10, 11, 12}
+                .Select(x => new PlatformComputeBinarizable {Field = 
x}).ToArray();
+
+            var objArray = typedArray.ToArray<object>();
+
+            // object[]
+            var prx = 
Services.GetServiceProxy<ITestIgniteServiceArray>(SvcName);
+
+            Assert.AreEqual(new[] {11, 12, 13}, 
prx.TestBinarizableArrayOfObjects(objArray)
+                .OfType<PlatformComputeBinarizable>().Select(x => 
x.Field).ToArray());
+
+            Assert.IsNull(prx.TestBinarizableArrayOfObjects(null));
+
+            Assert.IsEmpty(prx.TestBinarizableArrayOfObjects(new object[0]));
+
+            // T[]
+            Assert.AreEqual(new[] {11, 12, 13}, 
prx.TestBinarizableArray(typedArray)
+                  .Select(x => x.Field).ToArray());
+
+            Assert.IsEmpty(prx.TestBinarizableArray(new 
PlatformComputeBinarizable[0]));
+
+            Assert.IsNull(prx.TestBinarizableArray(null));
+
+            // BinaryObject[]
+            var binPrx = cluster.GetServices()
+                .WithKeepBinary()
+                .WithServerKeepBinary()
+                .GetServiceProxy<ITestIgniteServiceArray>(SvcName);
+
+            var res = binPrx.TestBinaryObjectArray(
+                
typedArray.Select(Grid1.GetBinary().ToBinary<IBinaryObject>).ToArray());
+
+            Assert.AreEqual(new[] {11, 12, 13}, res.Select(b => 
b.GetField<int>("Field")));
+
+            // TestBinarizableArray2 has no corresponding class in Java.
+            var typedArray2 = new[] {10, 11, 12}
+                .Select(x => new PlatformComputeBinarizable2 {Field = 
x}).ToArray();
+
+            var actual = prx.TestBinarizableArray2(typedArray2)
+                .Select(x => x.Field).ToArray();
+
+            Assert.AreEqual(new[] {11, 12, 13}, actual);
+        }
+
+        /// <summary>
         /// Tests service descriptors.
         /// </summary>
         [Test]
@@ -859,18 +916,29 @@ namespace Apache.Ignite.Core.Tests.Services
             Assert.AreEqual(7, svc.testBinarizable(new 
PlatformComputeBinarizable {Field = 6}).Field);
 
             // Binary collections
-            var arr = new [] {10, 11, 12}.Select(x => new 
PlatformComputeBinarizable {Field = x}).ToArray<object>();
+            var arr  = new[] {10, 11, 12}.Select(
+                x => new PlatformComputeBinarizable {Field = x}).ToArray();
+            var arrOfObj = arr.ToArray<object>();
+
             Assert.AreEqual(new[] {11, 12, 13}, 
svc.testBinarizableCollection(arr)
-                .OfType<PlatformComputeBinarizable>().Select(x => 
x.Field).ToArray());
-            Assert.AreEqual(new[] {11, 12, 13},
-                
svc.testBinarizableArray(arr).OfType<PlatformComputeBinarizable>().Select(x => 
x.Field).ToArray());
+                .OfType<PlatformComputeBinarizable>().Select(x => x.Field));
+
+            Assert.AreEqual(new[] {11, 12, 13}, 
svc.testBinarizableArrayOfObjects(arrOfObj)
+                .OfType<PlatformComputeBinarizable>().Select(x => x.Field));
+
+            Assert.IsNull(svc.testBinarizableArrayOfObjects(null));
+
+            Assert.AreEqual(new[] {11, 12, 13}, svc.testBinarizableArray(arr)
+                .Select(x => x.Field));
+
+            Assert.IsNull(svc.testBinarizableArray(null));
 
             // Binary object
             Assert.AreEqual(15,
                 binSvc.testBinaryObject(
                     Grid1.GetBinary().ToBinary<IBinaryObject>(new 
PlatformComputeBinarizable {Field = 6}))
                     .GetField<int>("Field"));
-            
+
             DateTime dt = new DateTime(1992, 1, 1, 0, 0, 0, 0, 
DateTimeKind.Utc);
 
             Assert.AreEqual(dt, svc.test(dt));
@@ -885,6 +953,12 @@ namespace Apache.Ignite.Core.Tests.Services
             Assert.IsNull(svc.testNullUUID(null));
             Assert.AreEqual(guid, svc.testArray(new Guid?[] {guid})[0]);
 
+            // Binary object array.
+            var binArr = 
arr.Select(Grid1.GetBinary().ToBinary<IBinaryObject>).ToArray();
+
+            Assert.AreEqual(new[] {11, 12, 13}, 
binSvc.testBinaryObjectArray(binArr)
+                .Select(x => x.GetField<int>("Field")));
+
             Services.Cancel(javaSvcName);
         }
 
@@ -1118,6 +1192,78 @@ namespace Apache.Ignite.Core.Tests.Services
         }
 
         /// <summary>
+        /// Test serializable service with a methods having an array of user 
types and objects.
+        /// </summary>
+        public interface ITestIgniteServiceArray
+        {
+            /** */
+            object[] TestBinarizableArrayOfObjects(object[] x);
+
+            /** */
+            PlatformComputeBinarizable[] 
TestBinarizableArray(PlatformComputeBinarizable[] x);
+
+            /** */
+            IBinaryObject[] TestBinaryObjectArray(IBinaryObject[] x);
+
+            /** Class TestBinarizableArray2 has no an equals class in Java. */
+            PlatformComputeBinarizable2[] 
TestBinarizableArray2(PlatformComputeBinarizable2[] x);
+        }
+
+        /// <summary>
+        /// Test serializable service with a methods having an array of user 
types and objects.
+        /// </summary>
+        [Serializable]
+        private class TestIgniteServiceArraySerializable : 
TestIgniteServiceSerializable, ITestIgniteServiceArray
+        {
+            /** */
+            public object[] TestBinarizableArrayOfObjects(object[] arg)
+            {
+                if (arg == null)
+                    return null;
+
+                for (var i = 0; i < arg.Length; i++)
+                    if (arg[i] != null)
+                        if (arg[i].GetType() == 
typeof(PlatformComputeBinarizable))
+                            arg[i] = new PlatformComputeBinarizable()
+                                {Field = ((PlatformComputeBinarizable) 
arg[i]).Field + 1};
+                        else
+                            arg[i] = new PlatformComputeBinarizable2()
+                                {Field = ((PlatformComputeBinarizable2) 
arg[i]).Field + 1};
+
+                return arg;
+            }
+
+            /** */
+            public PlatformComputeBinarizable[] 
TestBinarizableArray(PlatformComputeBinarizable[] arg)
+            {
+                // ReSharper disable once CoVariantArrayConversion
+                return 
(PlatformComputeBinarizable[])TestBinarizableArrayOfObjects(arg);
+            }
+
+            /** */
+            public IBinaryObject[] TestBinaryObjectArray(IBinaryObject[] x)
+            {
+                for (var i = 0; i < x.Length; i++)
+                {
+                    var binaryObject = x[i];
+
+                    var fieldVal = binaryObject.GetField<int>("Field");
+
+                    x[i] = binaryObject.ToBuilder().SetField("Field", fieldVal 
+ 1).Build();
+                }
+
+                return x;
+            }
+
+            /** */
+            public PlatformComputeBinarizable2[] 
TestBinarizableArray2(PlatformComputeBinarizable2[] arg)
+            {
+                // ReSharper disable once CoVariantArrayConversion
+                return 
(PlatformComputeBinarizable2[])TestBinarizableArrayOfObjects(arg);
+            }
+        }
+
+        /// <summary>
         /// Test service interface for proxying.
         /// </summary>
         public interface ITestIgniteService : IService, ITestIgniteServiceBase
@@ -1486,7 +1632,13 @@ namespace Apache.Ignite.Core.Tests.Services
             PlatformComputeBinarizable 
testBinarizable(PlatformComputeBinarizable x);
 
             /** */
-            object[] testBinarizableArray(object[] x);
+            object[] testBinarizableArrayOfObjects(object[] x);
+
+            /** */
+            IBinaryObject[] testBinaryObjectArray(IBinaryObject[] x);
+
+            /** */
+            PlatformComputeBinarizable[] 
testBinarizableArray(PlatformComputeBinarizable[] x);
 
             /** */
             ICollection testBinarizableCollection(ICollection x);
@@ -1503,5 +1655,14 @@ namespace Apache.Ignite.Core.Tests.Services
             /** */
             public int Field { get; set; }
         }
+
+        /// <summary>
+        /// Class has no an equals class in Java.
+        /// </summary>
+        public class PlatformComputeBinarizable2
+        {
+            /** */
+            public int Field { get; set; }
+        }
     }
 }
diff --git 
a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Common/DelegateConverter.cs 
b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Common/DelegateConverter.cs
index 54df5f0..968a5c6 100644
--- 
a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Common/DelegateConverter.cs
+++ 
b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Common/DelegateConverter.cs
@@ -38,6 +38,10 @@ namespace Apache.Ignite.Core.Impl.Common
         /** */
         private static readonly MethodInfo ReadObjectMethod = typeof 
(IBinaryRawReader).GetMethod("ReadObject");
 
+        /** */
+        private static readonly MethodInfo ConvertArrayMethod = 
typeof(DelegateConverter).GetMethod("ConvertArray",
+            BindingFlags.Static | BindingFlags.NonPublic);
+
         /// <summary>
         /// Compiles a function without arguments.
         /// </summary>
@@ -95,7 +99,7 @@ namespace Apache.Ignite.Core.Impl.Common
         /// Compiled function that calls specified method on specified target.
         /// </returns>
         [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of 
public methods")]
-        public static T CompileFunc<T>(Type targetType, MethodInfo method, 
Type[] argTypes, 
+        public static T CompileFunc<T>(Type targetType, MethodInfo method, 
Type[] argTypes,
             bool[] convertToObject = null)
             where T : class
         {
@@ -114,7 +118,7 @@ namespace Apache.Ignite.Core.Impl.Common
             targetType = method.IsStatic ? null : (targetType ?? 
method.DeclaringType);
 
             var targetParam = Expression.Parameter(typeof(object));
-            
+
             Expression targetParamConverted = null;
             ParameterExpression[] argParams;
             int argParamsOffset = 0;
@@ -178,9 +182,9 @@ namespace Apache.Ignite.Core.Impl.Common
             for (var i = 0; i < methodParams.Length; i++)
             {
                 var arrElem = Expression.ArrayIndex(arrParam, 
Expression.Constant(i));
-                argParams[i] = Expression.Convert(arrElem, 
methodParams[i].ParameterType);
+                argParams[i] = Convert(arrElem, methodParams[i].ParameterType);
             }
-            
+
             Expression callExpr = Expression.Call(targetParamConverted, 
method, argParams);
 
             if (callExpr.Type == typeof(void))
@@ -312,11 +316,11 @@ namespace Apache.Ignite.Core.Impl.Common
         /// </summary>
         /// <typeparam name="T">Result type</typeparam>
         /// <param name="ctor">The ctor.</param>
-        /// <param name="innerCtorFunc">Function to retrieve reading 
constructor for an argument. 
+        /// <param name="innerCtorFunc">Function to retrieve reading 
constructor for an argument.
         /// Can be null or return null, in this case the argument will be read 
directly via ReadObject.</param>
         /// <returns></returns>
         [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of 
public methods")]
-        public static Func<IBinaryRawReader, T> CompileCtor<T>(ConstructorInfo 
ctor, 
+        public static Func<IBinaryRawReader, T> CompileCtor<T>(ConstructorInfo 
ctor,
             Func<Type, ConstructorInfo> innerCtorFunc)
         {
             Debug.Assert(ctor != null);
@@ -338,7 +342,7 @@ namespace Apache.Ignite.Core.Impl.Common
         /// <returns>
         /// Ctor call expression.
         /// </returns>
-        private static Expression GetConstructorExpression(ConstructorInfo 
ctor, 
+        private static Expression GetConstructorExpression(ConstructorInfo 
ctor,
             Func<Type, ConstructorInfo> innerCtorFunc, Expression readerParam, 
Type resultType)
         {
             var ctorParams = ctor.GetParameters();
@@ -480,11 +484,11 @@ namespace Apache.Ignite.Core.Impl.Common
 
             Debug.Assert(declaringType != null);
 
-            var method = new DynamicMethod(string.Empty, null, new[] { 
typeof(object), field.FieldType }, 
+            var method = new DynamicMethod(string.Empty, null, new[] { 
typeof(object), field.FieldType },
                 declaringType, true);
 
             var il = method.GetILGenerator();
-            
+
             il.Emit(OpCodes.Ldarg_0);
 
             if (declaringType.IsValueType)
@@ -522,5 +526,40 @@ namespace Apache.Ignite.Core.Impl.Common
 
             return null;
         }
+
+        /// <summary>
+        /// Converts expression to a given type.
+        /// </summary>
+        private static Expression Convert(Expression value, Type targetType)
+        {
+            if (targetType.IsArray && targetType.GetElementType() != 
typeof(object))
+            {
+                var convertMethod = 
ConvertArrayMethod.MakeGenericMethod(targetType.GetElementType());
+
+                var objArray = Expression.Convert(value, typeof(object[]));
+
+                return Expression.Call(null, convertMethod, objArray);
+            }
+
+            return Expression.Convert(value, targetType);
+        }
+
+        /// <summary>
+        /// Converts object array to typed array.
+        /// </summary>
+        // ReSharper disable once UnusedMember.Local (used by reflection).
+        private static T[] ConvertArray<T>(object[] arr)
+        {
+            if (arr == null)
+            {
+                return null;
+            }
+
+            var res = new T[arr.Length];
+
+            Array.Copy(arr, res, arr.Length);
+
+            return res;
+        }
     }
-}
\ No newline at end of file
+}

Reply via email to