You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by al...@apache.org on 2020/05/22 22:45:21 UTC

[ignite] branch master updated: IGNITE-10100 Implement calling .NET service from java - Fixes #7751.

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

alexpl 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 5e3cadc  IGNITE-10100 Implement calling .NET service from java - Fixes #7751.
5e3cadc is described below

commit 5e3cadc9d4cf4a6afcedef02112a9f7228ce78d2
Author: Ivan Daschinskiy <iv...@gmail.com>
AuthorDate: Sat May 23 01:40:48 2020 +0300

    IGNITE-10100 Implement calling .NET service from java - Fixes #7751.
    
    Signed-off-by: Aleksey Plekhanov <pl...@gmail.com>
---
 .../platform/services/PlatformAbstractService.java |   8 +-
 .../platform/services/PlatformService.java         |  13 +
 .../processors/platform/utils/PlatformUtils.java   |  16 +-
 .../processors/service/GridServiceProcessor.java   |  10 +-
 .../processors/service/GridServiceProxy.java       |  81 ++++-
 .../processors/service/IgniteServiceProcessor.java |  14 +-
 .../ignite/platform/PlatformServiceMethod.java     |  48 +++
 .../platform/AbstractPlatformServiceCallTask.java  | 278 +++++++++++++++++
 .../PlatformServiceCallCollectionsTask.java        |  93 ++++++
 .../ignite/platform/PlatformServiceCallTask.java   |  81 +++++
 .../Apache.Ignite.Core.Tests.DotNetCore.csproj     |   1 +
 .../Apache.Ignite.Core.Tests.csproj                |   1 +
 .../Services/CallPlatformServiceTest.cs            | 345 +++++++++++++++++++++
 13 files changed, 966 insertions(+), 23 deletions(-)

diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/services/PlatformAbstractService.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/services/PlatformAbstractService.java
index ea95f73..59d87cb 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/services/PlatformAbstractService.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/services/PlatformAbstractService.java
@@ -181,6 +181,12 @@ public abstract class PlatformAbstractService implements PlatformService, Extern
 
     /** {@inheritDoc} */
     @Override public Object invokeMethod(String mthdName, boolean srvKeepBinary, Object[] args)
+            throws IgniteCheckedException {
+        return invokeMethod(mthdName, srvKeepBinary, false, args);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Object invokeMethod(String mthdName, boolean srvKeepBinary, boolean deserializeResult, Object[] args)
         throws IgniteCheckedException {
         assert ptr != 0;
         assert platformCtx != null;
@@ -213,7 +219,7 @@ public abstract class PlatformAbstractService implements PlatformService, Extern
 
             BinaryRawReaderEx reader = platformCtx.reader(in);
 
-            return PlatformUtils.readInvocationResult(platformCtx, reader);
+            return PlatformUtils.readInvocationResult(platformCtx, reader, deserializeResult);
         }
     }
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/services/PlatformService.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/services/PlatformService.java
index 2d6e1cc..de0b142 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/services/PlatformService.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/services/PlatformService.java
@@ -36,6 +36,19 @@ public interface PlatformService extends Service {
     public Object invokeMethod(String mthdName, boolean srvKeepBinary, Object[] args) throws IgniteCheckedException;
 
     /**
+     * Invokes native service method.
+     *
+     * @param mthdName Method name.
+     * @param srvKeepBinary Server keep binary flag.
+     * @param args Arguments.
+     * @param deserializeResult If {@code true}, call service in cross-platform compatible manner.
+     * @return Resulting data.
+     * @throws org.apache.ignite.IgniteCheckedException If failed.
+     */
+    public Object invokeMethod(String mthdName, boolean srvKeepBinary, boolean deserializeResult, Object[] args)
+            throws IgniteCheckedException;
+
+    /**
      * Gets native pointer.
      *
      * @return Native pointer.
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/utils/PlatformUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/utils/PlatformUtils.java
index 47cc30b..1fa5fe1 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/utils/PlatformUtils.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/utils/PlatformUtils.java
@@ -833,12 +833,26 @@ public class PlatformUtils {
      */
     public static Object readInvocationResult(PlatformContext ctx, BinaryRawReaderEx reader)
         throws IgniteCheckedException {
+        return readInvocationResult(ctx, reader, false);
+    }
+
+    /**
+     * Reads invocation result (of a job/service/etc) using a common protocol.
+     *
+     * @param ctx Platform context.
+     * @param reader Reader.
+     * @param deserialize If {@code true} deserialize invocation result.
+     * @return Result.
+     * @throws IgniteCheckedException When invocation result is an error.
+     */
+    public static Object readInvocationResult(PlatformContext ctx, BinaryRawReaderEx reader, boolean deserialize)
+            throws IgniteCheckedException {
         // 1. Read success flag.
         boolean success = reader.readBoolean();
 
         if (success)
             // 2. Return result as is.
-            return reader.readObjectDetached();
+            return deserialize ? reader.readObject() : reader.readObjectDetached();
         else {
             // 3. Read whether exception is in form of object or string.
             boolean hasException = reader.readBoolean();
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/service/GridServiceProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/service/GridServiceProcessor.java
index 9b5f689..c0f624f 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/service/GridServiceProcessor.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/service/GridServiceProcessor.java
@@ -73,6 +73,7 @@ import org.apache.ignite.internal.processors.cache.query.CacheQuery;
 import org.apache.ignite.internal.processors.cache.query.GridCacheQueryManager;
 import org.apache.ignite.internal.processors.cluster.DiscoveryDataClusterState;
 import org.apache.ignite.internal.processors.cluster.IgniteChangeGlobalStateSupport;
+import org.apache.ignite.internal.processors.platform.services.PlatformService;
 import org.apache.ignite.internal.processors.task.GridInternal;
 import org.apache.ignite.internal.processors.timeout.GridTimeoutObject;
 import org.apache.ignite.internal.util.GridEmptyIterator;
@@ -1030,11 +1031,12 @@ public class GridServiceProcessor extends ServiceProcessorAdapter implements Ign
                 Service svc = ctx.service();
 
                 if (svc != null) {
-                    if (!srvcCls.isAssignableFrom(svc.getClass()))
+                    if (srvcCls.isAssignableFrom(svc.getClass()))
+                        return (T)svc;
+                    else if (!PlatformService.class.isAssignableFrom(svc.getClass())) {
                         throw new IgniteException("Service does not implement specified interface [svcItf=" +
-                            srvcCls.getName() + ", svcCls=" + svc.getClass().getName() + ']');
-
-                    return (T)svc;
+                                srvcCls.getName() + ", svcCls=" + svc.getClass().getName() + ']');
+                    }
                 }
             }
         }
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/service/GridServiceProxy.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/service/GridServiceProxy.java
index ab0422d..64eada3 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/service/GridServiceProxy.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/service/GridServiceProxy.java
@@ -34,7 +34,6 @@ import java.util.Map;
 import java.util.UUID;
 import java.util.concurrent.ThreadLocalRandom;
 import java.util.concurrent.atomic.AtomicReference;
