IGNITE-7561 .NET: Add IServices.GetDynamicServiceProxy This closes #3457
Project: http://git-wip-us.apache.org/repos/asf/ignite/repo Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/f8d16749 Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/f8d16749 Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/f8d16749 Branch: refs/heads/ignite-7485-2 Commit: f8d16749c14089b4fba840a2e424e411763a52da Parents: 6ae7014 Author: Pavel Tupitsyn <ptupit...@apache.org> Authored: Thu Feb 1 14:53:16 2018 +0300 Committer: Pavel Tupitsyn <ptupit...@apache.org> Committed: Thu Feb 1 14:53:16 2018 +0300 ---------------------------------------------------------------------- .../Services/ServiceProxyTest.cs | 3 +- .../Services/ServicesAsyncWrapper.cs | 18 ++- .../Services/ServicesTest.cs | 148 ++++++++++++++++++- .../Apache.Ignite.Core.csproj | 1 + .../Impl/Services/DynamicServiceProxy.cs | 108 ++++++++++++++ .../Impl/Services/ServiceProxyInvoker.cs | 14 +- .../Impl/Services/ServiceProxySerializer.cs | 37 +++-- .../Impl/Services/Services.cs | 43 +++++- .../Apache.Ignite.Core/Services/IServices.cs | 26 ++++ 9 files changed, 363 insertions(+), 35 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ignite/blob/f8d16749/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServiceProxyTest.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServiceProxyTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServiceProxyTest.cs index 6146146..cb36d1d 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServiceProxyTest.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServiceProxyTest.cs @@ -283,7 +283,8 @@ namespace Apache.Ignite.Core.Tests.Services // 1) Write to a stream inStream.WriteBool(SrvKeepBinary); // WriteProxyMethod does not do this, but Java does - ServiceProxySerializer.WriteProxyMethod(_marsh.StartMarshal(inStream), method, args, Platform.DotNet); + ServiceProxySerializer.WriteProxyMethod(_marsh.StartMarshal(inStream), method.Name, + method, args, Platform.DotNet); inStream.SynchronizeOutput(); http://git-wip-us.apache.org/repos/asf/ignite/blob/f8d16749/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesAsyncWrapper.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesAsyncWrapper.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesAsyncWrapper.cs index 18db5d5..cf058a9 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesAsyncWrapper.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesAsyncWrapper.cs @@ -91,7 +91,7 @@ namespace Apache.Ignite.Core.Tests.Services } catch (AggregateException ex) { - throw ex.InnerException; + throw ex.InnerException ?? ex; } } @@ -110,7 +110,7 @@ namespace Apache.Ignite.Core.Tests.Services } catch (AggregateException ex) { - throw ex.InnerException; + throw ex.InnerException ?? ex; } } @@ -129,7 +129,7 @@ namespace Apache.Ignite.Core.Tests.Services } catch (AggregateException ex) { - throw ex.InnerException; + throw ex.InnerException ?? ex; } } @@ -194,6 +194,18 @@ namespace Apache.Ignite.Core.Tests.Services } /** <inheritDoc /> */ + public dynamic GetDynamicServiceProxy(string name) + { + return _services.GetDynamicServiceProxy(name); + } + + /** <inheritDoc /> */ + public dynamic GetDynamicServiceProxy(string name, bool sticky) + { + return _services.GetDynamicServiceProxy(name, sticky); + } + + /** <inheritDoc /> */ public IServices WithKeepBinary() { return new ServicesAsyncWrapper(_services.WithKeepBinary()); http://git-wip-us.apache.org/repos/asf/ignite/blob/f8d16749/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTest.cs ---------------------------------------------------------------------- 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 b8f4cdf..c6a8e0b 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTest.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTest.cs @@ -318,6 +318,67 @@ namespace Apache.Ignite.Core.Tests.Services } /// <summary> + /// Tests dynamic service proxies. + /// </summary> + [Test] + public void TestGetDynamicServiceProxy() + { + // Deploy to remotes. + var svc = new TestIgniteServiceSerializable { TestProperty = 37 }; + Grid1.GetCluster().ForRemotes().GetServices().DeployNodeSingleton(SvcName, svc); + + // Make sure there is no local instance on grid3 + Assert.IsNull(Grid3.GetServices().GetService<ITestIgniteService>(SvcName)); + + // Get proxy. + dynamic prx = Grid3.GetServices().GetDynamicServiceProxy(SvcName, false); + + // Property getter. + Assert.AreEqual(37, prx.TestProperty); + Assert.IsTrue(prx.Initialized); + Assert.IsTrue(prx.Executed); + Assert.IsFalse(prx.Cancelled); + Assert.AreEqual(SvcName, prx.LastCallContextName); + + // Property setter. + prx.TestProperty = 42; + Assert.AreEqual(42, prx.TestProperty); + + // Method invoke. + Assert.AreEqual(prx.ToString(), svc.ToString()); + Assert.AreEqual("baz", prx.Method("baz")); + + // Non-existent member. + var ex = Assert.Throws<ServiceInvocationException>(() => prx.FooBar(1)); + Assert.AreEqual( + string.Format("Failed to invoke proxy: there is no method 'FooBar' in type '{0}' with 1 arguments", + typeof(TestIgniteServiceSerializable)), (ex.InnerException ?? ex).Message); + + // Exception in service. + ex = Assert.Throws<ServiceInvocationException>(() => prx.ErrMethod(123)); + Assert.AreEqual("ExpectedException", (ex.InnerException ?? ex).Message.Substring(0, 17)); + } + + /// <summary> + /// Tests dynamic service proxies with local service instance. + /// </summary> + [Test] + public void TestGetDynamicServiceProxyLocal() + { + // Deploy to all nodes. + var svc = new TestIgniteServiceSerializable { TestProperty = 37 }; + Grid1.GetServices().DeployNodeSingleton(SvcName, svc); + + // Make sure there is an instance on grid1. + var svcInst = Grid1.GetServices().GetService<ITestIgniteService>(SvcName); + Assert.IsNotNull(svcInst); + + // Get dynamic proxy that simply wraps the service instance. + var prx = Grid1.GetServices().GetDynamicServiceProxy(SvcName); + Assert.AreSame(prx, svcInst); + } + + /// <summary> /// Tests the duck typing: proxy interface can be different from actual service interface, /// only called method signature should be compatible. /// </summary> @@ -729,13 +790,9 @@ namespace Apache.Ignite.Core.Tests.Services [Test] public void TestCallJavaService() { - const string javaSvcName = "javaService"; - // Deploy Java service - Grid1.GetCompute() - .ExecuteJavaTask<object>("org.apache.ignite.platform.PlatformDeployServiceTask", javaSvcName); - - TestUtils.WaitForCondition(() => Services.GetServiceDescriptors().Any(x => x.Name == javaSvcName), 1000); + const string javaSvcName = "javaService"; + DeployJavaService(javaSvcName); // Verify decriptor var descriptor = Services.GetServiceDescriptors().Single(x => x.Name == javaSvcName); @@ -814,6 +871,85 @@ namespace Apache.Ignite.Core.Tests.Services } /// <summary> + /// Tests Java service invocation with dynamic proxy. + /// </summary> + [Test] + public void TestCallJavaServiceDynamicProxy() + { + const string javaSvcName = "javaService"; + DeployJavaService(javaSvcName); + + var svc = Grid1.GetServices().GetDynamicServiceProxy(javaSvcName, true); + + // Basics + Assert.IsTrue(svc.isInitialized()); + Assert.IsTrue(TestUtils.WaitForCondition(() => svc.isExecuted(), 500)); + Assert.IsFalse(svc.isCancelled()); + + // Primitives + Assert.AreEqual(4, svc.test((byte)3)); + Assert.AreEqual(5, svc.test((short)4)); + Assert.AreEqual(6, svc.test(5)); + Assert.AreEqual(6, svc.test((long)5)); + Assert.AreEqual(3.8f, svc.test(2.3f)); + Assert.AreEqual(5.8, svc.test(3.3)); + Assert.IsFalse(svc.test(true)); + Assert.AreEqual('b', svc.test('a')); + Assert.AreEqual("Foo!", svc.test("Foo")); + + // Nullables (Java wrapper types) + Assert.AreEqual(4, svc.testWrapper(3)); + Assert.AreEqual(5, svc.testWrapper((short?)4)); + Assert.AreEqual(6, svc.testWrapper((int?)5)); + Assert.AreEqual(6, svc.testWrapper((long?)5)); + Assert.AreEqual(3.8f, svc.testWrapper(2.3f)); + Assert.AreEqual(5.8, svc.testWrapper(3.3)); + Assert.AreEqual(false, svc.testWrapper(true)); + Assert.AreEqual('b', svc.testWrapper('a')); + + // Arrays + Assert.AreEqual(new byte[] { 2, 3, 4 }, svc.testArray(new byte[] { 1, 2, 3 })); + Assert.AreEqual(new short[] { 2, 3, 4 }, svc.testArray(new short[] { 1, 2, 3 })); + Assert.AreEqual(new[] { 2, 3, 4 }, svc.testArray(new[] { 1, 2, 3 })); + Assert.AreEqual(new long[] { 2, 3, 4 }, svc.testArray(new long[] { 1, 2, 3 })); + Assert.AreEqual(new float[] { 2, 3, 4 }, svc.testArray(new float[] { 1, 2, 3 })); + Assert.AreEqual(new double[] { 2, 3, 4 }, svc.testArray(new double[] { 1, 2, 3 })); + Assert.AreEqual(new[] { "a1", "b1" }, svc.testArray(new[] { "a", "b" })); + Assert.AreEqual(new[] { 'c', 'd' }, svc.testArray(new[] { 'b', 'c' })); + Assert.AreEqual(new[] { false, true, false }, svc.testArray(new[] { true, false, true })); + + // Nulls + Assert.AreEqual(9, svc.testNull(8)); + Assert.IsNull(svc.testNull(null)); + + // Overloads + Assert.AreEqual(3, svc.test(2, "1")); + Assert.AreEqual(3, svc.test("1", 2)); + + // Binary + Assert.AreEqual(7, svc.testBinarizable(new PlatformComputeBinarizable { Field = 6 }).Field); + + // Binary object + var binSvc = Services.WithKeepBinary().WithServerKeepBinary().GetDynamicServiceProxy(javaSvcName); + + Assert.AreEqual(15, + binSvc.testBinaryObject( + Grid1.GetBinary().ToBinary<IBinaryObject>(new PlatformComputeBinarizable { Field = 6 })) + .GetField<int>("Field")); + } + + /// <summary> + /// Deploys the java service. + /// </summary> + private void DeployJavaService(string javaSvcName) + { + Grid1.GetCompute() + .ExecuteJavaTask<object>("org.apache.ignite.platform.PlatformDeployServiceTask", javaSvcName); + + TestUtils.WaitForCondition(() => Services.GetServiceDescriptors().Any(x => x.Name == javaSvcName), 1000); + } + + /// <summary> /// Tests the footer setting. /// </summary> [Test] http://git-wip-us.apache.org/repos/asf/ignite/blob/f8d16749/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj b/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj index adae2b1..d99b60e 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj @@ -103,6 +103,7 @@ <Compile Include="Impl\IPlatformTargetInternal.cs" /> <Compile Include="Impl\DataRegionMetrics.cs" /> <Compile Include="Impl\PersistentStore\PersistentStoreMetrics.cs" /> + <Compile Include="Impl\Services\DynamicServiceProxy.cs" /> <Compile Include="Impl\Services\ServiceMethodHelper.cs" /> <Compile Include="Impl\Services\ServiceProxyFactory.cs" /> <Compile Include="Impl\Services\ServiceProxyTypeGenerator.cs" /> http://git-wip-us.apache.org/repos/asf/ignite/blob/f8d16749/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/DynamicServiceProxy.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/DynamicServiceProxy.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/DynamicServiceProxy.cs new file mode 100644 index 0000000..71e9768 --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/DynamicServiceProxy.cs @@ -0,0 +1,108 @@ +/* + * 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.Impl.Services +{ + using System; + using System.Diagnostics; + using System.Dynamic; + + /// <summary> + /// Service proxy based on DynamicObject to be used with <c>dynamic</c> keyword. + /// </summary> + internal class DynamicServiceProxy : DynamicObject + { + private readonly Func<string, object[], object> _invokeMethod; + + /// <summary> + /// Initializes a new instance of the <see cref="DynamicServiceProxy"/> class. + /// </summary> + /// <param name="invokeMethod">The service invoke method.</param> + public DynamicServiceProxy(Func<string, object[], object> invokeMethod) + { + Debug.Assert(invokeMethod != null); + + _invokeMethod = invokeMethod; + } + + /// <summary> + /// Provides the implementation for operations that get member values. + /// </summary> + /// <param name="binder">Provides information about the object that called the dynamic operation. + /// The binder.Name property provides the name of the member on which the dynamic operation is performed. + /// </param> + /// <param name="result">The result of the get operation.</param> + /// <returns> + /// true if the operation is successful; otherwise, false. If this method returns false, + /// the run-time binder of the language determines the behavior. + /// (In most cases, a run-time exception is thrown.) + /// </returns> + public override bool TryGetMember(GetMemberBinder binder, out object result) + { + // Note that we don't know whether it is a field or a property, + // but services are supposed to be accessed through an interface, so property is assumed. + result = _invokeMethod("get_" + binder.Name, null); + return true; + } + + /// <summary> + /// Provides the implementation for operations that set member values. + /// </summary> + /// <param name="binder">Provides information about the object that called the dynamic operation + /// The binder.Name property provides the name of the member to which the value is being assigned. </param> + /// <param name="value">The value to set to the member.</param> + /// <returns> + /// true if the operation is successful; otherwise, false. If this method returns false, + /// the run-time binder of the language determines the behavior. + /// (In most cases, a language-specific run-time exception is thrown.) + /// </returns> + public override bool TrySetMember(SetMemberBinder binder, object value) + { + // Note that we don't know whether it is a field or a property, + // but services are supposed to be accessed through an interface, so property is assumed. + _invokeMethod("set_" + binder.Name, new[] { value }); + return true; + } + + /// <summary> + /// Provides the implementation for operations that invoke a member. + /// </summary> + /// <param name="binder">Provides information about the dynamic operation. + /// The binder.Name property provides the name of the member on which the dynamic operation is performed. + /// </param> + /// <param name="args">The arguments that are passed to the object member during the invoke operation.</param> + /// <param name="result">The result of the member invocation.</param> + /// <returns> + /// true if the operation is successful; otherwise, false. + /// If this method returns false, the run-time binder of the language determines the behavior. + /// (In most cases, a language-specific run-time exception is thrown.) + /// </returns> + public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) + { + result = _invokeMethod(binder.Name, args); + return true; + } + + /// <summary> + /// Returns a <see cref="string" /> that represents this instance. + /// </summary> + public override string ToString() + { + return (string) _invokeMethod("ToString", null); + } + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/f8d16749/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxyInvoker.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxyInvoker.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxyInvoker.cs index bf6cd16..006aa00 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxyInvoker.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxyInvoker.cs @@ -69,13 +69,16 @@ namespace Apache.Ignite.Core.Impl.Services /// <summary> /// Finds suitable method in the specified type, or throws an exception. /// </summary> - private static Func<object, object[], object> GetMethodOrThrow(Type svcType, string methodName, object[] arguments) + private static Func<object, object[], object> GetMethodOrThrow(Type svcType, string methodName, + object[] arguments) { Debug.Assert(svcType != null); Debug.Assert(!string.IsNullOrWhiteSpace(methodName)); + + var argsLength = arguments == null ? 0 : arguments.Length; // 0) Check cached methods - var cacheKey = Tuple.Create(svcType, methodName, arguments.Length); + var cacheKey = Tuple.Create(svcType, methodName, argsLength); Func<object, object[], object> res; if (Methods.TryGetValue(cacheKey, out res)) @@ -83,7 +86,7 @@ namespace Apache.Ignite.Core.Impl.Services // 1) Find methods by name var methods = svcType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) - .Where(m => CleanupMethodName(m) == methodName && m.GetParameters().Length == arguments.Length) + .Where(m => CleanupMethodName(m) == methodName && m.GetParameters().Length == argsLength) .ToArray(); if (methods.Length == 1) @@ -96,7 +99,7 @@ namespace Apache.Ignite.Core.Impl.Services throw new InvalidOperationException( string.Format(CultureInfo.InvariantCulture, "Failed to invoke proxy: there is no method '{0}' in type '{1}' with {2} arguments", - methodName, svcType, arguments.Length)); + methodName, svcType, argsLength)); // 2) There is more than 1 method with specified name - resolve with argument types. methods = methods.Where(m => AreMethodArgsCompatible(arguments, m.GetParameters())).ToArray(); @@ -105,10 +108,11 @@ namespace Apache.Ignite.Core.Impl.Services return (obj, args) => methods[0].Invoke(obj, args); // 3) 0 or more than 1 matching method - throw. - var argsString = arguments.Length == 0 + var argsString = argsLength == 0 ? "0" : "(" + // ReSharper disable once ConditionIsAlwaysTrueOrFalse + // ReSharper disable once AssignNullToNotNullAttribute arguments.Select(x => x == null ? "null" : x.GetType().Name).Aggregate((x, y) => x + ", " + y) + ")"; http://git-wip-us.apache.org/repos/asf/ignite/blob/f8d16749/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxySerializer.cs ---------------------------------------------------------------------- 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 422908f..42638bc 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxySerializer.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxySerializer.cs @@ -37,16 +37,16 @@ namespace Apache.Ignite.Core.Impl.Services /// Writes proxy method invocation data to the specified writer. /// </summary> /// <param name="writer">Writer.</param> - /// <param name="method">Method.</param> + /// <param name="methodName">Name of the method.</param> + /// <param name="method">Method (optional, can be null).</param> /// <param name="arguments">Arguments.</param> /// <param name="platform">The platform.</param> - public static void WriteProxyMethod(BinaryWriter writer, MethodBase method, object[] arguments, - Platform platform) + public static void WriteProxyMethod(BinaryWriter writer, string methodName, MethodBase method, + object[] arguments, Platform platform) { Debug.Assert(writer != null); - Debug.Assert(method != null); - writer.WriteString(method.Name); + writer.WriteString(methodName); if (arguments != null) { @@ -55,7 +55,7 @@ namespace Apache.Ignite.Core.Impl.Services if (platform == Platform.DotNet) { - // Write as is + // Write as is for .NET. foreach (var arg in arguments) { writer.WriteObjectDetached(arg); @@ -64,11 +64,13 @@ namespace Apache.Ignite.Core.Impl.Services else { // Other platforms do not support Serializable, need to convert arrays and collections - var methodArgs = method.GetParameters(); - Debug.Assert(methodArgs.Length == arguments.Length); + var mParams = method != null ? method.GetParameters() : null; + Debug.Assert(mParams == null || mParams.Length == arguments.Length); - for (int i = 0; i < arguments.Length; i++) - WriteArgForPlatforms(writer, methodArgs[i], arguments[i]); + for (var i = 0; i < arguments.Length; i++) + { + WriteArgForPlatforms(writer, mParams != null ? mParams[i].ParameterType : null, arguments[i]); + } } } else @@ -213,9 +215,9 @@ namespace Apache.Ignite.Core.Impl.Services /// <summary> /// Writes the argument in platform-compatible format. /// </summary> - private static void WriteArgForPlatforms(BinaryWriter writer, ParameterInfo param, object arg) + private static void WriteArgForPlatforms(BinaryWriter writer, Type paramType, object arg) { - var hnd = GetPlatformArgWriter(param, arg); + var hnd = GetPlatformArgWriter(paramType, arg); if (hnd != null) { @@ -230,14 +232,19 @@ namespace Apache.Ignite.Core.Impl.Services /// <summary> /// Gets arg writer for platform-compatible service calls. /// </summary> - private static Action<BinaryWriter, object> GetPlatformArgWriter(ParameterInfo param, object arg) + private static Action<BinaryWriter, object> GetPlatformArgWriter(Type paramType, object arg) { - var type = param.ParameterType; + if (arg == null) + { + return null; + } + + var type = paramType ?? arg.GetType(); // Unwrap nullable type = Nullable.GetUnderlyingType(type) ?? type; - if (arg == null || type.IsPrimitive) + if (type.IsPrimitive) return null; var handler = BinarySystemHandlers.GetWriteHandler(type); http://git-wip-us.apache.org/repos/asf/ignite/blob/f8d16749/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/Services.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/Services.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/Services.cs index 0360f97..53f9621 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/Services.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/Services.cs @@ -367,7 +367,8 @@ namespace Apache.Ignite.Core.Impl.Services public T GetServiceProxy<T>(string name, bool sticky) where T : class { IgniteArgumentCheck.NotNullOrEmpty(name, "name"); - IgniteArgumentCheck.Ensure(typeof(T).IsInterface, "T", "Service proxy type should be an interface: " + typeof(T)); + IgniteArgumentCheck.Ensure(typeof(T).IsInterface, "T", + "Service proxy type should be an interface: " + typeof(T)); // In local scenario try to return service instance itself instead of a proxy // Get as object because proxy interface may be different from real interface @@ -385,24 +386,56 @@ namespace Apache.Ignite.Core.Impl.Services var platform = GetServiceDescriptors().Cast<ServiceDescriptor>().Single(x => x.Name == name).Platform; return ServiceProxyFactory<T>.CreateProxy((method, args) => - InvokeProxyMethod(javaProxy, method, args, platform)); + InvokeProxyMethod(javaProxy, method.Name, method, args, platform)); + } + + /** <inheritDoc /> */ + public dynamic GetDynamicServiceProxy(string name) + { + return GetDynamicServiceProxy(name, false); + } + + /** <inheritDoc /> */ + public dynamic GetDynamicServiceProxy(string name, bool sticky) + { + IgniteArgumentCheck.NotNullOrEmpty(name, "name"); + + // In local scenario try to return service instance itself instead of a proxy + var locInst = GetService<object>(name); + + if (locInst != null) + { + return locInst; + } + + var javaProxy = DoOutOpObject(OpServiceProxy, w => + { + w.WriteString(name); + w.WriteBoolean(sticky); + }); + + var platform = GetServiceDescriptors().Cast<ServiceDescriptor>().Single(x => x.Name == name).Platform; + + return new DynamicServiceProxy((methodName, args) => + InvokeProxyMethod(javaProxy, methodName, null, args, platform)); } /// <summary> /// Invokes the service proxy method. /// </summary> /// <param name="proxy">Unmanaged proxy.</param> + /// <param name="methodName">Name of the method.</param> /// <param name="method">Method to invoke.</param> /// <param name="args">Arguments.</param> /// <param name="platform">The platform.</param> /// <returns> /// Invocation result. /// </returns> - private object InvokeProxyMethod(IPlatformTargetInternal proxy, MethodBase method, object[] args, - Platform platform) + private object InvokeProxyMethod(IPlatformTargetInternal proxy, string methodName, + MethodBase method, object[] args, Platform platform) { return DoOutInOp(OpInvokeMethod, - writer => ServiceProxySerializer.WriteProxyMethod(writer, method, args, platform), + writer => ServiceProxySerializer.WriteProxyMethod(writer, methodName, method, args, platform), (stream, res) => ServiceProxySerializer.ReadInvocationResult(stream, Marshaller, _keepBinary), proxy); } http://git-wip-us.apache.org/repos/asf/ignite/blob/f8d16749/modules/platforms/dotnet/Apache.Ignite.Core/Services/IServices.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Services/IServices.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Services/IServices.cs index bcbd4fb..84c23fa 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Services/IServices.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Services/IServices.cs @@ -272,6 +272,32 @@ namespace Apache.Ignite.Core.Services T GetServiceProxy<T>(string name, bool sticky) where T : class; /// <summary> + /// Gets a remote handle on the service as a dynamic object. If service is available locally, + /// then local instance is returned, otherwise, a remote proxy is dynamically + /// created and provided for the specified service. + /// <para /> + /// This method utilizes <c>dynamic</c> feature of the language and does not require any + /// service interfaces or classes. Java services can be accessed as well as .NET services. + /// </summary> + /// <param name="name">Service name.</param> + /// <returns>Either proxy over remote service or local service if it is deployed locally.</returns> + dynamic GetDynamicServiceProxy(string name); + + /// <summary> + /// Gets a remote handle on the service as a dynamic object. If service is available locally, + /// then local instance is returned, otherwise, a remote proxy is dynamically + /// created and provided for the specified service. + /// <para /> + /// This method utilizes <c>dynamic</c> feature of the language and does not require any + /// service interfaces or classes. Java services can be accessed as well as .NET services. + /// </summary> + /// <param name="name">Service name.</param> + /// <param name="sticky">Whether or not Ignite should always contact the same remote + /// service or try to load-balance between services.</param> + /// <returns>Either proxy over remote service or local service if it is deployed locally.</returns> + dynamic GetDynamicServiceProxy(string name, bool sticky); + + /// <summary> /// Returns an instance with binary mode enabled. /// Service method results will be kept in binary form. /// </summary>