Repository: ignite Updated Branches: refs/heads/ignite-zk 44a81bbb6 -> 7a1cdefc5
IGNITE-7281 .NET Core: make Services work through custom proxy This closes #3328 Project: http://git-wip-us.apache.org/repos/asf/ignite/repo Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/11508d94 Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/11508d94 Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/11508d94 Branch: refs/heads/ignite-zk Commit: 11508d941ee6f7008538416fc1c7af71e602c9d1 Parents: 43c7dda Author: Alexey Rokhin <[email protected]> Authored: Thu Jan 11 16:29:20 2018 +0300 Committer: Pavel Tupitsyn <[email protected]> Committed: Thu Jan 11 16:29:20 2018 +0300 ---------------------------------------------------------------------- .../Apache.Ignite.Core.Tests.DotNetCore.csproj | 5 + .../Services/ServiceProxyTest.cs | 16 +- .../Services/ServicesTest.cs | 38 +-- .../Apache.Ignite.Core.csproj | 4 +- .../Impl/Services/ServiceMethodHelper.cs | 61 ++++ .../Impl/Services/ServiceProxy.cs | 75 ----- .../Impl/Services/ServiceProxyFactory.cs | 68 +++++ .../Impl/Services/ServiceProxyTypeGenerator.cs | 281 +++++++++++++++++++ .../Impl/Services/Services.cs | 8 +- .../Properties/AssemblyInfo.cs | 4 +- 10 files changed, 447 insertions(+), 113 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ignite/blob/11508d94/modules/platforms/dotnet/Apache.Ignite.Core.Tests.DotNetCore/Apache.Ignite.Core.Tests.DotNetCore.csproj ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests.DotNetCore/Apache.Ignite.Core.Tests.DotNetCore.csproj b/modules/platforms/dotnet/Apache.Ignite.Core.Tests.DotNetCore/Apache.Ignite.Core.Tests.DotNetCore.csproj index d660b62..3786928 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests.DotNetCore/Apache.Ignite.Core.Tests.DotNetCore.csproj +++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests.DotNetCore/Apache.Ignite.Core.Tests.DotNetCore.csproj @@ -112,6 +112,10 @@ <Compile Include="..\Apache.Ignite.Core.Tests\Plugin\TestIgnitePluginException.cs" Link="Plugin\TestIgnitePluginException.cs" /> <Compile Include="..\Apache.Ignite.Core.Tests\Plugin\TestIgnitePluginProvider.cs" Link="Plugin\TestIgnitePluginProvider.cs" /> <Compile Include="..\Apache.Ignite.Core.Tests\Query\BinarizablePerson.cs" Link="Cache\Query\BinarizablePerson.cs" /> + <Compile Include="..\Apache.Ignite.Core.Tests\Services\ServiceProxyTest.cs" Link="Services\ServiceProxyTest.cs" /> + <Compile Include="..\Apache.Ignite.Core.Tests\Services\ServicesAsyncWrapper.cs" Link="Services\ServicesAsyncWrapper.cs" /> + <Compile Include="..\Apache.Ignite.Core.Tests\Services\ServicesTest.cs" Link="Services\ServicesTest.cs" /> + <Compile Include="..\Apache.Ignite.Core.Tests\Services\ServicesTestAsync.cs" Link="Services\ServicesTestAsync.cs" /> <Compile Include="..\Apache.Ignite.Core.Tests\TaskExtensions.cs" Link="Common\TaskExtensions.cs" /> <Compile Include="..\Apache.Ignite.Core.Tests\TestUtils.Common.cs" Link="Common\TestUtils.Common.cs" /> </ItemGroup> @@ -168,6 +172,7 @@ <Folder Include="DataStructures\" /> <Folder Include="ApiParity\" /> <Folder Include="Plugin\" /> + <Folder Include="Services\" /> <Folder Include="ThinClient\Cache\" /> </ItemGroup> http://git-wip-us.apache.org/repos/asf/ignite/blob/11508d94/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 eb6192d..133ffdf 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServiceProxyTest.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServiceProxyTest.cs @@ -190,6 +190,9 @@ namespace Apache.Ignite.Core.Tests.Services "can't resolve ambiguity.", ex.Message); } + /// <summary> + /// Tests the exception. + /// </summary> [Test] public void TestException() { @@ -261,7 +264,7 @@ namespace Apache.Ignite.Core.Tests.Services { _svc = new TestIgniteService(Binary); - var prx = new ServiceProxy<T>(InvokeProxyMethod).GetTransparentProxy(); + var prx = ServiceProxyFactory<T>.CreateProxy(InvokeProxyMethod); Assert.IsFalse(ReferenceEquals(_svc, prx)); @@ -315,7 +318,7 @@ namespace Apache.Ignite.Core.Tests.Services /// <summary> /// Test service interface. /// </summary> - protected interface ITestIgniteServiceProperties + public interface ITestIgniteServiceProperties { /** */ int IntProp { get; set; } @@ -330,7 +333,7 @@ namespace Apache.Ignite.Core.Tests.Services /// <summary> /// Test service interface to check ambiguity handling. /// </summary> - protected interface ITestIgniteServiceAmbiguity + public interface ITestIgniteServiceAmbiguity { /** */ int AmbiguousMethod(int arg); @@ -339,7 +342,7 @@ namespace Apache.Ignite.Core.Tests.Services /// <summary> /// Test service interface. /// </summary> - protected interface ITestIgniteService : ITestIgniteServiceProperties + public interface ITestIgniteService : ITestIgniteServiceProperties { /** */ void VoidMethod(); @@ -390,7 +393,7 @@ namespace Apache.Ignite.Core.Tests.Services /// <summary> /// Test service interface. Does not derive from actual interface, but has all the same method signatures. /// </summary> - protected interface ITestIgniteServiceProxyInterface + public interface ITestIgniteServiceProxyInterface { /** */ int IntProp { get; set; } @@ -570,6 +573,7 @@ namespace Apache.Ignite.Core.Tests.Services /** <inheritdoc /> */ public override int GetHashCode() { + // ReSharper disable once NonReadonlyMemberInGetHashCode return IntProp.GetHashCode(); } @@ -653,7 +657,7 @@ namespace Apache.Ignite.Core.Tests.Services /// <summary> /// Binarizable object for method argument/result. /// </summary> - protected class TestBinarizableClass : IBinarizable + public class TestBinarizableClass : IBinarizable { /** */ public string Prop { get; set; } http://git-wip-us.apache.org/repos/asf/ignite/blob/11508d94/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 d3dd9b0..17cf923 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTest.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTest.cs @@ -30,7 +30,6 @@ namespace Apache.Ignite.Core.Tests.Services using Apache.Ignite.Core.Common; using Apache.Ignite.Core.Resource; using Apache.Ignite.Core.Services; - using Apache.Ignite.Core.Tests.Compute; using NUnit.Framework; /// <summary> @@ -72,7 +71,6 @@ namespace Apache.Ignite.Core.Tests.Services public void SetUp() { StartGrids(); - EventsTestHelper.ListenResult = true; } /// <summary> @@ -96,8 +94,6 @@ namespace Apache.Ignite.Core.Tests.Services } finally { - EventsTestHelper.AssertFailures(); - if (TestContext.CurrentContext.Test.Name.StartsWith("TestEventTypes")) StopGrids(); // clean events for other tests } @@ -292,7 +288,6 @@ namespace Apache.Ignite.Core.Tests.Services // Check proxy properties Assert.IsNotNull(prx); - Assert.AreEqual(prx.GetType(), svc.GetType()); Assert.AreEqual(prx.ToString(), svc.ToString()); Assert.AreEqual(17, prx.TestProperty); Assert.IsTrue(prx.Initialized); @@ -352,8 +347,7 @@ namespace Apache.Ignite.Core.Tests.Services // .. but setter does not var ex = Assert.Throws<ServiceInvocationException>(() => { prx.TestProperty = new object(); }); - Assert.IsNotNull(ex.InnerException); - Assert.AreEqual("Specified cast is not valid.", ex.InnerException.Message); + Assert.IsInstanceOf<InvalidCastException>(ex.InnerException); } /// <summary> @@ -375,7 +369,6 @@ namespace Apache.Ignite.Core.Tests.Services Assert.AreEqual(1, desc.AffinityKey); Assert.AreEqual(1, desc.MaxPerNodeCount); Assert.AreEqual(1, desc.TotalCount); - Assert.AreEqual(typeof(TestIgniteServiceSerializable), desc.Type); Assert.AreEqual(Grid1.GetCluster().GetLocalNode().Id, desc.OriginNodeId); var top = desc.TopologySnapshot; @@ -747,11 +740,6 @@ namespace Apache.Ignite.Core.Tests.Services // Verify decriptor var descriptor = Services.GetServiceDescriptors().Single(x => x.Name == javaSvcName); Assert.AreEqual(javaSvcName, descriptor.Name); - Assert.Throws<ServiceInvocationException>(() => - { - // ReSharper disable once UnusedVariable - var type = descriptor.Type; - }); var svc = Services.GetServiceProxy<IJavaService>(javaSvcName, false); var binSvc = Services.WithKeepBinary().WithServerKeepBinary() @@ -848,9 +836,9 @@ namespace Apache.Ignite.Core.Tests.Services if (Grid1 != null) return; - Grid1 = Ignition.Start(GetConfiguration("config\\compute\\compute-grid1.xml")); - Grid2 = Ignition.Start(GetConfiguration("config\\compute\\compute-grid2.xml")); - Grid3 = Ignition.Start(GetConfiguration("config\\compute\\compute-grid3.xml")); + Grid1 = Ignition.Start(GetConfiguration("Config\\Compute\\compute-grid1.xml")); + Grid2 = Ignition.Start(GetConfiguration("Config\\Compute\\compute-grid2.xml")); + Grid3 = Ignition.Start(GetConfiguration("Config\\Compute\\compute-grid3.xml")); Grids = new[] { Grid1, Grid2, Grid3 }; } @@ -895,14 +883,16 @@ namespace Apache.Ignite.Core.Tests.Services /// </summary> private IgniteConfiguration GetConfiguration(string springConfigUrl) { +#if !NETCOREAPP2_0 if (!CompactFooter) - springConfigUrl = ComputeApiTestFullFooter.ReplaceFooterSetting(springConfigUrl); + { + springConfigUrl = Compute.ComputeApiTestFullFooter.ReplaceFooterSetting(springConfigUrl); + } +#endif - return new IgniteConfiguration + return new IgniteConfiguration(TestUtils.GetTestConfiguration()) { SpringConfigUrl = springConfigUrl, - JvmClasspath = TestUtils.CreateTestClasspath(), - JvmOptions = TestUtils.TestJavaOptions(), BinaryConfiguration = new BinaryConfiguration( typeof (TestIgniteServiceBinarizable), typeof (TestIgniteServiceBinarizableErr), @@ -952,7 +942,7 @@ namespace Apache.Ignite.Core.Tests.Services /// <summary> /// Test service interface for proxying. /// </summary> - private interface ITestIgniteService + public interface ITestIgniteService { int TestProperty { get; set; } @@ -982,7 +972,7 @@ namespace Apache.Ignite.Core.Tests.Services /// Test service interface for proxy usage. /// Has some of the original interface members with different signatures. /// </summary> - private interface ITestIgniteServiceProxyInterface + public interface ITestIgniteServiceProxyInterface { /** */ Guid NodeId { get; } @@ -1199,7 +1189,7 @@ namespace Apache.Ignite.Core.Tests.Services /// Java service proxy interface. /// </summary> [SuppressMessage("ReSharper", "InconsistentNaming")] - private interface IJavaService + public interface IJavaService { /** */ bool isCancelled(); @@ -1315,7 +1305,7 @@ namespace Apache.Ignite.Core.Tests.Services /// <summary> /// Interop class. /// </summary> - private class PlatformComputeBinarizable + public class PlatformComputeBinarizable { /** */ public int Field { get; set; } http://git-wip-us.apache.org/repos/asf/ignite/blob/11508d94/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 cdde538..9a6aeb9 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj @@ -142,6 +142,9 @@ <Compile Include="Impl\IPlatformTargetInternal.cs" /> <Compile Include="Impl\DataRegionMetrics.cs" /> <Compile Include="Impl\PersistentStore\PersistentStoreMetrics.cs" /> + <Compile Include="Impl\Services\ServiceMethodHelper.cs" /> + <Compile Include="Impl\Services\ServiceProxyFactory.cs" /> + <Compile Include="Impl\Services\ServiceProxyTypeGenerator.cs" /> <Compile Include="Impl\Shell.cs" /> <Compile Include="Impl\Unmanaged\Jni\DllLoader.cs" /> <Compile Include="Impl\Unmanaged\Jni\AppDomains.cs" /> @@ -519,7 +522,6 @@ <Compile Include="Impl\Resource\ResourceTypeDescriptor.cs" /> <Compile Include="Impl\Services\ServiceContext.cs" /> <Compile Include="Impl\Services\ServiceDescriptor.cs" /> - <Compile Include="Impl\Services\ServiceProxy.cs" /> <Compile Include="Impl\Services\ServiceProxyInvoker.cs" /> <Compile Include="Impl\Services\ServiceProxySerializer.cs" /> <Compile Include="Impl\Services\Services.cs" /> http://git-wip-us.apache.org/repos/asf/ignite/blob/11508d94/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceMethodHelper.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceMethodHelper.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceMethodHelper.cs new file mode 100644 index 0000000..e6fb2c0 --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceMethodHelper.cs @@ -0,0 +1,61 @@ +/* + * 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.Collections.Generic; + using System.Diagnostics; + using System.Reflection; + + /// <summary> + /// Provides reflection information about types. + /// This class used by ServiceProxyTypeGenerator and by generated proxy (to initialize static field). + /// </summary> + internal static class ServiceMethodHelper + { + /// <summary> + /// Provides information about virtual methods of the type + /// </summary> + /// <param name="type">Type to inspect.</param> + /// <returns>List of virtual methods.</returns> + public static MethodInfo[] GetVirtualMethods(Type type) + { + Debug.Assert(type != null); + var methods = new List<MethodInfo>(); + + foreach (var method in type.GetMethods(BindingFlags.Instance | BindingFlags.Public | + BindingFlags.NonPublic | BindingFlags.DeclaredOnly)) + { + if (method.IsVirtual) + methods.Add(method); + } + + if (type.IsInterface) + { + foreach (var method in typeof(object).GetMethods( + BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly)) + { + if (method.IsVirtual) + methods.Add(method); + } + } + + return methods.ToArray(); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ignite/blob/11508d94/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxy.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxy.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxy.cs deleted file mode 100644 index 7952865..0000000 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxy.cs +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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. - */ - -#if !NETCOREAPP2_0 -namespace Apache.Ignite.Core.Impl.Services -{ - using System; - using System.Diagnostics; - using System.Diagnostics.CodeAnalysis; - using System.Reflection; - using System.Runtime.Remoting.Messaging; - using System.Runtime.Remoting.Proxies; - - /// <summary> - /// Service proxy: user works with a remote service as if it is a local object. - /// </summary> - /// <typeparam name="T">User type to be proxied.</typeparam> - internal class ServiceProxy<T> : RealProxy - { - /** Services. */ - private readonly Func<MethodBase, object[], object> _invokeAction; - - /// <summary> - /// Initializes a new instance of the <see cref="ServiceProxy{T}" /> class. - /// </summary> - /// <param name="invokeAction">Method invoke action.</param> - public ServiceProxy(Func<MethodBase, object[], object> invokeAction) - : base(typeof (T)) - { - Debug.Assert(invokeAction != null); - - _invokeAction = invokeAction; - } - - /** <inheritdoc /> */ - [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods")] - public override IMessage Invoke(IMessage msg) - { - var methodCall = msg as IMethodCallMessage; - - if (methodCall == null) - throw new NotSupportedException("Service proxy operation type not supported: " + msg.GetType() + - ". Only method and property calls are supported."); - - if (methodCall.InArgCount != methodCall.ArgCount) - throw new NotSupportedException("Service proxy does not support out arguments: " - + methodCall.MethodBase); - - var result = _invokeAction(methodCall.MethodBase, methodCall.Args); - - return new ReturnMessage(result, null, 0, methodCall.LogicalCallContext, methodCall); - } - - /** <inheritdoc /> */ - public new T GetTransparentProxy() - { - return (T) base.GetTransparentProxy(); - } - } -} -#endif \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ignite/blob/11508d94/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxyFactory.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxyFactory.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxyFactory.cs new file mode 100644 index 0000000..a32f10e --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxyFactory.cs @@ -0,0 +1,68 @@ +/* + * 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.Linq.Expressions; + using System.Reflection; + using ProxyAction = System.Func<System.Reflection.MethodBase, object[], object>; + + /// <summary> + /// Factory for proxy creation. + /// </summary> + /// <typeparam name="T">User type to be proxied.</typeparam> + internal static class ServiceProxyFactory<T> + { + /** */ + private static readonly Func<ProxyAction, T> Factory = GenerateFactory(); + + /// <summary> + /// Creates proxy which methods call provided function. + /// </summary> + /// <param name="action">Action to call.</param> + /// <returns>Proxy.</returns> + public static T CreateProxy(ProxyAction action) + { + Debug.Assert(action != null); + + return Factory(action); + } + + /// <summary> + /// Generates the proxy factory. + /// </summary> + private static Func<ProxyAction, T> GenerateFactory() + { + // Generate proxy class + var result = ServiceProxyTypeGenerator.Generate(typeof(T)); + var typeCtr = result.Item1.GetConstructor(new[] { typeof(ProxyAction), typeof(MethodInfo[]) }); + Debug.Assert(typeCtr != null); + + // Generate method that creates proxy class instance. + // Single parameter of method + var action = Expression.Parameter(typeof(ProxyAction)); + + // Call constructor and pass action parameter and array of methods. + var ctr = Expression.New(typeCtr, action, Expression.Constant(result.Item2)); + var lambda = Expression.Lambda<Func<ProxyAction, T>>(ctr, action); + + return lambda.Compile(); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ignite/blob/11508d94/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxyTypeGenerator.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxyTypeGenerator.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxyTypeGenerator.cs new file mode 100644 index 0000000..97de9c7 --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxyTypeGenerator.cs @@ -0,0 +1,281 @@ +/* + * 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.Reflection; + using System.Reflection.Emit; + using ProxyAction = System.Func<System.Reflection.MethodBase, object[], object>; + + /// <summary> + /// Emits service proxy type. + /// </summary> + internal static class ServiceProxyTypeGenerator + { + /** */ + private static readonly Type ActionType = typeof(ProxyAction); + + /** */ + private static readonly MethodInfo InvokeMethod = ActionType.GetMethod("Invoke"); + + /** */ + private static readonly ModuleBuilder ModuleBuilder = CreateModuleBuilder(); + + /// <summary> + /// Generates the proxy for specified service type. + /// </summary> + public static Tuple<Type, MethodInfo[]> Generate(Type serviceType) + { + Debug.Assert(serviceType != null); + Debug.Assert(serviceType.FullName != null); + + var isClass = serviceType.IsClass; + var proxyType = ModuleBuilder.DefineType(serviceType.FullName, + TypeAttributes.Class, isClass ? serviceType : null); + + var buildContext = new ProxyBuildContext(proxyType, serviceType); + if (!isClass) + { + proxyType.AddInterfaceImplementation(serviceType); + } + + GenerateFields(buildContext); + GenerateStaticConstructor(buildContext); + GenerateConstructor(buildContext); + + buildContext.Methods = ServiceMethodHelper.GetVirtualMethods(buildContext.ServiceType); + for (var i = 0; i < buildContext.Methods.Length; i++) + { + GenerateMethod(buildContext, i); + } + + var type = proxyType.CreateType(); + return Tuple.Create(type, buildContext.Methods); + } + + /// <summary> + /// Creates a builder for a temporary module. + /// </summary> + private static ModuleBuilder CreateModuleBuilder() + { + var name = Guid.NewGuid().ToString("N"); + +#if !NETCOREAPP2_0 + var assemblyBuilder = + AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName(name), + AssemblyBuilderAccess.RunAndCollect); +#else + var assemblyBuilder = + AssemblyBuilder.DefineDynamicAssembly(new AssemblyName(name), + AssemblyBuilderAccess.RunAndCollect); +#endif + + return assemblyBuilder.DefineDynamicModule(name); + } + + /// <summary> + /// Generates readonly fields: action and method array. + /// </summary> + private static void GenerateFields(ProxyBuildContext buildContext) + { + // Static field - empty object array to optimize calls without parameters. + buildContext.EmptyParametersField = buildContext.ProxyType.DefineField("_emptyParameters", + typeof(object[]), FieldAttributes.Static | FieldAttributes.Private | FieldAttributes.InitOnly); + + // Instance field for function to invoke. + buildContext.ActionField = buildContext.ProxyType.DefineField("_action", ActionType, + FieldAttributes.Private | FieldAttributes.InitOnly); + + // Field - array with methods of service's type. + buildContext.MethodsField = buildContext.ProxyType.DefineField("_methods", typeof(MethodInfo[]), + FieldAttributes.Private | FieldAttributes.InitOnly); + } + + /// <summary> + /// Generates the static constructor (type initializer). + /// </summary> + private static void GenerateStaticConstructor(ProxyBuildContext buildContext) + { + var cb = buildContext.ProxyType.DefineConstructor( + MethodAttributes.Static | MethodAttributes.Private | MethodAttributes.HideBySig, + CallingConventions.Standard, new Type[0]); + var gen = cb.GetILGenerator(); + //fill _emptyParameters field + gen.Emit(OpCodes.Ldc_I4_0); + gen.Emit(OpCodes.Newarr, typeof(object)); + gen.Emit(OpCodes.Stsfld, buildContext.EmptyParametersField); + + gen.Emit(OpCodes.Ret); + } + + /// <summary> + /// Generates the constructor which calls base class (when necessary) and initializes fields. + /// </summary> + private static void GenerateConstructor(ProxyBuildContext buildContext) + { + var baseType = buildContext.ServiceType; + var isClass = baseType.IsClass; + + ConstructorInfo baseCtr = null; + if (isClass) + { + baseCtr = baseType.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public, + null, new Type[0], null); + if (baseCtr == null) + throw new NotSupportedException( + "Service proxy does not support base types without parameterless constructor: " + + baseType.FullName); + } + + var cb = buildContext.ProxyType.DefineConstructor(MethodAttributes.Public, CallingConventions.HasThis, + new[] {ActionType, typeof(MethodInfo[])}); + var gen = cb.GetILGenerator(); + + if (isClass) + { + // Load "this". + gen.Emit(OpCodes.Ldarg_0); + // Call base constructor. + gen.Emit(OpCodes.Call, baseCtr); + } + + // Assign parameters to fields. + gen.Emit(OpCodes.Ldarg_0); + gen.Emit(OpCodes.Ldarg_1); + gen.Emit(OpCodes.Stfld, buildContext.ActionField); + + gen.Emit(OpCodes.Ldarg_0); + gen.Emit(OpCodes.Ldarg_2); + gen.Emit(OpCodes.Stfld, buildContext.MethodsField); + + gen.Emit(OpCodes.Ret); + } + + /// <summary> + /// Generates the overriding method which delegates to ProxyAction. + /// </summary> + private static void GenerateMethod(ProxyBuildContext buildContext, int methodIndex) + { + var method = buildContext.Methods[methodIndex]; + Debug.Assert(method.DeclaringType != null); + var parameters = method.GetParameters(); + var parameterTypes = new Type[parameters.Length]; + for (var i = 0; i < parameters.Length; i++) + parameterTypes[i] = parameters[i].ParameterType; + + var attributes = MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig; + if (method.DeclaringType.IsInterface) + attributes |= MethodAttributes.Final | MethodAttributes.NewSlot; + if ((method.Attributes & MethodAttributes.SpecialName) == MethodAttributes.SpecialName) + attributes |= MethodAttributes.SpecialName; + var methodBuilder = + buildContext.ProxyType.DefineMethod(method.Name, attributes, method.ReturnType, parameterTypes); + var gen = methodBuilder.GetILGenerator(); + + // Prepare arguments for action invocation. + + // Load action field. + gen.Emit(OpCodes.Ldarg_0); + gen.Emit(OpCodes.Ldfld, buildContext.ActionField); + + // Load methods array field. + gen.Emit(OpCodes.Ldarg_0); + gen.Emit(OpCodes.Ldfld, buildContext.MethodsField); + + // Load index of method. + gen.Emit(OpCodes.Ldc_I4, methodIndex); + + // Load array element. + gen.Emit(OpCodes.Ldelem_Ref); + + if (parameters.Length > 0) + { + // Create array for action's parameters. + gen.Emit(OpCodes.Ldc_I4, parameters.Length); + gen.Emit(OpCodes.Newarr, typeof(object)); + + // Fill array. + // Load call arguments. + for (var i = 0; i < parameters.Length; i++) + { + gen.Emit(OpCodes.Dup); + + // Parameter's index in array. + gen.Emit(OpCodes.Ldc_I4, i); + + // Parameter's value. + gen.Emit(OpCodes.Ldarg, i + 1); + if (parameterTypes[i].IsValueType) + { + gen.Emit(OpCodes.Box, parameterTypes[i]); + } + + // Set array's element + gen.Emit(OpCodes.Stelem_Ref); + } + } + else + { + // Load static empty parameters field. + gen.Emit(OpCodes.Ldsfld, buildContext.EmptyParametersField); + } + + // Call action method. + gen.Emit(OpCodes.Callvirt, InvokeMethod); + + // Load result. + if (method.ReturnType != typeof(void)) + { + if (method.ReturnType.IsValueType) + gen.Emit(OpCodes.Unbox_Any, method.ReturnType); + } + else + { + // Method should not return result, so remove result from stack. + gen.Emit(OpCodes.Pop); + } + //exit + gen.Emit(OpCodes.Ret); + } + + /// <summary> + /// Proxy build state. + /// </summary> + private class ProxyBuildContext + { + /// <summary> + /// Initializes a new instance of the <see cref="ProxyBuildContext"/> class. + /// </summary> + public ProxyBuildContext(TypeBuilder proxyType, Type serviceType) + { + ProxyType = proxyType; + ServiceType = serviceType; + } + + /** */ public TypeBuilder ProxyType { get; private set; } + /** */ public Type ServiceType { get; private set; } + + /** */ public FieldBuilder MethodsField { get; set; } + /** */ public FieldBuilder EmptyParametersField { get; set; } + /** */ public FieldBuilder ActionField { get; set; } + + /** */ public MethodInfo[] Methods { get; set; } + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ignite/blob/11508d94/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 ce8332b..0360f97 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/Services.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/Services.cs @@ -366,7 +366,6 @@ namespace Apache.Ignite.Core.Impl.Services /** <inheritDoc /> */ public T GetServiceProxy<T>(string name, bool sticky) where T : class { -#if !NETCOREAPP2_0 IgniteArgumentCheck.NotNullOrEmpty(name, "name"); IgniteArgumentCheck.Ensure(typeof(T).IsInterface, "T", "Service proxy type should be an interface: " + typeof(T)); @@ -385,11 +384,8 @@ namespace Apache.Ignite.Core.Impl.Services var platform = GetServiceDescriptors().Cast<ServiceDescriptor>().Single(x => x.Name == name).Platform; - return new ServiceProxy<T>((method, args) => - InvokeProxyMethod(javaProxy, method, args, platform)).GetTransparentProxy(); -#else - throw new Apache.Ignite.Core.Common.IgniteException("Service proxies are not supported on .NET Core: IGNITE-7281"); -#endif + return ServiceProxyFactory<T>.CreateProxy((method, args) => + InvokeProxyMethod(javaProxy, method, args, platform)); } /// <summary> http://git-wip-us.apache.org/repos/asf/ignite/blob/11508d94/modules/platforms/dotnet/Apache.Ignite.Core/Properties/AssemblyInfo.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Properties/AssemblyInfo.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Properties/AssemblyInfo.cs index 7dc615c..0c0f44c 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Properties/AssemblyInfo.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Properties/AssemblyInfo.cs @@ -44,5 +44,7 @@ using System.Runtime.InteropServices; [assembly: InternalsVisibleTo("Apache.Ignite.Core.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a5bf8e0062a26bde53ccf0f8c42ef5b122a22052f99aecacb7028adcc163050324ee3c75ff40eb0cbe2d0426fa20eca03726cad90d7eb882ff47f5361567a82b676a27565f88b2479d7b9354ae0a1e526ee781b6e11de943d8f4a49efb53765f8c954022bede0fca86c133fab038af8dc88b67d6b6e5b9796d6ca490e699efab")] [assembly: InternalsVisibleTo("Apache.Ignite.Benchmarks, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a3e0c1df4cbedbd4ed0e88808401c69b69ec12575ed1c056ac9f448e018fb29af19d236b7b03563aad66c48ab2045e72971ed098d4f65d4cdd38d65abcb39b4f84c626b22ccab2754375f0e8c97dc304fa146f0eddad5cc40a71803a8f15b0b0bb0bff0d4bf0ff6a64bb1044e0d71e6e2405b83fd4c1f7b3e2cfc2e9d50823d4")] [assembly: InternalsVisibleTo("Apache.Ignite.AspNet.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c9380ce05eb74bd7c531f72e9ea615c59d7eceb09bd9795cb3dff1fcf638fd799c2a58a9be42fff156efe1c8cdebb751e27763f6c9a7c80cdc1dc1bbf44283608ef18ccd5017fd57b2b026503637c89c2537f361807f3bdd49265f4d444716159d989342561d324b1a0961640338bb32eaf67f4ae0c95f1b210f65404b0909c6")] - +#if NETCOREAPP2_0 +[assembly: InternalsVisibleTo("Apache.Ignite.Core.Tests.DotNetCore")] +#endif #endif \ No newline at end of file
