diff --git a/modules/core/src/main/java/org/apache/ignite/client/ClientServices.java b/modules/core/src/main/java/org/apache/ignite/client/ClientServices.java
new file mode 100644
index 0000000000000..fe2408e6b5f52
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/client/ClientServices.java
@@ -0,0 +1,56 @@
+/*
+ * 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.client;
+
+/**
+ * Thin client services facade.
+ */
+public interface ClientServices {
+ /**
+ * Gets the cluster group to which this {@code ClientServices} instance belongs.
+ *
+ * @return Cluster group to which this {@code ClientServices} instance belongs.
+ */
+ public ClientClusterGroup clusterGroup();
+
+ /**
+ * Gets a remote handle on the service.
+ *
+ * Note: There are no guarantees that each method invocation for the same proxy will always contact the same remote
+ * service (on the same remote node).
+ *
+ * @param name Service name.
+ * @param svcItf Interface for the service.
+ * @return Proxy over remote service.
+ */
+ public T serviceProxy(String name, Class super T> svcItf);
+
+ /**
+ * Gets a remote handle on the service with timeout.
+ *
+ * Note: There are no guarantees that each method invocation for the same proxy will always contact the same remote
+ * service (on the same remote node).
+ *
+ * @param name Service name.
+ * @param svcItf Interface for the service.
+ * @param timeout If greater than 0 created proxy will wait for service availability only specified time,
+ * and will limit remote service invocation time.
+ * @return Proxy over remote service.
+ */
+ public T serviceProxy(String name, Class super T> svcItf, long timeout);
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/client/IgniteClient.java b/modules/core/src/main/java/org/apache/ignite/client/IgniteClient.java
index fa8cf8f5f2ea4..1d10a8aeda8b1 100644
--- a/modules/core/src/main/java/org/apache/ignite/client/IgniteClient.java
+++ b/modules/core/src/main/java/org/apache/ignite/client/IgniteClient.java
@@ -119,4 +119,24 @@ public interface IgniteClient extends AutoCloseable {
* @return Client cluster facade.
*/
public ClientCluster cluster();
+
+ /**
+ * Gets {@code services} facade over all cluster nodes started in server mode.
+ *
+ * @return Services facade over all cluster nodes started in server mode.
+ */
+ public ClientServices services();
+
+ /**
+ * Gets {@code services} facade over nodes within the cluster group. All operations
+ * on the returned {@link ClientServices} instance will only include nodes from
+ * the specified cluster group.
+ *
+ * Note: In some cases there will be additional requests for each service invocation from client to server
+ * to resolve cluster group.
+ *
+ * @param grp Cluster group.
+ * @return {@code Services} functionality over given cluster group.
+ */
+ public ClientServices services(ClientClusterGroup grp);
}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/client/thin/ClientOperation.java b/modules/core/src/main/java/org/apache/ignite/internal/client/thin/ClientOperation.java
index 3cf228cff4d01..34fb17d560606 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/client/thin/ClientOperation.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/client/thin/ClientOperation.java
@@ -74,7 +74,9 @@ enum ClientOperation {
/** Get nodes info by IDs. */CLUSTER_GROUP_GET_NODE_INFO(5101),
/** Execute compute task. */COMPUTE_TASK_EXECUTE(6000),
- /** Finished compute task notification. */COMPUTE_TASK_FINISHED(6001, true);
+ /** Finished compute task notification. */COMPUTE_TASK_FINISHED(6001, true),
+
+ /** Invoke service. */SERVICE_INVOKE(7000);
/** Code. */
private final int code;
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/client/thin/ClientServicesImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/client/thin/ClientServicesImpl.java
new file mode 100644
index 0000000000000..5bf523408c143
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/client/thin/ClientServicesImpl.java
@@ -0,0 +1,180 @@
+/*
+ * 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.internal.client.thin;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Collection;
+import java.util.UUID;
+import org.apache.ignite.client.ClientClusterGroup;
+import org.apache.ignite.client.ClientException;
+import org.apache.ignite.client.ClientServices;
+import org.apache.ignite.internal.binary.BinaryRawWriterEx;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.internal.util.typedef.internal.A;
+import org.apache.ignite.platform.PlatformServiceMethod;
+
+/**
+ * Implementation of {@link ClientServices}.
+ */
+class ClientServicesImpl implements ClientServices {
+ /** Channel. */
+ private final ReliableChannel ch;
+
+ /** Binary marshaller. */
+ private final ClientBinaryMarshaller marsh;
+
+ /** Utils for serialization/deserialization. */
+ private final ClientUtils utils;
+
+ /** Cluster group. */
+ private final ClientClusterGroupImpl grp;
+
+ /** Constructor. */
+ ClientServicesImpl(ReliableChannel ch, ClientBinaryMarshaller marsh, ClientClusterGroupImpl grp) {
+ this.ch = ch;
+ this.marsh = marsh;
+ this.grp = grp;
+
+ utils = new ClientUtils(marsh);
+ }
+
+ /** {@inheritDoc} */
+ @Override public ClientClusterGroup clusterGroup() {
+ return grp;
+ }
+
+ /** {@inheritDoc} */
+ @Override public T serviceProxy(String name, Class super T> svcItf) {
+ return serviceProxy(name, svcItf, 0);
+ }
+
+ /** {@inheritDoc} */
+ @Override public T serviceProxy(String name, Class super T> svcItf, long timeout) {
+ A.notNullOrEmpty(name, "name");
+ A.notNull(svcItf, "svcItf");
+
+ return (T)Proxy.newProxyInstance(svcItf.getClassLoader(), new Class[] {svcItf},
+ new ServiceInvocationHandler<>(name, timeout, grp));
+ }
+
+ /**
+ * Gets services facade over the specified cluster group.
+ *
+ * @param grp Cluster group.
+ */
+ ClientServices withClusterGroup(ClientClusterGroupImpl grp) {
+ A.notNull(grp, "grp");
+
+ return new ClientServicesImpl(ch, marsh, grp);
+ }
+
+ /**
+ * Service invocation handler.
+ */
+ private class ServiceInvocationHandler implements InvocationHandler {
+ /** Flag "Has parameter types" mask. */
+ private static final byte FLAG_PARAMETER_TYPES_MASK = 0x02;
+
+ /** Service name. */
+ private final String name;
+
+ /** Timeout. */
+ private final long timeout;
+
+ /** Cluster group. */
+ private final ClientClusterGroupImpl grp;
+
+ /**
+ * @param name Service name.
+ * @param timeout Timeout.
+ */
+ private ServiceInvocationHandler(String name, long timeout, ClientClusterGroupImpl grp) {
+ this.name = name;
+ this.timeout = timeout;
+ this.grp = grp;
+ }
+
+ /** {@inheritDoc} */
+ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ try {
+ Collection nodeIds = grp.nodeIds();
+
+ if (nodeIds != null && nodeIds.isEmpty())
+ throw new ClientException("Cluster group is empty.");
+
+ return ch.service(ClientOperation.SERVICE_INVOKE,
+ req -> writeServiceInvokeRequest(req, nodeIds, method, args),
+ res -> utils.readObject(res.in(), false)
+ );
+ }
+ catch (ClientError e) {
+ throw new ClientException(e);
+ }
+ }
+
+ /**
+ * @param ch Payload output channel.
+ */
+ private void writeServiceInvokeRequest(
+ PayloadOutputChannel ch,
+ Collection nodeIds,
+ Method method,
+ Object[] args
+ ) {
+ ch.clientChannel().protocolCtx().checkFeatureSupported(ProtocolBitmaskFeature.SERVICE_INVOKE);
+
+ try (BinaryRawWriterEx writer = utils.createBinaryWriter(ch.out())) {
+ writer.writeString(name);
+ writer.writeByte(FLAG_PARAMETER_TYPES_MASK); // Flags.
+ writer.writeLong(timeout);
+
+ if (nodeIds == null)
+ writer.writeInt(0);
+ else {
+ writer.writeInt(nodeIds.size());
+
+ for (UUID nodeId : nodeIds) {
+ writer.writeLong(nodeId.getMostSignificantBits());
+ writer.writeLong(nodeId.getLeastSignificantBits());
+ }
+ }
+
+ PlatformServiceMethod ann = method.getDeclaredAnnotation(PlatformServiceMethod.class);
+
+ writer.writeString(ann != null ? ann.value() : method.getName());
+
+ Class>[] paramTypes = method.getParameterTypes();
+
+ if (F.isEmpty(args))
+ writer.writeInt(0);
+ else {
+ writer.writeInt(args.length);
+
+ assert args.length == paramTypes.length : "args=" + args.length + ", types=" + paramTypes.length;
+
+ for (int i = 0; i < args.length; i++) {
+ writer.writeInt(marsh.context().typeId(paramTypes[i].getName()));
+ writer.writeObject(args[i]);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/client/thin/ClientUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/client/thin/ClientUtils.java
index a781546ab3d6d..d59200f343d6b 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/client/thin/ClientUtils.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/client/thin/ClientUtils.java
@@ -18,6 +18,7 @@
package org.apache.ignite.internal.client.thin;
import java.io.IOException;
+import java.lang.reflect.Array;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Arrays;
@@ -55,6 +56,7 @@
import org.apache.ignite.internal.binary.BinaryReaderExImpl;
import org.apache.ignite.internal.binary.BinaryReaderHandles;
import org.apache.ignite.internal.binary.BinarySchema;
+import org.apache.ignite.internal.binary.BinaryThreadLocalContext;
import org.apache.ignite.internal.binary.BinaryUtils;
import org.apache.ignite.internal.binary.BinaryWriterExImpl;
import org.apache.ignite.internal.binary.streams.BinaryHeapInputStream;
@@ -527,6 +529,13 @@ void writeObject(BinaryOutputStream out, Object obj) {
out.writeByteArray(marsh.marshal(obj));
}
+ /**
+ * @param out Output stream.
+ */
+ BinaryRawWriterEx createBinaryWriter(BinaryOutputStream out) {
+ return new BinaryWriterExImpl(marsh.context(), out, BinaryThreadLocalContext.get().schemaHolder(), null);
+ }
+
/** Read Ignite binary object from input stream. */
T readObject(BinaryInputStream in, boolean keepBinary) {
if (keepBinary)
@@ -590,7 +599,7 @@ private Object[] unwrapArray(Object[] arr, BinaryReaderHandles hnds) {
if (BinaryUtils.knownArray(arr))
return arr;
- Object[] res = new Object[arr.length];
+ Object[] res = (Object[])Array.newInstance(arr.getClass().getComponentType(), arr.length);
for (int i = 0; i < arr.length; i++)
res[i] = unwrapBinary(arr[i], hnds);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/client/thin/ProtocolBitmaskFeature.java b/modules/core/src/main/java/org/apache/ignite/internal/client/thin/ProtocolBitmaskFeature.java
index 3b705b9637fae..c86dd7b181fe8 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/client/thin/ProtocolBitmaskFeature.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/client/thin/ProtocolBitmaskFeature.java
@@ -37,7 +37,10 @@ public enum ProtocolBitmaskFeature {
CLUSTER_STATES(2),
/** Cluster groups. */
- CLUSTER_GROUPS(4);
+ CLUSTER_GROUPS(4),
+
+ /** Invoke service methods. */
+ SERVICE_INVOKE(5);
/** */
private static final EnumSet ALL_FEATURES_AS_ENUM_SET =
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/client/thin/ProtocolContext.java b/modules/core/src/main/java/org/apache/ignite/internal/client/thin/ProtocolContext.java
index e5be57582feef..ec8fb6e2f51bf 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/client/thin/ProtocolContext.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/client/thin/ProtocolContext.java
@@ -18,6 +18,7 @@
package org.apache.ignite.internal.client.thin;
import java.util.EnumSet;
+import org.apache.ignite.client.ClientFeatureNotSupportedByServerException;
/**
* Protocol Context.
@@ -45,6 +46,17 @@ public boolean isFeatureSupported(ProtocolBitmaskFeature feature) {
return features.contains(feature);
}
+ /**
+ * Check that feature is supported by the server.
+ *
+ * @param feature Feature.
+ * @throws ClientFeatureNotSupportedByServerException If feature is not supported by the server.
+ */
+ public void checkFeatureSupported(ProtocolBitmaskFeature feature) throws ClientFeatureNotSupportedByServerException {
+ if (!isFeatureSupported(feature))
+ throw new ClientFeatureNotSupportedByServerException(feature);
+ }
+
/**
* @return {@code true} if protocol version feature supported.
*/
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/client/thin/TcpIgniteClient.java b/modules/core/src/main/java/org/apache/ignite/internal/client/thin/TcpIgniteClient.java
index e4c6b9fcdab5c..3db66f5bf8e3e 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/client/thin/TcpIgniteClient.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/client/thin/TcpIgniteClient.java
@@ -38,6 +38,7 @@
import org.apache.ignite.client.ClientClusterGroup;
import org.apache.ignite.client.ClientCompute;
import org.apache.ignite.client.ClientException;
+import org.apache.ignite.client.ClientServices;
import org.apache.ignite.client.ClientTransactions;
import org.apache.ignite.client.IgniteClient;
import org.apache.ignite.configuration.ClientConfiguration;
@@ -78,6 +79,9 @@ public class TcpIgniteClient implements IgniteClient {
/** Cluster facade. */
private final ClientClusterImpl cluster;
+ /** Services facade. */
+ private final ClientServicesImpl services;
+
/** Marshaller. */
private final ClientBinaryMarshaller marsh;
@@ -115,6 +119,8 @@ private TcpIgniteClient(ClientConfiguration cfg) throws ClientException {
cluster = new ClientClusterImpl(ch, marsh);
compute = new ClientComputeImpl(ch, marsh, cluster.defaultClusterGroup());
+
+ services = new ClientServicesImpl(ch, marsh, cluster.defaultClusterGroup());
}
/** {@inheritDoc} */
@@ -228,6 +234,16 @@ private TcpIgniteClient(ClientConfiguration cfg) throws ClientException {
return cluster;
}
+ /** {@inheritDoc} */
+ @Override public ClientServices services() {
+ return services;
+ }
+
+ /** {@inheritDoc} */
+ @Override public ClientServices services(ClientClusterGroup grp) {
+ return services.withClusterGroup((ClientClusterGroupImpl)grp);
+ }
+
/**
* Initializes new instance of {@link IgniteClient}.
*
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/ClientBitmaskFeature.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/ClientBitmaskFeature.java
index 3da4e11587d47..3ec7530452ff1 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/ClientBitmaskFeature.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/ClientBitmaskFeature.java
@@ -37,7 +37,10 @@ public enum ClientBitmaskFeature implements ThinProtocolFeature {
CLUSTER_GROUP_GET_NODES_ENDPOINTS(3),
/** Cluster groups. */
- CLUSTER_GROUPS(4);
+ CLUSTER_GROUPS(4),
+
+ /** Service invocation. */
+ SERVICE_INVOKE(5);
/** */
private static final EnumSet ALL_FEATURES_AS_ENUM_SET =
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/ClientMessageParser.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/ClientMessageParser.java
index 37ef13123e980..94d32bc53be71 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/ClientMessageParser.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/ClientMessageParser.java
@@ -17,7 +17,6 @@
package org.apache.ignite.internal.processors.platform.client;
-import org.apache.ignite.internal.binary.BinaryRawReaderEx;
import org.apache.ignite.internal.binary.BinaryRawWriterEx;
import org.apache.ignite.internal.binary.BinaryReaderExImpl;
import org.apache.ignite.internal.binary.GridBinaryMarshaller;
@@ -75,6 +74,7 @@
import org.apache.ignite.internal.processors.platform.client.cluster.ClientClusterWalChangeStateRequest;
import org.apache.ignite.internal.processors.platform.client.cluster.ClientClusterWalGetStateRequest;
import org.apache.ignite.internal.processors.platform.client.compute.ClientExecuteTaskRequest;
+import org.apache.ignite.internal.processors.platform.client.service.ClientServiceInvokeRequest;
import org.apache.ignite.internal.processors.platform.client.tx.ClientTxEndRequest;
import org.apache.ignite.internal.processors.platform.client.tx.ClientTxStartRequest;
@@ -250,6 +250,9 @@ public class ClientMessageParser implements ClientListenerMessageParser {
/** */
public static final short OP_COMPUTE_TASK_FINISHED = 6001;
+ /** Service invocation. */
+ private static final short OP_SERVICE_INVOKE = 7000;
+
/** Marshaller. */
private final GridBinaryMarshaller marsh;
@@ -280,7 +283,7 @@ public class ClientMessageParser implements ClientListenerMessageParser {
BinaryInputStream inStream = new BinaryHeapInputStream(msg);
// skipHdrCheck must be true (we have 103 op code).
- BinaryRawReaderEx reader = new BinaryReaderExImpl(marsh.context(), inStream,
+ BinaryReaderExImpl reader = new BinaryReaderExImpl(marsh.context(), inStream,
null, null, true, true);
return decode(reader);
@@ -292,7 +295,7 @@ public class ClientMessageParser implements ClientListenerMessageParser {
* @param reader Reader.
* @return Request.
*/
- public ClientListenerRequest decode(BinaryRawReaderEx reader) {
+ public ClientListenerRequest decode(BinaryReaderExImpl reader) {
short opCode = reader.readShort();
switch (opCode) {
@@ -450,6 +453,9 @@ public ClientListenerRequest decode(BinaryRawReaderEx reader) {
case OP_COMPUTE_TASK_EXECUTE:
return new ClientExecuteTaskRequest(reader);
+
+ case OP_SERVICE_INVOKE:
+ return new ClientServiceInvokeRequest(reader);
}
return new ClientRawRequest(reader.readLong(), ClientStatus.INVALID_OP_CODE,
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/service/ClientServiceInvokeRequest.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/service/ClientServiceInvokeRequest.java
new file mode 100644
index 0000000000000..e51780bea678a
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/service/ClientServiceInvokeRequest.java
@@ -0,0 +1,323 @@
+/*
+ * 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.internal.processors.platform.client.service;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import org.apache.ignite.IgniteBinary;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.IgniteServices;
+import org.apache.ignite.internal.binary.BinaryRawReaderEx;
+import org.apache.ignite.internal.binary.BinaryReaderExImpl;
+import org.apache.ignite.internal.cluster.ClusterGroupAdapter;
+import org.apache.ignite.internal.processors.platform.PlatformNativeException;
+import org.apache.ignite.internal.processors.platform.client.ClientConnectionContext;
+import org.apache.ignite.internal.processors.platform.client.ClientObjectResponse;
+import org.apache.ignite.internal.processors.platform.client.ClientRequest;
+import org.apache.ignite.internal.processors.platform.client.ClientResponse;
+import org.apache.ignite.internal.processors.platform.services.PlatformService;
+import org.apache.ignite.internal.processors.platform.services.PlatformServices;
+import org.apache.ignite.internal.processors.service.GridServiceProxy;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.internal.util.typedef.internal.S;
+import org.apache.ignite.services.Service;
+import org.apache.ignite.services.ServiceDescriptor;
+
+/**
+ * Request to invoke service method.
+ */
+public class ClientServiceInvokeRequest extends ClientRequest {
+ /** Flag keep binary mask. */
+ private static final byte FLAG_KEEP_BINARY_MASK = 0x01;
+
+ /** Flag "has parameter types", indicates that method arguments prefixed by typeId to help to resolve methods. */
+ private static final byte FLAG_PARAMETER_TYPES_MASK = 0x02;
+
+ /** Methods cache. */
+ private static final Map methodsCache = new ConcurrentHashMap<>();
+
+ /** Service name. */
+ private final String name;
+
+ /** Flags. */
+ private final byte flags;
+
+ /** Timeout. */
+ private final long timeout;
+
+ /** Nodes. */
+ private final Collection nodeIds;
+
+ /** Method name. */
+ private final String methodName;
+
+ /** Method parameter type IDs. */
+ private final int[] paramTypeIds;
+
+ /** Service arguments. */
+ private final Object[] args;
+
+ /** Objects reader. */
+ private final BinaryRawReaderEx reader;
+
+ /**
+ * Constructor.
+ *
+ * @param reader Reader.
+ */
+ public ClientServiceInvokeRequest(BinaryReaderExImpl reader) {
+ super(reader);
+
+ name = reader.readString();
+
+ flags = reader.readByte();
+
+ timeout = reader.readLong();
+
+ int cnt = reader.readInt();
+
+ nodeIds = new ArrayList<>(cnt);
+
+ for (int i = 0; i < cnt; i++)
+ nodeIds.add(new UUID(reader.readLong(), reader.readLong()));
+
+ methodName = reader.readString();
+
+ int argCnt = reader.readInt();
+
+ paramTypeIds = hasParameterTypes() ? new int[argCnt] : null;
+
+ args = new Object[argCnt];
+
+ // We can't deserialize some types (arrays of user defined types for example) from detached objects.
+ // On the other hand, deserialize should be done as part of process() call (not in constructor) for proper
+ // error handling.
+ // To overcome these issues we store binary reader reference, parse request in constructor (by reading detached
+ // objects), restore arguments starting position in input stream and deserialize arguments from input stream
+ // in process() method.
+ this.reader = reader;
+
+ int argsStartPos = reader.in().position();
+
+ for (int i = 0; i < argCnt; i++) {
+ if (paramTypeIds != null)
+ paramTypeIds[i] = reader.readInt();
+
+ args[i] = reader.readObjectDetached();
+ }
+
+ reader.in().position(argsStartPos);
+ }
+
+ /** {@inheritDoc} */
+ @Override public ClientResponse process(ClientConnectionContext ctx) {
+ if (F.isEmpty(name))
+ throw new IgniteException("Service name can't be empty");
+
+ if (F.isEmpty(methodName))
+ throw new IgniteException("Method name can't be empty");
+
+ ServiceDescriptor desc = findServiceDescriptor(ctx, name);
+
+ Class> svcCls = desc.serviceClass();
+
+ ClusterGroupAdapter grp = ctx.kernalContext().cluster().get();
+
+ if (ctx.securityContext() != null)
+ grp = (ClusterGroupAdapter)grp.forSubjectId(ctx.securityContext().subject().id());
+
+ grp = (ClusterGroupAdapter)(nodeIds.isEmpty() ? grp.forServers() : grp.forNodeIds(nodeIds));
+
+ IgniteServices services = grp.services();
+
+ if (!keepBinary() && args.length > 0) {
+ for (int i = 0; i < args.length; i++) {
+ if (paramTypeIds != null)
+ reader.readInt(); // Skip parameter typeId, we already read it in constructor.
+
+ args[i] = reader.readObject();
+ }
+ }
+
+ try {
+ Object res;
+
+ if (PlatformService.class.isAssignableFrom(svcCls)) {
+ PlatformService proxy = services.serviceProxy(name, PlatformService.class, false, timeout);
+
+ res = proxy.invokeMethod(methodName, keepBinary(), !keepBinary(), args);
+ }
+ else {
+ GridServiceProxy> proxy = new GridServiceProxy<>(grp, name, Service.class, false, timeout,
+ ctx.kernalContext());
+
+ Method method = resolveMethod(ctx, svcCls);
+
+ res = proxy.invokeMethod(method, args);
+ }
+
+ return new ClientObjectResponse(requestId(), res);
+ }
+ catch (PlatformNativeException e) {
+ ctx.kernalContext().log(getClass()).error("Failed to invoke platform service", e);
+
+ throw new IgniteException("Failed to invoke platform service, see server logs for details");
+ }
+ catch (Throwable e) {
+ throw new IgniteException(e);
+ }
+ }
+
+ /**
+ * Keep binary flag.
+ */
+ private boolean keepBinary() {
+ return (flags & FLAG_KEEP_BINARY_MASK) != 0;
+ }
+
+ /**
+ * "Has parameter types" flag.
+ */
+ private boolean hasParameterTypes() {
+ return (flags & FLAG_PARAMETER_TYPES_MASK) != 0;
+ }
+
+ /**
+ * @param ctx Connection context.
+ * @param name Service name.
+ */
+ private static ServiceDescriptor findServiceDescriptor(ClientConnectionContext ctx, String name) {
+ for (ServiceDescriptor desc : ctx.kernalContext().service().serviceDescriptors()) {
+ if (name.equals(desc.name()))
+ return desc;
+ }
+
+ throw new IgniteException("Service not found: " + name);
+ }
+
+ /**
+ * Resolve method by method name and parameter types or parameter values.
+ */
+ private Method resolveMethod(ClientConnectionContext ctx, Class> cls) throws ReflectiveOperationException {
+ if (paramTypeIds != null) {
+ MethodDescriptor desc = new MethodDescriptor(cls, methodName, paramTypeIds);
+
+ Method method = methodsCache.get(desc);
+
+ if (method != null)
+ return method;
+
+ IgniteBinary binary = ctx.kernalContext().grid().binary();
+
+ for (Method method0 : cls.getMethods()) {
+ if (methodName.equals(method0.getName())) {
+ MethodDescriptor desc0 = MethodDescriptor.forMethod(binary, method0);
+
+ methodsCache.putIfAbsent(desc0, method0);
+
+ if (desc0.equals(desc))
+ return method0;
+ }
+ }
+
+ throw new NoSuchMethodException("Method not found: " + desc);
+ }
+
+ // Try to find method by name and parameter values.
+ return PlatformServices.getMethod(cls, methodName, args);
+ }
+
+ /**
+ *
+ */
+ private static class MethodDescriptor {
+ /** Class. */
+ private final Class> cls;
+
+ /** Method name. */
+ private final String methodName;
+
+ /** Parameter type IDs. */
+ private final int[] paramTypeIds;
+
+ /** Hash code. */
+ private final int hash;
+
+ /**
+ * @param cls Class.
+ * @param methodName Method name.
+ * @param paramTypeIds Parameter type ids.
+ */
+ private MethodDescriptor(Class> cls, String methodName, int[] paramTypeIds) {
+ assert cls != null;
+ assert methodName != null;
+ assert paramTypeIds != null;
+
+ this.cls = cls;
+ this.methodName = methodName;
+ this.paramTypeIds = paramTypeIds;
+
+ // Precalculate hash in constructor, since we need it for all objects of this class.
+ hash = 31 * ((31 * cls.hashCode()) + methodName.hashCode()) + Arrays.hashCode(paramTypeIds);
+ }
+
+ /**
+ * @param binary Ignite binary.
+ * @param method Method.
+ */
+ private static MethodDescriptor forMethod(IgniteBinary binary, Method method) {
+ Class>[] paramTypes = method.getParameterTypes();
+
+ int[] paramTypeIds = new int[paramTypes.length];
+
+ for (int i = 0; i < paramTypes.length; i++)
+ paramTypeIds[i] = binary.typeId(paramTypes[i].getName());
+
+ return new MethodDescriptor(method.getDeclaringClass(), method.getName(), paramTypeIds);
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean equals(Object o) {
+ if (this == o)
+ return true;
+
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ MethodDescriptor that = (MethodDescriptor)o;
+
+ return cls.equals(that.cls) && methodName.equals(that.methodName)
+ && Arrays.equals(paramTypeIds, that.paramTypeIds);
+ }
+
+ /** {@inheritDoc} */
+ @Override public int hashCode() {
+ return hash;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return S.toString(MethodDescriptor.class, this, "paramTypeIds", paramTypeIds);
+ }
+ }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/services/PlatformServices.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/services/PlatformServices.java
index 24d473accbe43..6a6e05ea7acb1 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/services/PlatformServices.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/services/PlatformServices.java
@@ -514,6 +514,19 @@ private IgniteFuture dotnetDeployAllAsync(BinaryRawReaderEx reader, Ignite
return cfgs;
}
+ /**
+ * Finds a suitable method in a class.
+ *
+ * @param clazz Class.
+ * @param mthdName Name.
+ * @param args Args.
+ * @return Method.
+ * @throws NoSuchMethodException On error.
+ */
+ public static Method getMethod(Class> clazz, String mthdName, Object[] args) throws NoSuchMethodException {
+ return ServiceProxyHolder.getMethod(clazz, mthdName, args);
+ }
+
/**
* Proxy holder.
*/
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 64eada3282feb..3c92ebcc92d3e 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
@@ -191,8 +191,14 @@ else if (U.isToStringMethod(mtd))
if (svcCtx != null) {
Service svc = svcCtx.service();
- if (svc != null)
- return callServiceLocally(svc, mtd, args);
+ if (svc != null) {
+ try {
+ return callServiceLocally(svc, mtd, args);
+ }
+ catch (InvocationTargetException e) {
+ throw e.getTargetException();
+ }
+ }
}
}
else {
diff --git a/modules/core/src/test/java/org/apache/ignite/client/AsyncChannelTest.java b/modules/core/src/test/java/org/apache/ignite/client/AsyncChannelTest.java
index 543321c53a458..9ac12e1eeebba 100644
--- a/modules/core/src/test/java/org/apache/ignite/client/AsyncChannelTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/client/AsyncChannelTest.java
@@ -24,25 +24,22 @@
import javax.cache.Cache;
import org.apache.ignite.Ignite;
import org.apache.ignite.IgniteCache;
-import org.apache.ignite.Ignition;
import org.apache.ignite.cache.CacheAtomicityMode;
import org.apache.ignite.cache.CachePeekMode;
import org.apache.ignite.cache.query.Query;
import org.apache.ignite.cache.query.QueryCursor;
import org.apache.ignite.cache.query.ScanQuery;
import org.apache.ignite.configuration.CacheConfiguration;
-import org.apache.ignite.configuration.ClientConfiguration;
-import org.apache.ignite.configuration.ClientConnectorConfiguration;
import org.apache.ignite.configuration.IgniteConfiguration;
import org.apache.ignite.internal.IgniteInternalFuture;
+import org.apache.ignite.internal.client.thin.AbstractThinClientTest;
import org.apache.ignite.testframework.GridTestUtils;
-import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
import org.junit.Test;
/**
* Async channel tests.
*/
-public class AsyncChannelTest extends GridCommonAbstractTest {
+public class AsyncChannelTest extends AbstractThinClientTest {
/** Nodes count. */
private static final int NODES_CNT = 3;
@@ -52,9 +49,6 @@ public class AsyncChannelTest extends GridCommonAbstractTest {
/** Cache name. */
private static final String CACHE_NAME = "tx_cache";
- /** Client connector address. */
- private static final String CLIENT_CONN_ADDR = "127.0.0.1:" + ClientConnectorConfiguration.DFLT_PORT;
-
/** {@inheritDoc} */
@Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception {
return super.getConfiguration(igniteInstanceName).setCacheConfiguration(
@@ -75,7 +69,7 @@ public class AsyncChannelTest extends GridCommonAbstractTest {
*/
@Test
public void testAsyncRequests() throws Exception {
- try (IgniteClient client = Ignition.startClient(new ClientConfiguration().setAddresses(CLIENT_CONN_ADDR))) {
+ try (IgniteClient client = startClient(0)) {
Ignite ignite = grid(0);
IgniteCache igniteCache = ignite.cache(CACHE_NAME);
@@ -130,7 +124,7 @@ public void testAsyncRequests() throws Exception {
*/
@Test
public void testConcurrentRequests() throws Exception {
- try (IgniteClient client = Ignition.startClient(new ClientConfiguration().setAddresses(CLIENT_CONN_ADDR))) {
+ try (IgniteClient client = startClient(0)) {
ClientCache clientCache = client.cache(CACHE_NAME);
clientCache.clear();
@@ -164,7 +158,7 @@ public void testConcurrentRequests() throws Exception {
*/
@Test
public void testConcurrentQueries() throws Exception {
- try (IgniteClient client = Ignition.startClient(new ClientConfiguration().setAddresses(CLIENT_CONN_ADDR))) {
+ try (IgniteClient client = startClient(0)) {
ClientCache clientCache = client.cache(CACHE_NAME);
clientCache.clear();
diff --git a/modules/core/src/test/java/org/apache/ignite/client/ConnectToStartingNodeTest.java b/modules/core/src/test/java/org/apache/ignite/client/ConnectToStartingNodeTest.java
index f79928d86b5b7..bbb2c87ceafea 100644
--- a/modules/core/src/test/java/org/apache/ignite/client/ConnectToStartingNodeTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/client/ConnectToStartingNodeTest.java
@@ -20,25 +20,19 @@
import java.util.concurrent.Callable;
import java.util.concurrent.CyclicBarrier;
import org.apache.ignite.Ignite;
-import org.apache.ignite.Ignition;
-import org.apache.ignite.configuration.ClientConfiguration;
-import org.apache.ignite.configuration.ClientConnectorConfiguration;
import org.apache.ignite.configuration.IgniteConfiguration;
import org.apache.ignite.internal.IgniteInternalFuture;
+import org.apache.ignite.internal.client.thin.AbstractThinClientTest;
import org.apache.ignite.spi.IgniteSpiException;
import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi;
import org.apache.ignite.testframework.GridTestUtils;
-import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
import org.jetbrains.annotations.Nullable;
import org.junit.Test;
/**
* Checks that connection with starting node will be established correctly.
*/
-public class ConnectToStartingNodeTest extends GridCommonAbstractTest {
- /** Client connector address. */
- private static final String CLIENT_CONN_ADDR = "127.0.0.1:" + ClientConnectorConfiguration.DFLT_PORT;
-
+public class ConnectToStartingNodeTest extends AbstractThinClientTest {
/** Barrier to suspend discovery SPI start. */
private final CyclicBarrier barrier = new CyclicBarrier(2);
@@ -75,7 +69,7 @@ public void testClientConnectBeforeDiscoveryStart() throws Exception {
barrier.await();
IgniteInternalFuture futStartClient = GridTestUtils.runAsync(
- () -> Ignition.startClient(new ClientConfiguration().setAddresses(CLIENT_CONN_ADDR)));
+ () -> startClient(grid()));
// Server doesn't accept connection before discovery SPI started.
assertFalse(GridTestUtils.waitForCondition(futStartClient::isDone, 500L));
diff --git a/modules/core/src/test/java/org/apache/ignite/client/Person.java b/modules/core/src/test/java/org/apache/ignite/client/Person.java
index c92305df39e34..27510a7bdde7b 100644
--- a/modules/core/src/test/java/org/apache/ignite/client/Person.java
+++ b/modules/core/src/test/java/org/apache/ignite/client/Person.java
@@ -19,6 +19,7 @@
import java.util.Objects;
import org.apache.ignite.cache.query.annotations.QuerySqlField;
+import org.apache.ignite.internal.util.typedef.internal.S;
/**
* A person entity used for the tests.
@@ -62,4 +63,9 @@ public String getName() {
return other.id.equals(id) && other.name.equals(name);
}
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return S.toString(Person.class, this);
+ }
}
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/client/thin/AbstractThinClientTest.java b/modules/core/src/test/java/org/apache/ignite/internal/client/thin/AbstractThinClientTest.java
new file mode 100644
index 0000000000000..b985a77d5055a
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/client/thin/AbstractThinClientTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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.internal.client.thin;
+
+import java.util.Arrays;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.Ignition;
+import org.apache.ignite.client.IgniteClient;
+import org.apache.ignite.cluster.ClusterNode;
+import org.apache.ignite.configuration.ClientConfiguration;
+import org.apache.ignite.internal.processors.odbc.ClientListenerProcessor;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+
+/**
+ * Abstract thin client test.
+ */
+public abstract class AbstractThinClientTest extends GridCommonAbstractTest {
+ /**
+ * Gets default client configuration.
+ */
+ protected ClientConfiguration getClientConfiguration() {
+ return new ClientConfiguration();
+ }
+
+ /**
+ * Start thin client with configured endpoints to specified nodes.
+ *
+ * @param nodes Nodes to connect.
+ * @return Thin client.
+ */
+ protected IgniteClient startClient(ClusterNode... nodes) {
+ String[] addrs = new String[nodes.length];
+
+ for (int i = 0; i < nodes.length; i++) {
+ ClusterNode node = nodes[i];
+
+ addrs[i] = F.first(node.addresses()) + ":" + node.attribute(ClientListenerProcessor.CLIENT_LISTENER_PORT);
+ }
+
+ return Ignition.startClient(getClientConfiguration().setAddresses(addrs));
+ }
+
+ /**
+ * Start thin client with configured endpoints to specified ignite instances.
+ *
+ * @param ignites Ignite instances to connect.
+ * @return Thin client.
+ */
+ protected IgniteClient startClient(Ignite... ignites) {
+ return startClient(Arrays.stream(ignites).map(ignite -> ignite.cluster().localNode()).toArray(ClusterNode[]::new));
+ }
+
+ /**
+ * Start thin client with configured endpoints to specified ignite instance indexes.
+ *
+ * @param igniteIdxs Ignite instance indexes to connect.
+ * @return Thin client.
+ */
+ protected IgniteClient startClient(int... igniteIdxs) {
+ return startClient(Arrays.stream(igniteIdxs).mapToObj(igniteIdx -> grid(igniteIdx).cluster().localNode())
+ .toArray(ClusterNode[]::new));
+ }
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/client/thin/ClusterApiTest.java b/modules/core/src/test/java/org/apache/ignite/internal/client/thin/ClusterApiTest.java
index 9f78342a0ce5b..5540ea3833a60 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/client/thin/ClusterApiTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/client/thin/ClusterApiTest.java
@@ -18,25 +18,18 @@
package org.apache.ignite.internal.client.thin;
import org.apache.ignite.IgniteCluster;
-import org.apache.ignite.Ignition;
import org.apache.ignite.client.ClientCluster;
import org.apache.ignite.client.IgniteClient;
import org.apache.ignite.cluster.ClusterState;
-import org.apache.ignite.configuration.ClientConfiguration;
-import org.apache.ignite.configuration.ClientConnectorConfiguration;
import org.apache.ignite.configuration.DataRegionConfiguration;
import org.apache.ignite.configuration.DataStorageConfiguration;
import org.apache.ignite.configuration.IgniteConfiguration;
-import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
import org.junit.Test;
/**
* Checks cluster state/WAL state operations for thin client.
*/
-public class ClusterApiTest extends GridCommonAbstractTest {
- /** Client connector address. */
- private static final String CLIENT_CONN_ADDR = "127.0.0.1:" + ClientConnectorConfiguration.DFLT_PORT;
-
+public class ClusterApiTest extends AbstractThinClientTest {
/** {@inheritDoc} */
@Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception {
return super.getConfiguration(igniteInstanceName)
@@ -69,7 +62,7 @@ public class ClusterApiTest extends GridCommonAbstractTest {
*/
@Test
public void testClusterState() throws Exception {
- try (IgniteClient client = Ignition.startClient(new ClientConfiguration().setAddresses(CLIENT_CONN_ADDR))) {
+ try (IgniteClient client = startClient(0)) {
ClientCluster clientCluster = client.cluster();
IgniteCluster igniteCluster = grid(0).cluster();
@@ -85,7 +78,7 @@ public void testClusterState() throws Exception {
*/
@Test
public void testWalState() throws Exception {
- try (IgniteClient client = Ignition.startClient(new ClientConfiguration().setAddresses(CLIENT_CONN_ADDR))) {
+ try (IgniteClient client = startClient(0)) {
ClientCluster clientCluster = client.cluster();
IgniteCluster igniteCluster = grid(0).cluster();
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/client/thin/ClusterGroupTest.java b/modules/core/src/test/java/org/apache/ignite/internal/client/thin/ClusterGroupTest.java
index e253fab7e4dc8..e3662042dda5d 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/client/thin/ClusterGroupTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/client/thin/ClusterGroupTest.java
@@ -23,27 +23,20 @@
import java.util.Map;
import java.util.UUID;
import org.apache.ignite.Ignite;
-import org.apache.ignite.Ignition;
import org.apache.ignite.client.ClientClusterGroup;
import org.apache.ignite.client.IgniteClient;
import org.apache.ignite.cluster.ClusterNode;
-import org.apache.ignite.configuration.ClientConfiguration;
-import org.apache.ignite.configuration.ClientConnectorConfiguration;
import org.apache.ignite.configuration.IgniteConfiguration;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.G;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi;
-import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
import org.junit.Test;
/**
* Checks cluster groups for thin client.
*/
-public class ClusterGroupTest extends GridCommonAbstractTest {
- /** Client connector address. */
- private static final String CLIENT_CONN_ADDR = "127.0.0.1:" + ClientConnectorConfiguration.DFLT_PORT;
-
+public class ClusterGroupTest extends AbstractThinClientTest {
/** Grid index attribute name. */
private static final String GRID_IDX_ATTR_NAME = "GRID_IDX";
@@ -66,7 +59,7 @@ public class ClusterGroupTest extends GridCommonAbstractTest {
startGrid(3, true, null);
startGrid(4, true, F.asMap(CUSTOM_ATTR_NAME, CUSTOM_ATTR_VAL));
- client = Ignition.startClient(new ClientConfiguration().setAddresses(CLIENT_CONN_ADDR));
+ client = startClient(0);
}
/** {@inheritDoc} */
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/client/thin/ComputeTaskTest.java b/modules/core/src/test/java/org/apache/ignite/internal/client/thin/ComputeTaskTest.java
index d400daff1ae1a..1faab885e9905 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/client/thin/ComputeTaskTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/client/thin/ComputeTaskTest.java
@@ -33,10 +33,8 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
-
import org.apache.ignite.Ignite;
import org.apache.ignite.IgniteException;
-import org.apache.ignite.Ignition;
import org.apache.ignite.client.ClientCache;
import org.apache.ignite.client.ClientClusterGroup;
import org.apache.ignite.client.ClientCompute;
@@ -44,7 +42,6 @@
import org.apache.ignite.client.IgniteClient;
import org.apache.ignite.compute.ComputeJobResult;
import org.apache.ignite.compute.ComputeTaskName;
-import org.apache.ignite.configuration.ClientConfiguration;
import org.apache.ignite.configuration.ClientConnectorConfiguration;
import org.apache.ignite.configuration.IgniteConfiguration;
import org.apache.ignite.configuration.ThinClientConfiguration;
@@ -54,7 +51,6 @@
import org.apache.ignite.internal.util.typedef.T2;
import org.apache.ignite.mxbean.ClientProcessorMXBean;
import org.apache.ignite.testframework.GridTestUtils;
-import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
import org.jetbrains.annotations.Nullable;
import org.junit.Ignore;
import org.junit.Test;
@@ -62,7 +58,7 @@
/**
* Checks compute grid funtionality of thin client.
*/
-public class ComputeTaskTest extends GridCommonAbstractTest {
+public class ComputeTaskTest extends AbstractThinClientTest {
/** Grids count. */
private static final int GRIDS_CNT = 4;
@@ -84,18 +80,6 @@ public class ComputeTaskTest extends GridCommonAbstractTest {
.setClientMode(getTestIgniteInstanceIndex(igniteInstanceName) == 3);
}
- /**
- *
- */
- private IgniteClient startClient(int... gridIdxs) {
- String[] addrs = new String[gridIdxs.length];
-
- for (int i = 0; i < gridIdxs.length; i++)
- addrs[i] = "127.0.0.1:" + (ClientConnectorConfiguration.DFLT_PORT + gridIdxs[i]);
-
- return Ignition.startClient(new ClientConfiguration().setAddresses(addrs));
- }
-
/** {@inheritDoc} */
@Override protected void beforeTestsStarted() throws Exception {
super.beforeTestsStarted();
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/client/thin/ServicesTest.java b/modules/core/src/test/java/org/apache/ignite/internal/client/thin/ServicesTest.java
new file mode 100644
index 0000000000000..9f0641ab6bff7
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/client/thin/ServicesTest.java
@@ -0,0 +1,417 @@
+/*
+ * 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.internal.client.thin;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.UUID;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.client.ClientClusterGroup;
+import org.apache.ignite.client.ClientException;
+import org.apache.ignite.client.IgniteClient;
+import org.apache.ignite.client.Person;
+import org.apache.ignite.internal.IgniteInternalFuture;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.resources.IgniteInstanceResource;
+import org.apache.ignite.services.Service;
+import org.apache.ignite.services.ServiceContext;
+import org.apache.ignite.testframework.GridTestUtils;
+import org.junit.Test;
+
+/**
+ * Checks service invocation for thin client.
+ */
+public class ServicesTest extends AbstractThinClientTest {
+ /** Node-id service name. */
+ private static final String NODE_ID_SERVICE_NAME = "node_id_svc";
+
+ /** Node-singleton service name. */
+ private static final String NODE_SINGLTON_SERVICE_NAME = "node_svc";
+
+ /** Cluster-singleton service name. */
+ private static final String CLUSTER_SINGLTON_SERVICE_NAME = "cluster_svc";
+
+ /** {@inheritDoc} */
+ @Override protected void beforeTestsStarted() throws Exception {
+ super.beforeTestsStarted();
+
+ startGrids(3);
+
+ startClientGrid(3);
+
+ grid(0).createCache(DEFAULT_CACHE_NAME);
+
+ awaitPartitionMapExchange();
+
+ grid(0).services().deployNodeSingleton(NODE_ID_SERVICE_NAME, new TestNodeIdService());
+
+ grid(0).services().deployNodeSingleton(NODE_SINGLTON_SERVICE_NAME, new TestService());
+
+ // Deploy CLUSTER_SINGLTON_SERVICE_NAME to grid(1).
+ int keyGrid1 = primaryKey(grid(1).cache(DEFAULT_CACHE_NAME));
+
+ grid(0).services().deployKeyAffinitySingleton(CLUSTER_SINGLTON_SERVICE_NAME, new TestService(),
+ DEFAULT_CACHE_NAME, keyGrid1);
+ }
+
+ /**
+ * Test that overloaded methods resolved correctly.
+ */
+ @Test
+ public void testOverloadedMethods() throws Exception {
+ try (IgniteClient client = startClient(0)) {
+ // Test local service calls (service deployed to each node).
+ TestServiceInterface svc = client.services().serviceProxy(NODE_SINGLTON_SERVICE_NAME,
+ TestServiceInterface.class);
+
+ checkOverloadedMethods(svc);
+
+ // Test remote service calls (client connected to grid(0) but service deployed to grid(1)).
+ svc = client.services().serviceProxy(CLUSTER_SINGLTON_SERVICE_NAME, TestServiceInterface.class);
+
+ checkOverloadedMethods(svc);
+ }
+ }
+
+ /**
+ * @param svc Service.
+ */
+ private void checkOverloadedMethods(TestServiceInterface svc) {
+ assertEquals("testMethod()", svc.testMethod());
+
+ assertEquals("testMethod(String val): test", svc.testMethod("test"));
+
+ assertEquals(123, svc.testMethod(123));
+
+ assertEquals("testMethod(Object val): test", svc.testMethod(new StringBuilder("test")));
+
+ assertEquals("testMethod(String val): null", svc.testMethod((String)null));
+
+ assertEquals("testMethod(Object val): null", svc.testMethod((Object)null));
+
+ Person person1 = new Person(1, "Person 1");
+ Person person2 = new Person(2, "Person 2");
+
+ assertEquals("testMethod(Person person, Object obj): " + person1 + ", " + person2,
+ svc.testMethod(person1, (Object)person2));
+
+ assertEquals("testMethod(Object obj, Person person): " + person1 + ", " + person2,
+ svc.testMethod((Object)person1, person2));
+ }
+
+ /**
+ * Test that methods which get and return collections work correctly.
+ */
+ @Test
+ public void testCollectionMethods() throws Exception {
+ try (IgniteClient client = startClient(0)) {
+ // Test local service calls (service deployed to each node).
+ TestServiceInterface svc = client.services().serviceProxy(NODE_SINGLTON_SERVICE_NAME,
+ TestServiceInterface.class);
+
+ checkCollectionMethods(svc);
+
+ // Test remote service calls (client connected to grid(0) but service deployed to grid(1)).
+ svc = client.services().serviceProxy(CLUSTER_SINGLTON_SERVICE_NAME, TestServiceInterface.class);
+
+ checkCollectionMethods(svc);
+ }
+ }
+
+ /**
+ * @param svc Service.
+ */
+ private void checkCollectionMethods(TestServiceInterface svc) {
+ Person person1 = new Person(1, "Person 1");
+ Person person2 = new Person(2, "Person 2");
+
+ Person[] arr = new Person[] {person1, person2};
+ assertTrue(Arrays.equals(arr, svc.testArray(arr)));
+
+ Collection col = new HashSet<>(F.asList(person1, person2));
+ assertEquals(col, svc.testCollection(col));
+
+ Map map = F.asMap(1, person1, 2, person2);
+ assertEquals(map, svc.testMap(map));
+ }
+
+ /**
+ * Test that exception is thrown when invoking service with wrong interface.
+ */
+ @Test
+ public void testWrongMethodInvocation() throws Exception {
+ try (IgniteClient client = startClient(0)) {
+ TestServiceInterface svc = client.services().serviceProxy(NODE_ID_SERVICE_NAME,
+ TestServiceInterface.class);
+
+ GridTestUtils.assertThrowsAnyCause(log, () -> svc.testMethod(0), ClientException.class,
+ "Method not found");
+ }
+ }
+
+ /**
+ * Test that exception is thrown when trying to invoke non-existing service.
+ */
+ @Test
+ public void testWrongServiceName() throws Exception {
+ try (IgniteClient client = startClient(0)) {
+ TestServiceInterface svc = client.services().serviceProxy("no_such_service",
+ TestServiceInterface.class);
+
+ GridTestUtils.assertThrowsAnyCause(log, () -> svc.testMethod(0), ClientException.class,
+ "Service not found");
+ }
+ }
+
+ /**
+ * Test that service exception message is propagated to client.
+ */
+ @Test
+ public void testServiceException() throws Exception {
+ try (IgniteClient client = startClient(0)) {
+ // Test local service calls (service deployed to each node).
+ TestServiceInterface svc = client.services().serviceProxy(NODE_SINGLTON_SERVICE_NAME,
+ TestServiceInterface.class);
+
+ GridTestUtils.assertThrowsAnyCause(log, svc::testException, ClientException.class,
+ "testException()");
+
+ // Test remote service calls (client connected to grid(0) but service deployed to grid(1)).
+ client.services().serviceProxy(CLUSTER_SINGLTON_SERVICE_NAME, TestServiceInterface.class);
+
+ GridTestUtils.assertThrowsAnyCause(log, svc::testException, ClientException.class,
+ "testException()");
+ }
+ }
+
+ /**
+ * Test that services executed on cluster group.
+ */
+ @Test
+ public void testServicesOnClusterGroup() throws Exception {
+ try (IgniteClient client = startClient(0)) {
+ // Local node.
+ ClientClusterGroup grp = client.cluster().forNodeId(nodeId(0), nodeId(3));
+
+ TestNodeIdServiceInterface nodeSvc0 = client.services(grp).serviceProxy(NODE_ID_SERVICE_NAME,
+ TestNodeIdServiceInterface.class);
+
+ assertEquals(nodeId(0), nodeSvc0.nodeId());
+
+ // Remote node.
+ grp = client.cluster().forNodeId(nodeId(1), nodeId(3));
+
+ nodeSvc0 = client.services(grp).serviceProxy(NODE_ID_SERVICE_NAME,
+ TestNodeIdServiceInterface.class);
+
+ assertEquals(nodeId(1), nodeSvc0.nodeId());
+
+ // Client node.
+ grp = client.cluster().forNodeId(nodeId(3));
+
+ TestNodeIdServiceInterface nodeSvc1 = client.services(grp).serviceProxy(NODE_ID_SERVICE_NAME,
+ TestNodeIdServiceInterface.class);
+
+ GridTestUtils.assertThrowsAnyCause(log, nodeSvc1::nodeId, ClientException.class,
+ "Failed to find deployed service");
+
+ // All nodes, except service node.
+ grp = client.cluster().forNodeId(nodeId(0), nodeId(2), nodeId(3));
+
+ TestServiceInterface nodeSvc2 = client.services(grp).serviceProxy(CLUSTER_SINGLTON_SERVICE_NAME,
+ TestServiceInterface.class);
+
+ GridTestUtils.assertThrowsAnyCause(log, nodeSvc2::testMethod, ClientException.class,
+ "Failed to find deployed service");
+ }
+ }
+
+ /**
+ * Test services timeout.
+ */
+ @Test
+ public void testServiceTimeout() throws Exception {
+ long timeout = 100L;
+
+ try (IgniteClient client = startClient(0)) {
+ TestServiceInterface svc = client.services().serviceProxy(CLUSTER_SINGLTON_SERVICE_NAME,
+ TestServiceInterface.class, timeout);
+
+ IgniteInternalFuture> fut = GridTestUtils.runAsync(() -> svc.sleep(timeout * 2));
+
+ GridTestUtils.assertThrowsAnyCause(log, fut::get, ClientException.class, "timed out");
+ }
+ }
+
+ /** */
+ public static interface TestServiceInterface {
+ /** */
+ public String testMethod();
+
+ /** */
+ public String testMethod(String val);
+
+ /** */
+ public String testMethod(Object val);
+
+ /** */
+ public int testMethod(int val);
+
+ /** */
+ public String testMethod(Person person, Object obj);
+
+ /** */
+ public String testMethod(Object obj, Person person);
+
+ /** */
+ public Person[] testArray(Person[] persons);
+
+ /** */
+ public Collection testCollection(Collection persons);
+
+ /** */
+ public Map testMap(Map persons);
+
+ /** */
+ public Object testException();
+
+ /** */
+ public void sleep(long millis);
+ }
+
+ /**
+ * Implementation of TestServiceInterface.
+ */
+ public static class TestService implements Service, TestServiceInterface {
+ /** {@inheritDoc} */
+ @Override public void cancel(ServiceContext ctx) {
+ // No-op.
+ }
+
+ /** {@inheritDoc} */
+ @Override public void init(ServiceContext ctx) throws Exception {
+ // No-op.
+ }
+
+ /** {@inheritDoc} */
+ @Override public void execute(ServiceContext ctx) throws Exception {
+ // No-op.
+ }
+
+ /** {@inheritDoc} */
+ @Override public String testMethod() {
+ return "testMethod()";
+ }
+
+ /** {@inheritDoc} */
+ @Override public String testMethod(String val) {
+ return "testMethod(String val): " + val;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String testMethod(Object val) {
+ return "testMethod(Object val): " + val;
+ }
+
+ /** {@inheritDoc} */
+ @Override public int testMethod(int val) {
+ return val;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String testMethod(Person person, Object obj) {
+ return "testMethod(Person person, Object obj): " + person + ", " + obj;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String testMethod(Object obj, Person person) {
+ return "testMethod(Object obj, Person person): " + obj + ", " + person;
+ }
+
+ /** {@inheritDoc} */
+ @Override public Person[] testArray(Person[] persons) {
+ return persons;
+ }
+
+ /** {@inheritDoc} */
+ @Override public Collection testCollection(Collection persons) {
+ return persons;
+ }
+
+ /** {@inheritDoc} */
+ @Override public Map testMap(Map persons) {
+ return persons;
+ }
+
+ /** {@inheritDoc} */
+ @Override public Object testException() {
+ throw new IllegalStateException("testException()");
+ }
+
+ /** {@inheritDoc} */
+ @Override public void sleep(long millis) {
+ long ts = U.currentTimeMillis();
+
+ doSleep(millis);
+
+ // Make sure cached timestamp updated.
+ while (ts + millis >= U.currentTimeMillis())
+ doSleep(100L);
+ }
+ }
+
+ /**
+ * Service to return ID of node where method was executed.
+ */
+ public static interface TestNodeIdServiceInterface {
+ /** Gets ID of node where method was executed */
+ public UUID nodeId();
+ }
+
+ /**
+ * Implementation of TestNodeIdServiceInterface
+ */
+ public static class TestNodeIdService implements Service, TestNodeIdServiceInterface {
+ /** Ignite. */
+ @IgniteInstanceResource
+ Ignite ignite;
+
+ /** {@inheritDoc} */
+ @Override public void cancel(ServiceContext ctx) {
+ // No-op.
+ }
+
+ /** {@inheritDoc} */
+ @Override public void init(ServiceContext ctx) throws Exception {
+ // No-op.
+ }
+
+ /** {@inheritDoc} */
+ @Override public void execute(ServiceContext ctx) throws Exception {
+ // No-op.
+ }
+
+ /** {@inheritDoc} */
+ @Override public UUID nodeId() {
+ return ignite.cluster().localNode().id();
+ }
+ }
+}
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
index 8609314e66138..bb5239bae535c 100644
--- a/modules/core/src/test/java/org/apache/ignite/platform/AbstractPlatformServiceCallTask.java
+++ b/modules/core/src/test/java/org/apache/ignite/platform/AbstractPlatformServiceCallTask.java
@@ -27,15 +27,19 @@
import java.util.UUID;
import org.apache.ignite.Ignite;
import org.apache.ignite.IgniteException;
+import org.apache.ignite.Ignition;
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.client.IgniteClient;
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.configuration.ClientConfiguration;
+import org.apache.ignite.internal.processors.odbc.ClientListenerProcessor;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.resources.IgniteInstanceResource;
import org.apache.ignite.services.ServiceDescriptor;
@@ -96,6 +100,11 @@ public abstract class AbstractPlatformServiceCallTask extends ComputeTaskAdapter
/** */
protected abstract static class AbstractServiceCallJob extends ComputeJobAdapter {
+ /** */
+ @SuppressWarnings("unused")
+ @IgniteInstanceResource
+ protected transient Ignite ignite;
+
/** */
protected final String srvcName;
@@ -120,10 +129,31 @@ protected AbstractServiceCallJob(String srvcName) {
}
}
+ /**
+ * Gets service proxy.
+ */
+ TestPlatformService serviceProxy() {
+ return ignite.services().serviceProxy(srvcName, TestPlatformService.class, false);
+ }
+
/**
* Test method to call platform service.
*/
abstract void runTest();
+
+ /**
+ * Start thin client connected to current ignite instance.
+ */
+ IgniteClient startClient() {
+ ClusterNode node = ignite.cluster().localNode();
+
+ String addr = F.first(node.addresses()) + ":" + node.attribute(ClientListenerProcessor.CLIENT_LISTENER_PORT);
+
+ return Ignition.startClient(new ClientConfiguration()
+ .setAddresses(addr)
+ .setBinaryConfiguration(ignite.configuration().getBinaryConfiguration())
+ );
+ }
}
/** */
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
index 7e2806b08a59e..65f48d91cd468 100644
--- a/modules/core/src/test/java/org/apache/ignite/platform/PlatformServiceCallCollectionsTask.java
+++ b/modules/core/src/test/java/org/apache/ignite/platform/PlatformServiceCallCollectionsTask.java
@@ -22,10 +22,8 @@
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.
@@ -38,11 +36,6 @@ public class PlatformServiceCallCollectionsTask extends AbstractPlatformServiceC
/** */
public static class PlatformServiceCallCollectionsJob extends AbstractServiceCallJob {
- /** */
- @SuppressWarnings("unused")
- @IgniteInstanceResource
- private transient Ignite ignite;
-
/**
* @param svcName Service name.
*/
@@ -52,7 +45,7 @@ public static class PlatformServiceCallCollectionsJob extends AbstractServiceCal
/** {@inheritDoc} */
@Override void runTest() {
- TestPlatformService srv = ignite.services().serviceProxy(srvcName, TestPlatformService.class, false);
+ TestPlatformService srv = serviceProxy();
{
TestValue[] exp = IntStream.range(0, 10).mapToObj(i -> new TestValue(i, "name_" + i))
diff --git a/modules/core/src/test/java/org/apache/ignite/platform/PlatformServiceCallCollectionsThinTask.java b/modules/core/src/test/java/org/apache/ignite/platform/PlatformServiceCallCollectionsThinTask.java
new file mode 100644
index 0000000000000..d874edf16d9b9
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/platform/PlatformServiceCallCollectionsThinTask.java
@@ -0,0 +1,65 @@
+/*
+ * 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 org.apache.ignite.client.IgniteClient;
+import org.apache.ignite.compute.ComputeJobAdapter;
+import org.apache.ignite.internal.processors.platform.services.PlatformService;
+import org.apache.ignite.internal.util.typedef.internal.U;
+
+/**
+ * Test invoke {@link PlatformService} methods with collections and arrays as arguments and return type from
+ * java thin client.
+ */
+public class PlatformServiceCallCollectionsThinTask extends AbstractPlatformServiceCallTask {
+ /** {@inheritDoc} */
+ @Override ComputeJobAdapter createJob(String svcName) {
+ return new PlatformServiceCallCollectionsThinJob(svcName);
+ }
+
+ /** */
+ static class PlatformServiceCallCollectionsThinJob extends
+ PlatformServiceCallCollectionsTask.PlatformServiceCallCollectionsJob {
+ /** Thin client. */
+ IgniteClient client;
+
+ /**
+ * @param srvcName Service name.
+ */
+ PlatformServiceCallCollectionsThinJob(String srvcName) {
+ super(srvcName);
+ }
+
+ /** {@inheritDoc} */
+ @Override TestPlatformService serviceProxy() {
+ return client.services().serviceProxy(srvcName, TestPlatformService.class);
+ }
+
+ /** {@inheritDoc} */
+ @Override void runTest() {
+ client = startClient();
+
+ try {
+ super.runTest();
+ }
+ finally {
+ U.close(client, ignite.log().getLogger(getClass()));
+ }
+ }
+ }
+}
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
index f39a3ee98790f..97c0d54c9d6ae 100644
--- a/modules/core/src/test/java/org/apache/ignite/platform/PlatformServiceCallTask.java
+++ b/modules/core/src/test/java/org/apache/ignite/platform/PlatformServiceCallTask.java
@@ -18,11 +18,9 @@
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;
/**
@@ -36,11 +34,6 @@ public class PlatformServiceCallTask extends AbstractPlatformServiceCallTask {
/** */
static class PlatformServiceCallJob extends AbstractServiceCallJob {
- /** */
- @SuppressWarnings("unused")
- @IgniteInstanceResource
- private transient Ignite ignite;
-
/**
* @param srvcName Service name.
*/
@@ -50,32 +43,41 @@ static class PlatformServiceCallJob extends AbstractServiceCallJob {
/** {@inheritDoc} */
@Override void runTest() {
- TestPlatformService srv = ignite.services().serviceProxy(srvcName, TestPlatformService.class, false);
+ TestPlatformService srv = serviceProxy();
- {
- UUID nodeId = srv.getNodeId();
- assertTrue(ignite.cluster().nodes().stream().anyMatch(n -> n.id().equals(nodeId)));
- }
+ checkNodeId(srv);
+ checkUuidProp(srv);
+ checkObjectProp(srv);
+ checkErrorMethod(srv);
+ }
- {
- UUID expUuid = UUID.randomUUID();
- srv.setGuidProp(expUuid);
- assertEquals(expUuid, srv.getGuidProp());
- }
+ /** */
+ protected void checkNodeId(TestPlatformService srv) {
+ UUID nodeId = srv.getNodeId();
+ assertTrue(ignite.cluster().nodes().stream().anyMatch(n -> n.id().equals(nodeId)));
+ }
- {
- TestValue exp = new TestValue(1, "test");
- srv.setValueProp(exp);
- assertEquals(exp, srv.getValueProp());
- }
+ /** */
+ protected void checkUuidProp(TestPlatformService srv) {
+ UUID expUuid = UUID.randomUUID();
+ srv.setGuidProp(expUuid);
+ assertEquals(expUuid, srv.getGuidProp());
+ }
- {
- PlatformNativeException nativeEx = (PlatformNativeException)GridTestUtils
- .assertThrowsWithCause(srv::errorMethod, PlatformNativeException.class)
- .getCause();
+ /** */
+ protected void checkObjectProp(TestPlatformService srv) {
+ TestValue exp = new TestValue(1, "test");
+ srv.setValueProp(exp);
+ assertEquals(exp, srv.getValueProp());
+ }
+
+ /** */
+ protected void checkErrorMethod(TestPlatformService srv) {
+ PlatformNativeException nativeEx = (PlatformNativeException)GridTestUtils
+ .assertThrowsWithCause(srv::errorMethod, PlatformNativeException.class)
+ .getCause();
- assertTrue(nativeEx.toString().contains("Failed method"));
- }
+ assertTrue(nativeEx.toString().contains("Failed method"));
}
}
}
diff --git a/modules/core/src/test/java/org/apache/ignite/platform/PlatformServiceCallThinTask.java b/modules/core/src/test/java/org/apache/ignite/platform/PlatformServiceCallThinTask.java
new file mode 100644
index 0000000000000..a1c840f3fb7ac
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/platform/PlatformServiceCallThinTask.java
@@ -0,0 +1,76 @@
+/*
+ * 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 org.apache.ignite.client.ClientException;
+import org.apache.ignite.client.IgniteClient;
+import org.apache.ignite.compute.ComputeJobAdapter;
+import org.apache.ignite.internal.processors.platform.services.PlatformService;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.testframework.GridTestUtils;
+
+/**
+ * Basic task to calling {@link PlatformService} from java thin client.
+ */
+public class PlatformServiceCallThinTask extends AbstractPlatformServiceCallTask {
+ /** {@inheritDoc} */
+ @Override ComputeJobAdapter createJob(String svcName) {
+ return new PlatformServiceCallThinJob(svcName);
+ }
+
+ /** */
+ static class PlatformServiceCallThinJob extends PlatformServiceCallTask.PlatformServiceCallJob {
+ /** Thin client. */
+ IgniteClient client;
+
+ /**
+ * @param srvcName Service name.
+ */
+ PlatformServiceCallThinJob(String srvcName) {
+ super(srvcName);
+ }
+
+ /** {@inheritDoc} */
+ @Override TestPlatformService serviceProxy() {
+ return client.services().serviceProxy(srvcName, TestPlatformService.class);
+ }
+
+ /** {@inheritDoc} */
+ @Override void runTest() {
+ client = startClient();
+
+ try {
+ super.runTest();
+ }
+ finally {
+ U.close(client, ignite.log().getLogger(getClass()));
+ }
+ }
+
+ /** */
+ @Override protected void checkErrorMethod(TestPlatformService srv) {
+ // For thin client only top level error message is passed from server to client, so we should override
+ // method for error check.
+ GridTestUtils.assertThrowsAnyCause(null, () -> {
+ srv.errorMethod();
+ return null;
+ }, ClientException.class, "Failed to invoke platform service");
+ }
+
+ }
+}
diff --git a/modules/indexing/src/test/java/org/apache/ignite/client/ClientTestSuite.java b/modules/indexing/src/test/java/org/apache/ignite/client/ClientTestSuite.java
index 07ac59fd5fdb5..c093efbf9b42a 100644
--- a/modules/indexing/src/test/java/org/apache/ignite/client/ClientTestSuite.java
+++ b/modules/indexing/src/test/java/org/apache/ignite/client/ClientTestSuite.java
@@ -20,6 +20,7 @@
import org.apache.ignite.internal.client.thin.ClusterApiTest;
import org.apache.ignite.internal.client.thin.ClusterGroupTest;
import org.apache.ignite.internal.client.thin.ComputeTaskTest;
+import org.apache.ignite.internal.client.thin.ServicesTest;
import org.apache.ignite.internal.client.thin.ThinClientPartitionAwarenessResourceReleaseTest;
import org.apache.ignite.internal.client.thin.ThinClientPartitionAwarenessStableTopologyTest;
import org.apache.ignite.internal.client.thin.ThinClientPartitionAwarenessUnstableTopologyTest;
@@ -47,6 +48,7 @@
ComputeTaskTest.class,
ClusterApiTest.class,
ClusterGroupTest.class,
+ ServicesTest.class,
ThinClientPartitionAwarenessStableTopologyTest.class,
ThinClientPartitionAwarenessUnstableTopologyTest.class,
ThinClientPartitionAwarenessResourceReleaseTest.class
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/CallPlatformServiceTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/CallPlatformServiceTest.cs
index b06671a1ee448..3efe0e82cd5f4 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/CallPlatformServiceTest.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/CallPlatformServiceTest.cs
@@ -39,6 +39,13 @@ public class CallPlatformServiceTest
/** */
private const string CheckCollectionsTaskName = "org.apache.ignite.platform.PlatformServiceCallCollectionsTask";
+ /** */
+ private const string CheckThinTaskName = "org.apache.ignite.platform.PlatformServiceCallThinTask";
+
+ /** */
+ private const string CheckCollectionsThinTaskName =
+ "org.apache.ignite.platform.PlatformServiceCallCollectionsThinTask";
+
/** */
protected IIgnite Grid1;
@@ -70,11 +77,13 @@ public void TearDown()
/// Tests call a platform service by invoking a special compute java task,
/// in which real invocation of the service is made.
///
- /// Tests common methods.
/// If true call on local node.
+ /// Task to test.
///
[Test]
- public void TestCallPlatformService([Values(true, false)] bool local)
+ public void TestCallPlatformService([Values(true, false)] bool local,
+ [Values(CheckTaskName, CheckCollectionsTaskName, CheckThinTaskName, CheckCollectionsThinTaskName)]
+ string taskName)
{
var cfg = new ServiceConfiguration
{
@@ -85,31 +94,9 @@ public void TestCallPlatformService([Values(true, false)] bool local)
Grid1.GetServices().Deploy(cfg);
- Grid1.GetCompute().ExecuteJavaTask