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
+}