-import org.apache.ignite.Ignite;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.IgniteException;
 import org.apache.ignite.IgniteLogger;
@@ -45,12 +44,15 @@ import org.apache.ignite.internal.GridKernalContext;
 import org.apache.ignite.internal.IgniteEx;
 import org.apache.ignite.internal.cluster.ClusterTopologyCheckedException;
 import org.apache.ignite.internal.managers.communication.GridIoPolicy;
+import org.apache.ignite.internal.processors.platform.PlatformNativeException;
+import org.apache.ignite.internal.processors.platform.services.PlatformService;
 import org.apache.ignite.internal.util.tostring.GridToStringExclude;
 import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.internal.util.typedef.X;
 import org.apache.ignite.internal.util.typedef.internal.S;
 import org.apache.ignite.internal.util.typedef.internal.U;
 import org.apache.ignite.lang.IgniteCallable;
+import org.apache.ignite.platform.PlatformServiceMethod;
 import org.apache.ignite.resources.IgniteInstanceResource;
 import org.apache.ignite.services.Service;
 
@@ -63,6 +65,19 @@ public class GridServiceProxy<T> implements Serializable {
     /** */
     private static final long serialVersionUID = 0L;
 
+    /** */
+    private static final Method PLATFORM_SERVICE_INVOKE_METHOD;
+
+    static {
+        try {
+            PLATFORM_SERVICE_INVOKE_METHOD = PlatformService.class.getMethod("invokeMethod", String.class,
+                    boolean.class, Object[].class);
+        }
+        catch (NoSuchMethodException e) {
+            throw new ExceptionInInitializerError("'invokeMethod' is not defined in " + PlatformService.class.getName());
+        }
+    }
+
     /** Grid logger. */
     @GridToStringExclude
     private final IgniteLogger log;
@@ -113,7 +128,8 @@ public class GridServiceProxy<T> implements Serializable {
         this.ctx = ctx;
         this.name = name;
         this.sticky = sticky;
-        this.waitTimeout = timeout;
+
+        waitTimeout = timeout;
         hasLocNode = hasLocalNode(prj);
 
         log = ctx.log(getClass());
@@ -176,7 +192,7 @@ public class GridServiceProxy<T> implements Serializable {
                             Service svc = svcCtx.service();
 
                             if (svc != null)
-                                return mtd.invoke(svc, args);
+                                return callServiceLocally(svc, mtd, args);
                         }
                     }
                     else {
@@ -185,7 +201,7 @@ public class GridServiceProxy<T> implements Serializable {
                         // Execute service remotely.
                         return ctx.closure().callAsyncNoFailover(
                             GridClosureCallMode.BROADCAST,
-                            new ServiceProxyCallable(mtd.getName(), name, mtd.getParameterTypes(), args),
+                            new ServiceProxyCallable(methodName(mtd), name, mtd.getParameterTypes(), args),
                             Collections.singleton(node),
                             false,
                             waitTimeout,
@@ -245,6 +261,19 @@ public class GridServiceProxy<T> implements Serializable {
     }
 
     /**
+     * @param svc Service to be called.
+     * @param mtd Method to call.
+     * @param args Method args.
+     * @return Invocation result.
+     */
+    private Object callServiceLocally(Service svc, Method mtd, Object[] args) throws Exception {
+        if (svc instanceof PlatformService && !PLATFORM_SERVICE_INVOKE_METHOD.equals(mtd))
+            return ((PlatformService)svc).invokeMethod(methodName(mtd), false, true, args);
+        else
+            return mtd.invoke(svc, args);
+    }
+
+    /**
      * @param sticky Whether multi-node request should be done.
      * @param name Service name.
      * @return Node with deployed service or {@code null} if there is no such node.
@@ -355,6 +384,15 @@ public class GridServiceProxy<T> implements Serializable {
     }
 
     /**
+     * @param mtd Method to invoke.
+     */
+    String methodName(Method mtd) {
+        PlatformServiceMethod ann = mtd.getDeclaredAnnotation(PlatformServiceMethod.class);
+
+        return ann == null ? mtd.getName() : ann.value();
+    }
+
+    /**
      * Invocation handler for service proxy.
      */
     private class ProxyInvocationHandler implements InvocationHandler {
@@ -379,14 +417,14 @@ public class GridServiceProxy<T> implements Serializable {
         private String svcName;
 
         /** Argument types. */
-        private Class[] argTypes;
+        private Class<?>[] argTypes;
 
         /** Args. */
         private Object[] args;
 
         /** Grid instance. */
         @IgniteInstanceResource
-        private transient Ignite ignite;
+        private transient IgniteEx ignite;
 
         /**
          * Empty constructor required for {@link Externalizable}.
@@ -401,7 +439,7 @@ public class GridServiceProxy<T> implements Serializable {
          * @param argTypes Argument types.
          * @param args Arguments for invocation.
          */
-        private ServiceProxyCallable(String mtdName, String svcName, Class[] argTypes, Object[] args) {
+        private ServiceProxyCallable(String mtdName, String svcName, Class<?>[] argTypes, Object[] args) {
             this.mtdName = mtdName;
             this.svcName = svcName;
             this.argTypes = argTypes;
@@ -410,20 +448,41 @@ public class GridServiceProxy<T> implements Serializable {
 
         /** {@inheritDoc} */
         @Override public Object call() throws Exception {
-            ServiceContextImpl svcCtx = ((IgniteEx)ignite).context().service().serviceContext(svcName);
+            ServiceContextImpl ctx = ignite.context().service().serviceContext(svcName);
 
-            if (svcCtx == null || svcCtx.service() == null)
+            if (ctx == null || ctx.service() == null)
                 throw new GridServiceNotFoundException(svcName);
 
             GridServiceMethodReflectKey key = new GridServiceMethodReflectKey(mtdName, argTypes);
 
-            Method mtd = svcCtx.method(key);
+            Method mtd = ctx.method(key);
+
+            if (ctx.service() instanceof PlatformService && mtd == null)
+                return callPlatformService((PlatformService)ctx.service());
+            else
+                return callService(ctx.service(), mtd);
+        }
+
+        /** */
+        private Object callPlatformService(PlatformService srv) {
+            try {
+                return srv.invokeMethod(mtdName, false, true, args);
+            }
+            catch (PlatformNativeException ne) {
+                throw new ServiceProxyException(U.convertException(ne));
+            }
+            catch (Exception e) {
+                throw new ServiceProxyException(e);
+            }
+        }
 
+        /** */
+        private Object callService(Service srv, Method mtd) throws Exception {
             if (mtd == null)
                 throw new GridServiceMethodNotFoundException(svcName, mtdName, argTypes);
 
             try {
-                return mtd.invoke(svcCtx.service(), args);
+                return mtd.invoke(srv, args);
             }
             catch (InvocationTargetException e) {
                 throw new ServiceProxyException(e.getCause());
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/service/IgniteServiceProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/service/IgniteServiceProcessor.java
index 8fc0616..79aa579 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/service/IgniteServiceProcessor.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/service/IgniteServiceProcessor.java
@@ -62,7 +62,7 @@ import org.apache.ignite.internal.processors.cache.DynamicCacheChangeRequest;
 import org.apache.ignite.internal.processors.cluster.ChangeGlobalStateMessage;
 import org.apache.ignite.internal.processors.cluster.DiscoveryDataClusterState;
 import org.apache.ignite.internal.processors.cluster.IgniteChangeGlobalStateSupport;
-import org.apache.ignite.spi.systemview.view.ServiceView;
+import org.apache.ignite.internal.processors.platform.services.PlatformService;
 import org.apache.ignite.internal.util.future.GridCompoundFuture;
 import org.apache.ignite.internal.util.future.GridFinishedFuture;
 import org.apache.ignite.internal.util.future.GridFutureAdapter;
@@ -83,6 +83,7 @@ import org.apache.ignite.spi.communication.CommunicationSpi;
 import org.apache.ignite.spi.discovery.DiscoveryDataBag;
 import org.apache.ignite.spi.discovery.DiscoverySpi;
 import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi;
+import org.apache.ignite.spi.systemview.view.ServiceView;
 import org.apache.ignite.thread.IgniteThreadFactory;
 import org.apache.ignite.thread.OomExceptionHandler;
 import org.jetbrains.annotations.NotNull;
@@ -945,11 +946,12 @@ public class IgniteServiceProcessor extends ServiceProcessorAdapter implements I
                 Service srvc = ctx.service();
 
                 if (srvc != null) {
-                    if (!srvcCls.isAssignableFrom(srvc.getClass()))
-                        throw new IgniteException("Service does not implement specified interface [srvcCls=" +
-                            srvcCls.getName() + ", srvcCls=" + srvc.getClass().getName() + ']');
-
-                    return (T)srvc;
+                    if (srvcCls.isAssignableFrom(srvc.getClass()))
+                        return (T)srvc;
+                    else if (!PlatformService.class.isAssignableFrom(srvc.getClass())) {
+                        throw new IgniteException("Service does not implement specified interface [srvcCls="
+                                + srvcCls.getName() + ", srvcCls=" + srvc.getClass().getName() + ']');
+                    }
                 }
             }
         }
diff --git a/modules/core/src/main/java/org/apache/ignite/platform/PlatformServiceMethod.java b/modules/core/src/main/java/org/apache/ignite/platform/PlatformServiceMethod.java
new file mode 100644
index 0000000..7e90cb6
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/platform/PlatformServiceMethod.java
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+package org.apache.ignite.platform;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation for setting mapping between java interface's method and platform service's method.
+ * Platform service method name is defined in {@link PlatformServiceMethod#value}
+ * <p/>
+ * For example, this annotated java inerface method:
+ * <pre>
+ * &#64;PlatformServiceMethod("SomeMethod")
+ * Object someMethod(Object[] args)
+ * </pre>
+ * will be mapped to {@code SomeMethod}, for example (.NET service):
+ * <pre>
+ * object SomeMethod(object[] args)
+ * </pre>
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD})
+public @interface PlatformServiceMethod {
+    /**
+     * Method name in corresponding platform service.
+     */
+    String value();
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/platform/AbstractPlatformServiceCallTask.java b/modules/core/src/test/java/org/apache/ignite/platform/AbstractPlatformServiceCallTask.java
new file mode 100644
index 0000000..8609314
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/platform/AbstractPlatformServiceCallTask.java
@@ -0,0 +1,278 @@
+/*
+ * 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.
+ */
+
+package org.apache.ignite.platform;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.UUID;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.binary.BinaryObjectException;
+import org.apache.ignite.binary.BinaryReader;
+import org.apache.ignite.binary.BinaryWriter;
+import org.apache.ignite.binary.Binarylizable;
+import org.apache.ignite.cluster.ClusterNode;
+import org.apache.ignite.compute.ComputeJob;
+import org.apache.ignite.compute.ComputeJobAdapter;
+import org.apache.ignite.compute.ComputeJobResult;
+import org.apache.ignite.compute.ComputeTaskAdapter;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.resources.IgniteInstanceResource;
+import org.apache.ignite.services.ServiceDescriptor;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Abstract task to test platform service.
+ */
+public abstract class AbstractPlatformServiceCallTask extends ComputeTaskAdapter<Object[], Object> {
+    /** */
+    @SuppressWarnings("unused")
+    @IgniteInstanceResource
+    private transient Ignite ignite;
+
+    /** {@inheritDoc} */
+    @Override public Map<? extends ComputeJob, ClusterNode> map(List<ClusterNode> subgrid, @Nullable Object[] arg)
+            throws IgniteException {
+        assert arg.length == 2;
+
+        String srvcName = (String)arg[0];
+        boolean loc = (Boolean)arg[1];
+
+        Optional<ServiceDescriptor> desc = ignite.services().serviceDescriptors().stream()
+                .filter(d -> d.name().equals(srvcName)).findAny();
+
+        assert desc.isPresent();
+
+        ClusterNode node;
+
+        Set<UUID> srvTop = desc.get().topologySnapshot().keySet();
+
+        if (loc) {
+            UUID nodeId = F.rand(srvTop);
+
+            node = ignite.cluster().node(nodeId);
+        }
+        else {
+            node = ignite.cluster().nodes().stream().filter(n -> !srvTop.contains(n.id())).findAny().orElse(null);
+
+            assert node != null;
+        }
+
+        return Collections.singletonMap(createJob(srvcName), node);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Object reduce(List<ComputeJobResult> results) throws IgniteException {
+        return results.get(0).getData();
+    }
+
+    /**
+     * Create task job.
+     *
+     * @param svcName Service name
+     * @return Instance of task job.
+     */
+    abstract ComputeJobAdapter createJob(String svcName);
+
+    /** */
+    protected abstract static class AbstractServiceCallJob extends ComputeJobAdapter {
+        /** */
+        protected final String srvcName;
+
+        /**
+         * @param srvcName Service name.
+         */
+        protected AbstractServiceCallJob(String srvcName) {
+            assert srvcName != null;
+
+            this.srvcName = srvcName;
+        }
+
+        /** {@inheritDoc} */
+        @Override public Object execute() throws IgniteException {
+            try {
+                runTest();
+
+                return null;
+            }
+            catch (Exception e) {
+                throw new IgniteException(e);
+            }
+        }
+
+        /**
+         * Test method to call platform service.
+         */
+        abstract void runTest();
+    }
+
+    /** */
+    protected static void assertEquals(Object exp, Object res) throws IgniteException {
+        if ((exp != null && !exp.equals(res)) || (res != null && !res.equals(exp)))
+            throw new IgniteException(String.format("Expected equals to %s, got %s", exp, res));
+    }
+
+    /** */
+    protected static void assertTrue(boolean res) {
+        assertEquals(true, res);
+    }
+
+    /** */
+    public interface TestPlatformService
+    {
+        /** */
+        @PlatformServiceMethod("get_NodeId")
+        UUID getNodeId();
+
+        /** */
+        @PlatformServiceMethod("get_GuidProp")
+        UUID getGuidProp();
+
+        /** */
+        @PlatformServiceMethod("set_GuidProp")
+        void setGuidProp(UUID val);
+
+        /** */
+        @PlatformServiceMethod("get_ValueProp")
+        TestValue getValueProp();
+
+        /** */
+        @PlatformServiceMethod("set_ValueProp")
+        void setValueProp(TestValue val);
+
+        /** */
+        @PlatformServiceMethod("ErrorMethod")
+        void errorMethod();
+
+        /** */
+        @PlatformServiceMethod("AddOneToEach")
+        TestValue[] addOneToEach(TestValue[] col);
+
+        /** */
+        @PlatformServiceMethod("AddOneToEachCollection")
+        Collection<TestValue> addOneToEachCollection(Collection<TestValue> col);
+
+        /** */
+        @PlatformServiceMethod("AddOneToEachDictionary")
+        Map<TestKey, TestValue> addOneToEachDictionary(Map<TestKey, TestValue> dict);
+
+        /** */
+        @PlatformServiceMethod("AddOne")
+        BinarizableTestValue addOne(BinarizableTestValue val);
+    }
+
+    /** */
+    public static class TestKey {
+        /** */
+        private final int id;
+
+        /** */
+        public TestKey(int id) {
+            this.id = id;
+        }
+
+        /** */
+        public int id() {
+            return id;
+        }
+
+        /** {@inheritDoc} */
+        @Override public boolean equals(Object o) {
+            if (this == o)
+                return true;
+
+            if (o == null || getClass() != o.getClass())
+                return false;
+
+            return id == ((TestKey)o).id;
+        }
+
+        /** {@inheritDoc} */
+        @Override public int hashCode() {
+            return Objects.hash(id);
+        }
+    }
+
+    /** */
+    public static class TestValue {
+        /** */
+        protected int id;
+
+        /** */
+        protected String name;
+
+        /** */
+        public TestValue(int id, String name) {
+            this.id = id;
+            this.name = name;
+        }
+
+        /** */
+        public int id() {
+            return id;
+        }
+
+        /** */
+        public String name() {
+            return name;
+        }
+
+        /** {@inheritDoc} */
+        @Override public boolean equals(Object o) {
+            if (this == o)
+                return true;
+
+            if (o == null || getClass() != o.getClass())
+                return false;
+
+            TestValue val = (TestValue) o;
+
+            return id == val.id && Objects.equals(name, val.name);
+        }
+
+        /** {@inheritDoc} */
+        @Override public int hashCode() {
+            return Objects.hash(id, name);
+        }
+    }
+
+    /** */
+    public static class BinarizableTestValue extends TestValue implements Binarylizable {
+        /** */
+        public BinarizableTestValue(int id, String name) {
+            super(id, name);
+        }
+
+        /** {@inheritDoc} */
+        @Override public void writeBinary(BinaryWriter writer) throws BinaryObjectException {
+            writer.writeInt("id", id);
+            writer.writeString("name", name);
+        }
+
+        /** {@inheritDoc} */
+        @Override public void readBinary(BinaryReader reader) throws BinaryObjectException {
+            id = reader.readInt("id");
+            name = reader.readString("name");
+        }
+    }
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/platform/PlatformServiceCallCollectionsTask.java b/modules/core/src/test/java/org/apache/ignite/platform/PlatformServiceCallCollectionsTask.java
new file mode 100644
index 0000000..7e2806b
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/platform/PlatformServiceCallCollectionsTask.java
@@ -0,0 +1,93 @@
+/*
+ * 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.
+ */
+
+package org.apache.ignite.platform;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.compute.ComputeJobAdapter;
+import org.apache.ignite.internal.util.typedef.T2;
+import org.apache.ignite.resources.IgniteInstanceResource;
+
+/**
+ * Test invoke methods with collections and arrays as arguments and return type.
+ */
+public class PlatformServiceCallCollectionsTask extends AbstractPlatformServiceCallTask {
+    /** {@inheritDoc} */
+    @Override ComputeJobAdapter createJob(String svcName) {
+        return new PlatformServiceCallCollectionsJob(svcName);
+    }
+
+    /** */
+    public static class PlatformServiceCallCollectionsJob extends AbstractServiceCallJob {
+        /** */
+        @SuppressWarnings("unused")
+        @IgniteInstanceResource
+        private transient Ignite ignite;
+
+        /**
+         * @param svcName Service name.
+         */
+        PlatformServiceCallCollectionsJob(String svcName) {
+            super(svcName);
+        }
+
+        /** {@inheritDoc} */
+        @Override void runTest() {
+            TestPlatformService srv = ignite.services().serviceProxy(srvcName, TestPlatformService.class, false);
+
+            {
+                TestValue[] exp = IntStream.range(0, 10).mapToObj(i -> new TestValue(i, "name_" + i))
+                        .toArray(TestValue[]::new);
+
+                TestValue[] res = srv.addOneToEach(exp);
+
+                assertEquals(exp.length, res.length);
+
+                for (int i = 0; i < exp.length; i++)
+                    assertEquals(exp[i].id() + 1, res[i].id());
+            }
+
+            {
+                List<TestValue> exp = IntStream.range(0, 10).mapToObj(i -> new TestValue(i, "name_" + i))
+                        .collect(Collectors.toList());
+
+                Collection<TestValue> res = srv.addOneToEachCollection(exp);
+
+                assertEquals(exp.size(), res.size());
+
+                res.forEach(v -> assertEquals(exp.get(v.id() - 1).name(), v.name()));
+            }
+
+            {
+                Map<TestKey, TestValue> exp = IntStream.range(0, 10)
+                        .mapToObj(i -> new T2<>(new TestKey(i), new TestValue(i, "name_" + i)))
+                        .collect(Collectors.toMap(T2::getKey, T2::getValue));
+
+                Map<TestKey, TestValue> res = srv.addOneToEachDictionary(exp);
+
+                assertEquals(exp.size(), res.size());
+
+                res.forEach((k, v) -> assertEquals(exp.get(new TestKey(k.id() - 1)).name(), v.name()));
+            }
+        }
+    }
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/platform/PlatformServiceCallTask.java b/modules/core/src/test/java/org/apache/ignite/platform/PlatformServiceCallTask.java
new file mode 100644
index 0000000..f39a3ee
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/platform/PlatformServiceCallTask.java
@@ -0,0 +1,81 @@
+/*
+ * 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.
+ */
+
+package org.apache.ignite.platform;
+
+import java.util.UUID;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.compute.ComputeJobAdapter;
+import org.apache.ignite.internal.processors.platform.PlatformNativeException;
+import org.apache.ignite.internal.processors.platform.services.PlatformService;
+import org.apache.ignite.resources.IgniteInstanceResource;
+import org.apache.ignite.testframework.GridTestUtils;
+
+/**
+ *  Basic task to calling {@link PlatformService} from Java.
+ */
+public class PlatformServiceCallTask extends AbstractPlatformServiceCallTask {
+    /** {@inheritDoc} */
+    @Override ComputeJobAdapter createJob(String svcName) {
+        return new PlatformServiceCallJob(svcName);
+    }
+
+    /** */
+    static class PlatformServiceCallJob extends AbstractServiceCallJob {
+        /** */
+        @SuppressWarnings("unused")
+        @IgniteInstanceResource
+        private transient Ignite ignite;
+
+        /**
+         * @param srvcName Service name.
+         */
+        PlatformServiceCallJob(String srvcName) {
+            super(srvcName);
+        }
+
+        /** {@inheritDoc} */
+        @Override void runTest() {
+            TestPlatformService srv = ignite.services().serviceProxy(srvcName, TestPlatformService.class, false);
+
+            {
+                UUID nodeId = srv.getNodeId();
+                assertTrue(ignite.cluster().nodes().stream().anyMatch(n -> n.id().equals(nodeId)));
+            }
+
+            {
+                UUID expUuid = UUID.randomUUID();
+                srv.setGuidProp(expUuid);
+                assertEquals(expUuid, srv.getGuidProp());
+            }
+
+            {
+                TestValue exp = new TestValue(1, "test");
+                srv.setValueProp(exp);
+                assertEquals(exp, srv.getValueProp());
+            }
+
+            {
+                PlatformNativeException nativeEx = (PlatformNativeException)GridTestUtils
+                        .assertThrowsWithCause(srv::errorMethod, PlatformNativeException.class)
+                        .getCause();
+
+                assertTrue(nativeEx.toString().contains("Failed method"));
+            }
+        }
+    }
+}
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 3465c7d..0178a0c 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
@@ -310,6 +310,7 @@
     <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\CallPlatformServiceTest.cs" Link="Services\CallPlatformServiceTest.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" />
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.csproj b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.csproj
index 028992f..d32da1c 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.csproj
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.csproj
@@ -366,6 +366,7 @@
     <Compile Include="Query\ImplicitBinarizablePerson.cs" />
     <Compile Include="Query\NoDefBinarizablePerson.cs" />
     <Compile Include="Query\BinarizablePerson.cs" />
+    <Compile Include="Services\CallPlatformServiceTest.cs" />
     <Compile Include="Services\ServicesTest.cs" />
     <Compile Include="Services\ServicesTestAsync.cs" />
     <Compile Include="Services\ServiceProxyTest.cs" />
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/CallPlatformServiceTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/CallPlatformServiceTest.cs
new file mode 100644
index 0000000..b06671a
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/CallPlatformServiceTest.cs
@@ -0,0 +1,345 @@
+/*
+ * 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.Tests.Services
+{
+    using System;
+    using System.Collections;
+    using System.Linq;
+    using Apache.Ignite.Core.Binary;
+    using Apache.Ignite.Core.Resource;
+    using Apache.Ignite.Core.Services;
+    using NUnit.Framework;
+
+    /// <summary>
+    /// Tests calling platform service from java.
+    /// </summary>
+    public class CallPlatformServiceTest
+    {
+        /** */
+        private const string ServiceName = "TestPlatformService";
+
+        /** */
+        private const string CheckTaskName = "org.apache.ignite.platform.PlatformServiceCallTask";
+
+        /** */
+        private const string CheckCollectionsTaskName = "org.apache.ignite.platform.PlatformServiceCallCollectionsTask";
+        
+        /** */
+        protected IIgnite Grid1;
+
+        /** */
+        protected IIgnite Grid2;
+
+        /** */
+        protected IIgnite Grid3;
+        
+        /// <summary>
+        /// Start grids and deploy test service.
+        /// </summary>
+        [SetUp]
+        public void SetUp()
+        {
+            StartGrids();
+        }
+        
+        /// <summary>
+        /// Stop grids after test.
+        /// </summary>
+        [TearDown]
+        public void TearDown()
+        {
+            StopGrids();
+        }
+
+        /// <summary>
+        /// Tests call a platform service by invoking a special compute java task,
+        /// in which real invocation of the service is made.
+        /// <para/>
+        /// Tests common methods.
+        /// <param name="local">If true call on local node.</param>
+        /// </summary>
+        [Test]
+        public void TestCallPlatformService([Values(true, false)] bool local)
+        {
+            var cfg = new ServiceConfiguration
+            {
+                Name = ServiceName,
+                TotalCount = 1,
+                Service = new TestPlatformService()
+            };
+            
+            Grid1.GetServices().Deploy(cfg);
+
+            Grid1.GetCompute().ExecuteJavaTask<object>(CheckTaskName, new object[] { ServiceName, local });
+        }
+        
+        /// <summary>
+        /// Tests call a platform service by invoking a special compute java task,
+        /// in which real invocation of the service is made.
+        /// <para/>
+        /// Tests collections method.
+        /// <param name="local">If true call on local node.</param>
+        /// </summary>
+        [Test]
+        public void TestCallPlatformServiceCollections([Values(true, false)] bool local)
+        {
+            var cfg = new ServiceConfiguration
+            {
+                Name = ServiceName,
+                TotalCount = 1,
+                Service = new TestPlatformService()
+            };
+            
+            Grid1.GetServices().Deploy(cfg);
+
+            Grid1.GetCompute().ExecuteJavaTask<object>(CheckCollectionsTaskName, new object[] { ServiceName, local });
+        }
+        
+        /// <summary>
+        /// Starts the grids.
+        /// </summary>
+        private void StartGrids()
+        {
+            if (Grid1 != null)
+                return;
+
+            Grid1 = Ignition.Start(GetConfiguration(1));
+            Grid2 = Ignition.Start(GetConfiguration(2));
+            Grid3 = Ignition.Start(GetConfiguration(3));
+        }
+
+        /// <summary>
+        /// Stops the grids.
+        /// </summary>
+        private void StopGrids()
+        {
+            Grid1 = Grid2 = Grid3 = null;
+
+            Ignition.StopAll(true);
+        }
+        
+        /// <summary>
+        /// Gets the Ignite configuration.
+        /// </summary>
+        private IgniteConfiguration GetConfiguration(int idx)
+        {
+            return new IgniteConfiguration(TestUtils.GetTestConfiguration())
+            {
+                IgniteInstanceName = "grid" + idx,
+                BinaryConfiguration = new BinaryConfiguration(typeof(TestKey), typeof(TestValue), 
+                    typeof(BinarizableTestValue))
+                {
+                    NameMapper = BinaryBasicNameMapper.SimpleNameInstance
+                }
+            };
+        }
+        
+        /** */
+        public interface ITestPlatformService : IService
+        {
+            /** */
+            Guid NodeId { get; }
+            
+            /** */
+            Guid? GuidProp { get; set; }
+            
+            /** */
+            TestValue ValueProp { get; set; }
+
+            /** */
+            void ErrorMethod();
+
+            /** */
+            TestValue[] AddOneToEach(TestValue[] arr);
+
+            /** */
+            ICollection AddOneToEachCollection(ICollection col);
+
+            /** */
+            IDictionary AddOneToEachDictionary(IDictionary dict);
+
+            /** */
+            BinarizableTestValue AddOne(BinarizableTestValue val);
+        }
+
+        #pragma warning disable 649
+        
+        /** */
+        private class TestPlatformService : ITestPlatformService
+        {
+            /** */
+            [InstanceResource]
+            private IIgnite _grid;
+
+            /** <inheritdoc /> */
+            public Guid NodeId
+            {
+                get { return _grid.GetCluster().GetLocalNode().Id;}
+            }
+
+            /** <inheritdoc /> */
+            public Guid? GuidProp { get; set; }
+            
+            /** <inheritdoc /> */
+            public TestValue ValueProp { get; set; }
+
+            /** <inheritdoc /> */
+            public void ErrorMethod()
+            {
+                throw new Exception("Failed method");
+            }
+            
+            /** <inheritdoc /> */
+            public TestValue[] AddOneToEach(TestValue[] arr)
+            {
+                return arr.Select(val => new TestValue()
+                {
+                    Id = val.Id + 1,
+                    Name = val.Name
+
+                }).ToArray();
+            }
+
+            /** <inheritdoc /> */
+            public ICollection AddOneToEachCollection(ICollection col)
+            {
+                var res =  col.Cast<TestValue>().Select(val => new TestValue()
+                {
+                    Id = val.Id + 1,
+                    Name = val.Name
+                
+                }).ToList();
+
+                return new ArrayList(res);
+            }
+
+            /** <inheritdoc /> */
+            public IDictionary AddOneToEachDictionary(IDictionary dict)
+            {
+                var res = new Hashtable();
+
+                foreach (DictionaryEntry pair in dict)
+                {
+                    var k = new TestKey(((TestKey) pair.Key).Id + 1);
+                    
+                    var v = new TestValue()
+                    {
+                        Id = ((TestValue)pair.Value).Id + 1,
+                        Name = ((TestValue)pair.Value).Name
+                    };
+                    
+                    res.Add(k, v);
+                }
+                
+                return res;
+            }
+
+            /** <inheritdoc /> */
+            public BinarizableTestValue AddOne(BinarizableTestValue val)
+            {
+                return new BinarizableTestValue()
+                {
+                    Id = val.Id + 1,
+                    Name = val.Name
+                };
+            }
+
+            /** <inheritdoc /> */
+            public void Init(IServiceContext context)
+            {
+                // No-op.
+            }
+
+            /** <inheritdoc /> */
+            public void Execute(IServiceContext context)
+            {
+                // No-op.
+            }
+
+            /** <inheritdoc /> */
+            public void Cancel(IServiceContext context)
+            {
+                // No-op;
+            }
+        }
+        
+        #pragma warning restore 649
+
+        /** */
+        public class TestKey
+        {
+            /** */
+            public TestKey(int id)
+            {
+                Id = id;
+            }
+
+            /** */
+            public int Id { get; set; }
+            
+            /** <inheritdoc /> */
+            public override int GetHashCode()
+            {
+                return Id;
+            }
+            
+            /** <inheritdoc /> */
+            public override bool Equals(object obj)
+            {
+                if (ReferenceEquals(null, obj)) 
+                    return false;
+                
+                if (ReferenceEquals(this, obj)) 
+                    return true;
+                
+                if (obj.GetType() != GetType()) 
+                    return false;
+                
+                return Id == ((TestKey)obj).Id;
+            }
+        }
+
+        /** */
+        public class TestValue
+        {
+            /** */
+            public int Id { get; set; }
+            
+            /** */
+            public string Name { get; set; }
+        }
+
+        /** */
+        public class BinarizableTestValue : TestValue, IBinarizable
+        {
+            /** <inheritdoc /> */
+            public void WriteBinary(IBinaryWriter writer)
+            {
+                writer.WriteInt("id", Id);
+                writer.WriteString("name", Name);
+            }
+
+            /** <inheritdoc /> */
+            public void ReadBinary(IBinaryReader reader)
+            {
+                Id = reader.ReadInt("id");
+                Name = reader.ReadString("name");
+            }
+        }
+    }
+}