You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by pp...@apache.org on 2022/07/08 13:16:57 UTC
[ignite] branch master updated: IGNITE-17269 Java service call interceptor. (#10128)
This is an automated email from the ASF dual-hosted git repository.
ppa 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 8c408bcc2c7 IGNITE-17269 Java service call interceptor. (#10128)
8c408bcc2c7 is described below
commit 8c408bcc2c7524ada3eab789082f518c6d40b4b6
Author: Pavel Pereslegin123123 <xx...@gmail.com>
AuthorDate: Fri Jul 8 16:16:46 2022 +0300
IGNITE-17269 Java service call interceptor. (#10128)
---
.../service/CompositeServiceCallInterceptor.java | 50 +++
.../processors/service/GridServiceProxy.java | 41 +-
.../processors/service/IgniteServiceProcessor.java | 27 +-
.../service/LazyServiceConfiguration.java | 32 +-
.../processors/service/ServiceContextImpl.java | 22 +-
.../apache/ignite/services/ServiceCallContext.java | 1 +
.../ignite/services/ServiceCallInterceptor.java | 104 +++++
.../ignite/services/ServiceConfiguration.java | 31 +-
.../service/IgniteServiceCallInterceptorTest.java | 441 +++++++++++++++++++++
...ployment2ClassLoadersDefaultMarshallerTest.java | 24 +-
.../testsuites/IgniteServiceGridTestSuite.java | 4 +-
.../tests/p2p/NoopServiceCallInterceptor.java | 32 ++
.../tests/p2p/NoopServiceCallInterceptor2.java | 32 ++
13 files changed, 810 insertions(+), 31 deletions(-)
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/service/CompositeServiceCallInterceptor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/service/CompositeServiceCallInterceptor.java
new file mode 100644
index 00000000000..779660cd5d7
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/service/CompositeServiceCallInterceptor.java
@@ -0,0 +1,50 @@
+/*
+ * 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.service;
+
+import java.util.concurrent.Callable;
+import org.apache.ignite.services.ServiceCallInterceptor;
+import org.apache.ignite.services.ServiceContext;
+
+/**
+ * Composite service call interceptor.
+ */
+public class CompositeServiceCallInterceptor implements ServiceCallInterceptor {
+ /** */
+ private static final long serialVersionUID = 0L;
+
+ /** Service call interceptors. */
+ private final ServiceCallInterceptor[] interceptors;
+
+ /** @param interceptors Service call interceptors. */
+ public CompositeServiceCallInterceptor(ServiceCallInterceptor[] interceptors) {
+ this.interceptors = interceptors;
+ }
+
+ /** {@inheritDoc} */
+ @Override public Object invoke(String mtd, Object[] args, ServiceContext ctx, Callable<Object> next) throws Exception {
+ return new Callable<Object>() {
+ int idx;
+
+ @Override public Object call() throws Exception {
+ // Recursively call interceptors.
+ return interceptors[idx].invoke(mtd, args, ctx, ++idx == interceptors.length ? next : this);
+ }
+ }.call();
+ }
+}
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 95359a8f23a..f9fa1cd9db1 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
@@ -61,6 +61,7 @@ import org.apache.ignite.marshaller.Marshaller;
import org.apache.ignite.platform.PlatformServiceMethod;
import org.apache.ignite.resources.IgniteInstanceResource;
import org.apache.ignite.services.Service;
+import org.apache.ignite.services.ServiceCallInterceptor;
import org.jetbrains.annotations.Nullable;
import static org.apache.ignite.internal.processors.task.GridTaskThreadContextKey.TC_IO_POLICY;
@@ -211,8 +212,8 @@ public class GridServiceProxy<T> implements Serializable {
HistogramMetricImpl hist = svcCtx.isStatisticsEnabled() ?
invocationHistogramm(svcCtx, mtd.getName(), args) : null;
- return hist == null ? callServiceLocally(svc, mtd, args, callAttrs) :
- measureCall(hist, () -> callServiceLocally(svc, mtd, args, callAttrs));
+ return hist == null ? callServiceLocally(svcCtx, mtd, args, callAttrs) :
+ measureCall(hist, () -> callServiceLocally(svcCtx, mtd, args, callAttrs));
}
}
}
@@ -286,46 +287,58 @@ public class GridServiceProxy<T> implements Serializable {
}
/**
- * @param svc Service to be called.
+ * @param svcCtx Service context.
* @param mtd Method to call.
* @param args Method args.
* @param callAttrs Service call context attributes.
* @return Invocation result.
*/
private Object callServiceLocally(
- Service svc,
+ ServiceContextImpl svcCtx,
Method mtd,
Object[] args,
@Nullable Map<String, Object> callAttrs
) throws Exception {
+ Service svc = svcCtx.service();
+
if (svc instanceof PlatformService && !PLATFORM_SERVICE_INVOKE_METHOD.equals(mtd))
return ((PlatformService)svc).invokeMethod(methodName(mtd), false, true, args, callAttrs);
else
- return callServiceMethod(svc, mtd, args, callAttrs);
+ return callServiceMethod(svcCtx, mtd, args, callAttrs);
}
/**
- * @param svc Service to be called.
+ * @param svcCtx Service context.
* @param mtd Method to call.
* @param args Method args.
* @param callAttrs Service call context attributes.
* @return Invocation result.
*/
private static Object callServiceMethod(
- Service svc,
+ ServiceContextImpl svcCtx,
Method mtd,
Object[] args,
@Nullable Map<String, Object> callAttrs
- ) throws InvocationTargetException, IllegalAccessException {
- if (callAttrs != null)
+ ) throws Exception {
+ ServiceCallContextImpl prevCtx = null;
+
+ if (callAttrs != null) {
+ // One service can be called from another in the same thread.
+ prevCtx = ServiceCallContextHolder.current();
+
ServiceCallContextHolder.current(new ServiceCallContextImpl(callAttrs));
+ }
try {
- return mtd.invoke(svc, args);
+ ServiceCallInterceptor interceptor = svcCtx.interceptor();
+
+ return interceptor == null ?
+ mtd.invoke(svcCtx.service(), args) :
+ interceptor.invoke(mtd.getName(), args, svcCtx, () -> mtd.invoke(svcCtx.service(), args));
}
finally {
if (callAttrs != null)
- ServiceCallContextHolder.current(null);
+ ServiceCallContextHolder.current(prevCtx);
}
}
@@ -577,7 +590,7 @@ public class GridServiceProxy<T> implements Serializable {
if (svcCtx.service() instanceof PlatformService && mtd == null)
return callPlatformService((PlatformService)svcCtx.service());
else
- return callOrdinaryService(svcCtx.service(), mtd);
+ return callOrdinaryService(svcCtx, mtd);
}
/** */
@@ -594,12 +607,12 @@ public class GridServiceProxy<T> implements Serializable {
}
/** */
- private Object callOrdinaryService(Service srv, Method mtd) throws Exception {
+ private Object callOrdinaryService(ServiceContextImpl svcCtx, Method mtd) throws Exception {
if (mtd == null)
throw new GridServiceMethodNotFoundException(svcName, mtdName, argTypes);
try {
- return callServiceMethod(srv, mtd, args, callAttrs);
+ return callServiceMethod(svcCtx, mtd, args, callAttrs);
}
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 ad8f00dd9ea..50312826432 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
@@ -88,8 +88,8 @@ import org.apache.ignite.marshaller.jdk.JdkMarshaller;
import org.apache.ignite.plugin.security.SecurityException;
import org.apache.ignite.plugin.security.SecurityPermission;
import org.apache.ignite.services.Service;
+import org.apache.ignite.services.ServiceCallInterceptor;
import org.apache.ignite.services.ServiceConfiguration;
-import org.apache.ignite.services.ServiceContext;
import org.apache.ignite.services.ServiceDeploymentException;
import org.apache.ignite.services.ServiceDescriptor;
import org.apache.ignite.spi.IgniteNodeValidationResult;
@@ -674,11 +674,12 @@ public class IgniteServiceProcessor extends GridProcessorAdapter implements Igni
if (err == null) {
try {
byte[] srvcBytes = U.marshal(marsh, cfg.getService());
+ byte[] interceptorsBytes = U.marshal(marsh, cfg.getInterceptors());
String[] knownSvcMdtNames = cfg instanceof PlatformServiceConfiguration ?
((PlatformServiceConfiguration)cfg).mtdNames() : null;
- cfgsCp.add(new LazyServiceConfiguration(cfg, srvcBytes).platformMtdNames(knownSvcMdtNames));
+ cfgsCp.add(new LazyServiceConfiguration(cfg, srvcBytes, interceptorsBytes).platformMtdNames(knownSvcMdtNames));
}
catch (Exception e) {
U.error(log, "Failed to marshal service with configured marshaller " +
@@ -1368,19 +1369,31 @@ public class IgniteServiceProcessor extends GridProcessorAdapter implements Igni
* @return Copy of service.
* @throws IgniteCheckedException If failed.
*/
- private Service copyAndInject(ServiceConfiguration cfg, ServiceContext svcCtx) throws IgniteCheckedException {
+ private Service copyAndInject(ServiceConfiguration cfg, ServiceContextImpl svcCtx) throws IgniteCheckedException {
if (cfg instanceof LazyServiceConfiguration) {
LazyServiceConfiguration srvcCfg = (LazyServiceConfiguration)cfg;
GridDeployment srvcDep = ctx.deploy().getDeployment(srvcCfg.serviceClassName());
+ ClassLoader clsLdr = U.resolveClassLoader(srvcDep != null ? srvcDep.classLoader() : null, ctx.config());
+ byte[] bytes = srvcCfg.serviceBytes();
- byte[] bytes = ((LazyServiceConfiguration)cfg).serviceBytes();
-
- Service srvc = U.unmarshal(marsh, bytes,
- U.resolveClassLoader(srvcDep != null ? srvcDep.classLoader() : null, ctx.config()));
+ Service srvc = U.unmarshal(marsh, bytes, clsLdr);
ctx.resource().inject(srvc, svcCtx);
+ ServiceCallInterceptor[] interceptors = U.unmarshal(marsh, srvcCfg.interceptorBytes(), clsLdr);
+
+ if (F.isEmpty(interceptors))
+ return srvc;
+
+ // Inject generic resources.
+ for (int i = 0; i < interceptors.length; i++)
+ ctx.resource().injectGeneric(interceptors[i]);
+
+ // Wrap in a composite interceptor if necessary.
+ svcCtx.interceptor(interceptors.length == 1 ?
+ interceptors[0] : new CompositeServiceCallInterceptor(interceptors));
+
return srvc;
}
else {
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/service/LazyServiceConfiguration.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/service/LazyServiceConfiguration.java
index d0d4df13f2f..e4e7a6d4707 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/service/LazyServiceConfiguration.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/service/LazyServiceConfiguration.java
@@ -17,11 +17,14 @@
package org.apache.ignite.internal.processors.service;
+import java.util.Arrays;
import org.apache.ignite.internal.util.tostring.GridToStringExclude;
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.ServiceCallInterceptor;
import org.apache.ignite.services.ServiceConfiguration;
+import org.jetbrains.annotations.Nullable;
/**
* Lazy service configuration.
@@ -34,12 +37,19 @@ public class LazyServiceConfiguration extends ServiceConfiguration {
@GridToStringExclude
private transient Service srvc;
+ /** Service interceptors. */
+ @GridToStringExclude
+ private transient ServiceCallInterceptor[] interceptors;
+
/** */
private String srvcClsName;
/** */
private byte[] srvcBytes;
+ /** */
+ private byte[] interceptorsBytes;
+
/** Names of platform service methods to build service statistics. */
@GridToStringExclude
private String[] platformMtdNames;
@@ -53,9 +63,10 @@ public class LazyServiceConfiguration extends ServiceConfiguration {
/**
* @param cfg Configuration.
- * @param srvcBytes Marshaller service.
+ * @param srvcBytes Marshalled service.
+ * @param interceptorsBytes Marshalled interceptors.
*/
- public LazyServiceConfiguration(ServiceConfiguration cfg, byte[] srvcBytes) {
+ public LazyServiceConfiguration(ServiceConfiguration cfg, byte[] srvcBytes, @Nullable byte[] interceptorsBytes) {
assert cfg.getService() != null : cfg;
assert srvcBytes != null;
@@ -69,6 +80,8 @@ public class LazyServiceConfiguration extends ServiceConfiguration {
srvc = cfg.getService();
srvcClsName = srvc.getClass().getName();
isStatisticsEnabled = cfg.isStatisticsEnabled();
+ interceptors = cfg.getInterceptors();
+ this.interceptorsBytes = interceptorsBytes;
}
/**
@@ -92,6 +105,18 @@ public class LazyServiceConfiguration extends ServiceConfiguration {
return srvc;
}
+ /** {@inheritDoc} */
+ @Override public ServiceCallInterceptor[] getInterceptors() {
+ return interceptors;
+ }
+
+ /**
+ * @return Interceptors bytes.
+ */
+ public byte[] interceptorBytes() {
+ return interceptorsBytes;
+ }
+
/** {@inheritDoc} */
@SuppressWarnings("RedundantIfStatement")
@Override public boolean equalsIgnoreNodeFilter(Object o) {
@@ -121,6 +146,9 @@ public class LazyServiceConfiguration extends ServiceConfiguration {
if (!F.eq(srvcClsName, that.srvcClsName))
return false;
+ if (!Arrays.equals(interceptorsBytes, that.interceptorsBytes))
+ return false;
+
return true;
}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/service/ServiceContextImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/service/ServiceContextImpl.java
index a5aa112b59d..5331cb1f6eb 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/service/ServiceContextImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/service/ServiceContextImpl.java
@@ -27,6 +27,7 @@ import org.apache.ignite.internal.util.tostring.GridToStringInclude;
import org.apache.ignite.internal.util.typedef.internal.S;
import org.apache.ignite.services.Service;
import org.apache.ignite.services.ServiceCallContext;
+import org.apache.ignite.services.ServiceCallInterceptor;
import org.apache.ignite.services.ServiceContext;
import org.apache.ignite.spi.metric.ReadOnlyMetricRegistry;
import org.jetbrains.annotations.Nullable;
@@ -68,6 +69,10 @@ public class ServiceContextImpl implements ServiceContext {
@GridToStringExclude
private volatile Service svc;
+ /** Service call interceptor. */
+ @GridToStringExclude
+ private volatile ServiceCallInterceptor interceptor;
+
/** Cancelled flag. */
private volatile boolean isCancelled;
@@ -87,7 +92,8 @@ public class ServiceContextImpl implements ServiceContext {
String cacheName,
Object affKey,
ExecutorService exe,
- boolean statisticsEnabled) {
+ boolean statisticsEnabled
+ ) {
this.name = name;
this.execId = execId;
this.cacheName = cacheName;
@@ -135,6 +141,20 @@ public class ServiceContextImpl implements ServiceContext {
return svc;
}
+ /**
+ * @param interceptor Service call interceptor.
+ */
+ void interceptor(ServiceCallInterceptor interceptor) {
+ this.interceptor = interceptor;
+ }
+
+ /**
+ * @return Service call interceptor.
+ */
+ ServiceCallInterceptor interceptor() {
+ return interceptor;
+ }
+
/**
* @return Executor service.
*/
diff --git a/modules/core/src/main/java/org/apache/ignite/services/ServiceCallContext.java b/modules/core/src/main/java/org/apache/ignite/services/ServiceCallContext.java
index 8eae10bafb8..978a68a0f16 100644
--- a/modules/core/src/main/java/org/apache/ignite/services/ServiceCallContext.java
+++ b/modules/core/src/main/java/org/apache/ignite/services/ServiceCallContext.java
@@ -55,6 +55,7 @@ import org.apache.ignite.lang.IgniteExperimental;
*
* @see ServiceContext
* @see ServiceCallContextBuilder
+ * @see ServiceCallInterceptor
*/
@IgniteExperimental
public interface ServiceCallContext {
diff --git a/modules/core/src/main/java/org/apache/ignite/services/ServiceCallInterceptor.java b/modules/core/src/main/java/org/apache/ignite/services/ServiceCallInterceptor.java
new file mode 100644
index 00000000000..44ff08221a6
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/services/ServiceCallInterceptor.java
@@ -0,0 +1,104 @@
+/*
+ * 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.services;
+
+import java.io.Serializable;
+import java.util.concurrent.Callable;
+import org.apache.ignite.lang.IgniteExperimental;
+
+/**
+ * Service call interceptor.
+ * <p>
+ * Allows the user to intercept the call to any service method except lifecycle methods
+ * ({@link Service#init init()}, {@link Service#execute() execute()} and {@link Service#cancel() cancel()}).
+ * <p>
+ * A typical use of an interceptor is a middleware logic that applies to all custom methods in a service.
+ * The user can specify multiple interceptors in the {@link ServiceConfiguration service configuration}.
+ * Each interceptor invokes the next interceptor in the chain using a delegated call, the last interceptor
+ * will call the service method.
+ * <p>
+ * Usage example:
+ * <pre name="code" class="java">
+ * ServiceCallInterceptor security = (mtd, args, ctx, svcCall) -> {
+ * if (!CustomSecurityProvider.get().access(mtd, ctx.currentCallContext().attribute("sessionId")))
+ * throw new SecurityException("Method invocation is not permitted");
+ *
+ * // Execute remaining interceptors and service method.
+ * return svcCall.call();
+ * };
+ *
+ * ServiceCallInterceptor audit = (mtd, args, ctx, svcCall) -> {
+ * String sessionId = ctx.currentCallContext().attribute("sessionId");
+ * AuditProvider prov = AuditProvider.get();
+ *
+ * // Record an event before execution of the method.
+ * prov.recordStartEvent(ctx.name(), mtd, sessionId);
+ *
+ * try {
+ * // Execute service method.
+ * svcCall.call();
+ * }
+ * catch (Exception e) {
+ * // Record error.
+ * prov.recordError(ctx.name(), mtd, sessionId), e.getMessage());
+ *
+ * // Re-throw exception to initiator.
+ * throw e;
+ * }
+ * finally {
+ * // Record finish event after execution of the service method.
+ * prov.recordFinishEvent(ctx.name(), mtd, sessionId);
+ * }
+ * }
+ *
+ * ServiceConfiguration svcCfg = new ServiceConfiguration()
+ * .setName("service")
+ * .setService(new MyServiceImpl())
+ * .setMaxPerNodeCount(1)
+ * .setInterceptors(audit, security);
+ *
+ * // Deploy service.
+ * ignite.services().deploy(svcCfg);
+ *
+ * // Set context parameters for the service proxy.
+ * ServiceCallContext callCtx = ServiceCallContext.builder().put("sessionId", sessionId).build();
+ *
+ * // Make service proxy with caller context.
+ * MyService proxy = ignite.services().serviceProxy("service", MyService.class, false, callCtx, 0);
+ *
+ * // The call to the business method to be intercepted.
+ * proxy.placeOrder(order1);
+ * proxy.placeOrder(order2);
+ * </pre>
+ *
+ * @see ServiceCallContext
+ * @see ServiceContext
+ */
+@IgniteExperimental
+public interface ServiceCallInterceptor extends Serializable {
+ /**
+ * Intercepts delegated service call.
+ *
+ * @param mtd Method name.
+ * @param args Method arguments.
+ * @param ctx Service context.
+ * @param next Delegated call to a service method and/or interceptor in the chain.
+ * @return Service call result.
+ */
+ public Object invoke(String mtd, Object[] args, ServiceContext ctx, Callable<Object> next) throws Exception;
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/services/ServiceConfiguration.java b/modules/core/src/main/java/org/apache/ignite/services/ServiceConfiguration.java
index ad2c6948737..76311c1b977 100644
--- a/modules/core/src/main/java/org/apache/ignite/services/ServiceConfiguration.java
+++ b/modules/core/src/main/java/org/apache/ignite/services/ServiceConfiguration.java
@@ -19,11 +19,13 @@ package org.apache.ignite.services;
import java.io.Externalizable;
import java.io.Serializable;
+import java.util.Arrays;
import org.apache.ignite.IgniteServices;
import org.apache.ignite.cluster.ClusterNode;
import org.apache.ignite.internal.processors.service.IgniteServiceProcessor;
import org.apache.ignite.internal.util.tostring.GridToStringExclude;
import org.apache.ignite.internal.util.typedef.internal.S;
+import org.apache.ignite.lang.IgniteExperimental;
import org.apache.ignite.lang.IgnitePredicate;
/**
@@ -83,6 +85,10 @@ public class ServiceConfiguration implements Serializable {
/** Enables or disables service statistics. */
protected boolean isStatisticsEnabled;
+ /** Interceptor. */
+ @GridToStringExclude
+ protected ServiceCallInterceptor[] interceptors;
+
/**
* Gets service name.
* <p>
@@ -292,6 +298,29 @@ public class ServiceConfiguration implements Serializable {
return isStatisticsEnabled;
}
+ /**
+ * Gets service call interceptors.
+ *
+ * @return Service call interceptors.
+ */
+ @IgniteExperimental
+ public ServiceCallInterceptor[] getInterceptors() {
+ return interceptors;
+ }
+
+ /**
+ * Sets service call interceptors.
+ *
+ * @param interceptors Service call interceptors.
+ * @return {@code this} for chaining.
+ */
+ @IgniteExperimental
+ public ServiceConfiguration setInterceptors(ServiceCallInterceptor... interceptors) {
+ this.interceptors = interceptors;
+
+ return this;
+ }
+
/** {@inheritDoc} */
@Override public boolean equals(Object o) {
if (!equalsIgnoreNodeFilter(o))
@@ -344,7 +373,7 @@ public class ServiceConfiguration implements Serializable {
if (svc != null ? !svc.getClass().equals(that.svc.getClass()) : that.svc != null)
return false;
- return true;
+ return Arrays.deepEquals(interceptors, that.interceptors);
}
/** {@inheritDoc} */
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/service/IgniteServiceCallInterceptorTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/service/IgniteServiceCallInterceptorTest.java
new file mode 100644
index 00000000000..832b982e870
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/service/IgniteServiceCallInterceptorTest.java
@@ -0,0 +1,441 @@
+/*
+ * 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.service;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import javax.naming.NoPermissionException;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteLogger;
+import org.apache.ignite.IgniteServices;
+import org.apache.ignite.internal.util.typedef.internal.SB;
+import org.apache.ignite.resources.IgniteInstanceResource;
+import org.apache.ignite.resources.LoggerResource;
+import org.apache.ignite.resources.ServiceContextResource;
+import org.apache.ignite.resources.ServiceResource;
+import org.apache.ignite.services.Service;
+import org.apache.ignite.services.ServiceCallContext;
+import org.apache.ignite.services.ServiceCallInterceptor;
+import org.apache.ignite.services.ServiceConfiguration;
+import org.apache.ignite.services.ServiceContext;
+import org.apache.ignite.services.ServiceDeploymentException;
+import org.apache.ignite.testframework.GridTestUtils;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+/**
+ * Tests service call interceptor.
+ */
+@RunWith(Parameterized.class)
+public class IgniteServiceCallInterceptorTest extends GridCommonAbstractTest implements Serializable {
+ /** */
+ private static final long serialVersionUID = 0L;
+
+ /** String attribute name. */
+ private static final String STR_ATTR_NAME = "str.attr";
+
+ /** Injected service name. */
+ private static final String SVC_NAME_INJECTED = "test-svc-injected";
+
+ /** Intercepted service name. */
+ private static final String SVC_NAME_INTERCEPTED = "test-svc-intercepted";
+
+ /** Nodes count. */
+ private static final int NODES_CNT = 3;
+
+ /** Max number of services per node. */
+ private static final int SVC_PER_NODE = 2;
+
+ /** Interceptor call counter. */
+ private static final AtomicInteger interceptorCallCntr = new AtomicInteger();
+
+ /** Service method call counter. */
+ private static final AtomicInteger svcCallCntr = new AtomicInteger();
+
+ /** Ignite client node. */
+ private static Ignite client;
+
+ /** Flag to deploy single service instance per cluster. */
+ @Parameterized.Parameter(0)
+ public boolean clusterSingleton;
+
+ /** Whether Ignite should always contact the same remote service instance. */
+ @Parameterized.Parameter(1)
+ public boolean sticky;
+
+ /** {@inheritDoc} */
+ @Override public void beforeTestsStarted() throws Exception {
+ startGrids(NODES_CNT - 1);
+ client = startClientGrid(NODES_CNT - 1);
+ }
+
+ /** */
+ @Parameterized.Parameters(name = "clusterSingleton={0}, sticky={1}")
+ public static Collection<?> parameters() {
+ return Arrays.asList(new Object[][] {
+ {false, false},
+ {false, true},
+ {true, true},
+ });
+ }
+
+ /** {@inheritDoc} */
+ @Override public void afterTest() {
+ grid(0).services().cancelAll();
+ }
+
+ /**
+ * @param name Service name.
+ * @param svc Service.
+ * @param singleton Flag to deploy single service instance per cluster.
+ * @param interceptors Interceptors.
+ * @return Service configuration.
+ */
+ private ServiceConfiguration serviceCfg(String name, Service svc, boolean singleton, ServiceCallInterceptor... interceptors) {
+ return new ServiceConfiguration()
+ .setName(name)
+ .setTotalCount(singleton ? 1 : NODES_CNT * SVC_PER_NODE)
+ .setMaxPerNodeCount(SVC_PER_NODE)
+ .setService(svc)
+ .setInterceptors(interceptors);
+ }
+
+ /**
+ * Validates configuration comparison during service deployment.
+ */
+ @Test
+ public void testRedeploy() {
+ ServiceCallInterceptor interceptor = (mtd, args, ctx, next) -> "1";
+
+ ServiceConfiguration cfg = serviceCfg(SVC_NAME_INTERCEPTED, new TestServiceImpl(), clusterSingleton, interceptor);
+ client.services().deploy(cfg);
+
+ TestService proxy = client.services().serviceProxy(SVC_NAME_INTERCEPTED, TestService.class, false);
+ assertEquals("1", proxy.method(0));
+
+ // Redeploy with the same configuration.
+ cfg.setInterceptors(interceptor);
+ client.services().deploy(cfg);
+
+ // Redeploy with different configuration.
+ cfg.setInterceptors((mtd, args, ctx, next) -> "2");
+
+ GridTestUtils.assertThrowsAnyCause(log, () -> {
+ client.services().deploy(cfg);
+
+ return null;
+ }, ServiceDeploymentException.class, null);
+
+ // Redeploy the service with a valid configuration.
+ client.services().cancel(SVC_NAME_INTERCEPTED);
+
+ client.services().deploy(cfg);
+ assertEquals("2", proxy.method(0));
+
+ // Undeploy service.
+ client.services().cancel(SVC_NAME_INTERCEPTED);
+
+ // Deploy with an interceptor that will not be able to deserialize.
+ cfg.setInterceptors(new CriticalErrorInterceptor());
+
+ GridTestUtils.assertThrowsAnyCause(log, () -> {
+ client.services().deploy(cfg);
+
+ return null;
+ }, ServiceDeploymentException.class, null);
+
+ for (int i = 0; i < NODES_CNT - 1; i++)
+ assertNull("idx=" + i, grid(i).services().service(SVC_NAME_INTERCEPTED));
+ }
+
+ /**
+ * Checks if an interceptor exception is being passed to the user.
+ */
+ @Test
+ public void testException() {
+ interceptorCallCntr.set(0);
+ svcCallCntr.set(0);
+
+ Exception expE = new NoPermissionException("Request is forbidden.");
+ String ctxVal = "42";
+
+ ServiceCallInterceptor security = (mtd, args, ctx, next) -> {
+ ServiceCallContext callCtx = ctx.currentCallContext();
+
+ if (callCtx == null || !ctxVal.equals(callCtx.attribute(STR_ATTR_NAME)))
+ throw expE;
+
+ return next.call();
+ };
+
+ ServiceCallInterceptor second = (mtd, args, ctx, next) -> {
+ interceptorCallCntr.incrementAndGet();
+
+ return next.call();
+ };
+
+ client.services().deployAll(Collections.singletonList(
+ serviceCfg(SVC_NAME_INTERCEPTED, new TestServiceImpl(), clusterSingleton, security, second)
+ ));
+
+ TestService noCtxProxy = client.services().serviceProxy(SVC_NAME_INTERCEPTED, TestService.class, sticky);
+ GridTestUtils.assertThrowsAnyCause(null, () -> noCtxProxy.method(0), expE.getClass(), expE.getMessage());
+ assertEquals(0, interceptorCallCntr.get());
+ assertEquals(0, svcCallCntr.get());
+
+ ServiceCallContext callCtx = ServiceCallContext.builder().put(STR_ATTR_NAME, "42").build();
+
+ String res = client.services().serviceProxy(SVC_NAME_INTERCEPTED, TestService.class, sticky, callCtx).method(0);
+ assertEquals("cls=" + TestServiceImpl.class.getSimpleName() + ", ctxVal=" + ctxVal + ", arg=0", res);
+ assertEquals(1, interceptorCallCntr.get());
+ assertEquals(1, svcCallCntr.get());
+ }
+
+ /**
+ * Ensures that the interceptor can completely override the call and prevent the service method invocation.
+ */
+ @Test
+ public void testBasicInterception() {
+ svcCallCntr.set(0);
+
+ String expMsg = "intercepted";
+
+ client.services().deployAll(Collections.singletonList(
+ serviceCfg(SVC_NAME_INTERCEPTED, new TestServiceImpl(), clusterSingleton, (mtd, args, ctx, next) -> expMsg)
+ ));
+
+ TestService svcProxy = client.services().serviceProxy(SVC_NAME_INTERCEPTED, TestService.class, sticky);
+
+ assertEquals(expMsg, svcProxy.method(1));
+ assertEquals(0, svcCallCntr.get());
+ }
+
+ /**
+ * Checks the call order and parameters of the interceptor in multithreaded mode.
+ *
+ * @throws Exception If failed.
+ */
+ @Test
+ public void testOrder() throws Exception {
+ int interceptorsCnt = 8;
+ int threadCnt = 16;
+
+ ServiceCallInterceptor[] interceptors = new ServiceCallInterceptor[interceptorsCnt];
+
+ for (int i = 0; i < interceptorsCnt; i++)
+ interceptors[interceptorsCnt - i - 1] = new SvcInterceptor(null, i);
+
+ client.services().deployAll(Arrays.asList(
+ serviceCfg(SVC_NAME_INTERCEPTED, new TestServiceImpl(), clusterSingleton, interceptors),
+ serviceCfg(SVC_NAME_INJECTED, new TestServiceInjected(), !clusterSingleton, interceptors)
+ ));
+
+ ServiceCallContext callCtx1 = ServiceCallContext.builder().put(STR_ATTR_NAME, "ctxVal1").build();
+ ServiceCallContext callCtx2 = ServiceCallContext.builder().put(STR_ATTR_NAME, "ctxVal2").build();
+
+ AtomicInteger cntr = new AtomicInteger();
+ CyclicBarrier barrier = new CyclicBarrier(threadCnt);
+
+ GridTestUtils.runMultiThreaded(() -> {
+ int arg = cntr.incrementAndGet();
+ IgniteServices services = grid(arg % NODES_CNT).services();
+
+ TestService ctx1Proxy = services.serviceProxy(SVC_NAME_INTERCEPTED, TestService.class, sticky, callCtx1);
+ TestService ctx2Proxy = services.serviceProxy(SVC_NAME_INTERCEPTED, TestService.class, sticky, callCtx2);
+
+ String exoCtx1 = fmtExpResult(TestServiceImpl.class, callCtx1, interceptors.length, "method", null, arg);
+ String exoCtx2 = fmtExpResult(TestServiceImpl.class, callCtx2, interceptors.length, "method", null, arg);
+ String exoCtx1inj = fmtExpResult(TestServiceInjected.class, callCtx1, interceptors.length, "method", "injected", arg);
+ String exoCtx2inj = fmtExpResult(TestServiceInjected.class, callCtx2, interceptors.length, "method", "injected", arg);
+
+ barrier.await(getTestTimeout(), TimeUnit.MILLISECONDS);
+
+ for (int i = 0; i < 100; i++) {
+ assertEquals(exoCtx1inj, ctx1Proxy.injected(arg));
+ assertEquals(exoCtx2inj, ctx2Proxy.injected(arg));
+ assertEquals(exoCtx1, ctx1Proxy.method(arg));
+ assertEquals(exoCtx2, ctx2Proxy.method(arg));
+ }
+
+ return null;
+ }, threadCnt, "worker");
+ }
+
+ /**
+ * @param cls Service implementation class name.
+ * @param ctx Service call context.
+ * @param incpsCnt Count of interceptors.
+ * @param mtd1 First method that was called.
+ * @param mtd2 Second method that was called (if one service calls another).
+ * @param arg Method argument.
+ * @return Expected result of the method call.
+ */
+ private String fmtExpResult(Class<?> cls, ServiceCallContext ctx, int incpsCnt, String mtd1, String mtd2, int arg) {
+ SB buf = new SB("cls=" + cls.getSimpleName() + ", ctxVal=" + ctx.attribute(STR_ATTR_NAME) + ", arg=" + arg);
+
+ for (String method : new String[] {mtd1, mtd2}) {
+ if (method == null)
+ break;
+
+ for (int i = 0; i < incpsCnt; i++)
+ buf.a("; mtd=").a(method).a(", ctxVal=").a(ctx.attribute(STR_ATTR_NAME)).a(", arg=").a(arg).a(", id=").a(i);
+ }
+
+ return buf.toString();
+ }
+
+ /** */
+ private interface TestService extends Service {
+ /** */
+ String method(int arg);
+
+ /** */
+ String injected(int arg);
+ }
+
+ /** */
+ private static class TestServiceImpl implements TestService {
+ /** Injected service. */
+ @ServiceResource(serviceName = SVC_NAME_INJECTED, proxyInterface = TestService.class, forwardCallerContext = true)
+ private TestService injected;
+
+ /** Service context. */
+ @ServiceContextResource
+ private ServiceContext ctx;
+
+ /** {@inheritDoc} */
+ @Override public String method(int arg) {
+ svcCallCntr.incrementAndGet();
+
+ ServiceCallContext callCtx = ctx.currentCallContext();
+
+ String attrVal = callCtx == null ? null : ctx.currentCallContext().attribute(STR_ATTR_NAME);
+
+ return "cls=" + getClass().getSimpleName() + ", ctxVal=" + attrVal + ", arg=" + arg;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String injected(int arg) {
+ assert injected != null;
+
+ return injected.method(arg);
+ }
+ }
+
+ /** */
+ private static class TestServiceInjected extends TestServiceImpl {
+ /** */
+ private static final long serialVersionUID = 0L;
+ }
+
+ /** Service call interceptor. */
+ static class SvcInterceptor implements ServiceCallInterceptor {
+ /** */
+ private static final long serialVersionUID = 0L;
+
+ /** Ignite. */
+ @IgniteInstanceResource
+ private Ignite ignite;
+
+ /** Logger. */
+ @LoggerResource
+ private IgniteLogger log;
+
+ /** Expected method name. */
+ private final String expMtdName;
+
+ /** Interceptor ID. */
+ private final int id;
+
+ /**
+ * @param expMtdName Expected method name.
+ * @param id Interceptor ID.
+ */
+ public SvcInterceptor(String expMtdName, int id) {
+ this.expMtdName = expMtdName;
+ this.id = id;
+ }
+
+ /** {@inheritDoc} */
+ @Override public Object invoke(String mtd, Object[] args, ServiceContext ctx, Callable<Object> next) throws Exception {
+ assert ignite != null;
+ assert log != null;
+
+ if (expMtdName != null)
+ assertEquals(expMtdName, mtd);
+
+ Object arg = args[0];
+
+ ServiceCallContext callCtx = ctx.currentCallContext();
+
+ assert callCtx != null;
+
+ Object res = next.call();
+
+ callCtx = ctx.currentCallContext();
+
+ assert callCtx != null : res;
+
+ String ctxVal = callCtx == null ? null : ctx.currentCallContext().attribute(STR_ATTR_NAME);
+
+ assert ctxVal != null;
+
+ return res + "; mtd=" + mtd + ", ctxVal=" + ctxVal + ", arg=" + arg + ", id=" + id;
+ }
+ }
+
+ /** */
+ private static class CriticalErrorInterceptor implements ServiceCallInterceptor, Externalizable {
+ /** Serial version uid. */
+ private static final long serialVersionUID = 0L;
+
+ /** */
+ public CriticalErrorInterceptor() {
+ // No-op.
+ }
+
+ /** {@inheritDoc} */
+ @Override public Object invoke(String mtd, Object[] args, ServiceContext ctx,
+ Callable<Object> next) throws Exception {
+
+ return next.call();
+ }
+
+ /** {@inheritDoc} */
+ @Override public void writeExternal(ObjectOutput out) throws IOException {
+ // No-op.
+ }
+
+ /** {@inheritDoc} */
+ @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+ throw new AssertionError("Expected critical error.");
+ }
+ }
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/service/IgniteServiceDeployment2ClassLoadersDefaultMarshallerTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/service/IgniteServiceDeployment2ClassLoadersDefaultMarshallerTest.java
index c8b9f879eae..86de5c848f2 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/service/IgniteServiceDeployment2ClassLoadersDefaultMarshallerTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/service/IgniteServiceDeployment2ClassLoadersDefaultMarshallerTest.java
@@ -26,6 +26,7 @@ import org.apache.ignite.configuration.IgniteConfiguration;
import org.apache.ignite.lang.IgnitePredicate;
import org.apache.ignite.marshaller.Marshaller;
import org.apache.ignite.services.Service;
+import org.apache.ignite.services.ServiceCallInterceptor;
import org.apache.ignite.services.ServiceConfiguration;
import org.apache.ignite.testframework.GridTestExternalClassLoader;
import org.apache.ignite.testframework.config.GridTestProperties;
@@ -33,15 +34,21 @@ import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
import org.junit.Test;
/**
- * Tests that not all nodes in cluster need user's service definition (only nodes according to filter).
+ * Tests that not all nodes in cluster need user's service and interceptor definition (only nodes according to filter).
*/
public class IgniteServiceDeployment2ClassLoadersDefaultMarshallerTest extends GridCommonAbstractTest {
/** */
private static final String NOOP_SERVICE_CLS_NAME = "org.apache.ignite.tests.p2p.NoopService";
+ /** */
+ private static final String NOOP_SERVICE_INTCP_NAME = "org.apache.ignite.tests.p2p.NoopServiceCallInterceptor";
+
/** */
private static final String NOOP_SERVICE_2_CLS_NAME = "org.apache.ignite.tests.p2p.NoopService2";
+ /** */
+ private static final String NOOP_SERVICE_INTCP_2_NAME = "org.apache.ignite.tests.p2p.NoopServiceCallInterceptor2";
+
/** */
private static final int GRID_CNT = 6;
@@ -115,8 +122,8 @@ public class IgniteServiceDeployment2ClassLoadersDefaultMarshallerTest extends G
/** {@inheritDoc} */
@Override protected void beforeTestsStarted() throws Exception {
- extClsLdr1 = new GridTestExternalClassLoader(URLS, NOOP_SERVICE_2_CLS_NAME);
- extClsLdr2 = new GridTestExternalClassLoader(URLS, NOOP_SERVICE_CLS_NAME);
+ extClsLdr1 = new GridTestExternalClassLoader(URLS, NOOP_SERVICE_2_CLS_NAME, NOOP_SERVICE_INTCP_2_NAME);
+ extClsLdr2 = new GridTestExternalClassLoader(URLS, NOOP_SERVICE_CLS_NAME, NOOP_SERVICE_INTCP_NAME);
}
/** {@inheritDoc} */
@@ -200,15 +207,22 @@ public class IgniteServiceDeployment2ClassLoadersDefaultMarshallerTest extends G
srvCfg.setNodeFilter(new TestNodeFilter(firstGrp ? grp1 : grp2));
Class<Service> srvcCls;
+ Class<ServiceCallInterceptor> interceptorCls;
- if (firstGrp)
+ if (firstGrp) {
srvcCls = (Class<Service>)extClsLdr1.loadClass(NOOP_SERVICE_CLS_NAME);
- else
+ interceptorCls = (Class<ServiceCallInterceptor>)extClsLdr1.loadClass(NOOP_SERVICE_INTCP_NAME);
+ }
+ else {
srvcCls = (Class<Service>)extClsLdr2.loadClass(NOOP_SERVICE_2_CLS_NAME);
+ interceptorCls = (Class<ServiceCallInterceptor>)extClsLdr2.loadClass(NOOP_SERVICE_INTCP_2_NAME);
+ }
Service srvc = srvcCls.newInstance();
+ ServiceCallInterceptor interceptor = interceptorCls.newInstance();
srvCfg.setService(srvc);
+ srvCfg.setInterceptors(interceptor);
srvCfg.setName("TestDeploymentService" + (firstGrp ? 1 : 2));
diff --git a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteServiceGridTestSuite.java b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteServiceGridTestSuite.java
index a1fe470a118..a5ee7fe1bba 100644
--- a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteServiceGridTestSuite.java
+++ b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteServiceGridTestSuite.java
@@ -39,6 +39,7 @@ import org.apache.ignite.internal.processors.service.GridServiceProxyTopologyIni
import org.apache.ignite.internal.processors.service.GridServiceReassignmentSelfTest;
import org.apache.ignite.internal.processors.service.GridServiceSerializationSelfTest;
import org.apache.ignite.internal.processors.service.IgniteServiceCallContextTest;
+import org.apache.ignite.internal.processors.service.IgniteServiceCallInterceptorTest;
import org.apache.ignite.internal.processors.service.IgniteServiceDeployment2ClassLoadersDefaultMarshallerTest;
import org.apache.ignite.internal.processors.service.IgniteServiceDeployment2ClassLoadersJdkMarshallerTest;
import org.apache.ignite.internal.processors.service.IgniteServiceDeployment2ClassLoadersOptimizedMarshallerTest;
@@ -122,7 +123,8 @@ import org.junit.runners.Suite;
GridServiceDeployClusterReadOnlyModeTest.class,
GridServiceClusterReadOnlyModeTest.class,
IgniteServiceCallContextTest.class,
- GridServiceMetricsTest.class
+ GridServiceMetricsTest.class,
+ IgniteServiceCallInterceptorTest.class
})
public class IgniteServiceGridTestSuite {
/** */
diff --git a/modules/extdata/p2p/src/main/java/org/apache/ignite/tests/p2p/NoopServiceCallInterceptor.java b/modules/extdata/p2p/src/main/java/org/apache/ignite/tests/p2p/NoopServiceCallInterceptor.java
new file mode 100644
index 00000000000..cc517d4a61c
--- /dev/null
+++ b/modules/extdata/p2p/src/main/java/org/apache/ignite/tests/p2p/NoopServiceCallInterceptor.java
@@ -0,0 +1,32 @@
+/*
+ * 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.tests.p2p;
+
+import java.util.concurrent.Callable;
+import org.apache.ignite.services.ServiceCallInterceptor;
+import org.apache.ignite.services.ServiceContext;
+
+/**
+ * No-op service call interceptor.
+ */
+public class NoopServiceCallInterceptor implements ServiceCallInterceptor {
+ /** {@inheritDoc} */
+ @Override public Object invoke(String mtd, Object[] args, ServiceContext ctx, Callable<Object> next) throws Exception {
+ return next.call();
+ }
+}
diff --git a/modules/extdata/p2p/src/main/java/org/apache/ignite/tests/p2p/NoopServiceCallInterceptor2.java b/modules/extdata/p2p/src/main/java/org/apache/ignite/tests/p2p/NoopServiceCallInterceptor2.java
new file mode 100644
index 00000000000..d5cc6021f82
--- /dev/null
+++ b/modules/extdata/p2p/src/main/java/org/apache/ignite/tests/p2p/NoopServiceCallInterceptor2.java
@@ -0,0 +1,32 @@
+/*
+ * 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.tests.p2p;
+
+import java.util.concurrent.Callable;
+import org.apache.ignite.services.ServiceCallInterceptor;
+import org.apache.ignite.services.ServiceContext;
+
+/**
+ * No-op service call interceptor.
+ */
+public class NoopServiceCallInterceptor2 implements ServiceCallInterceptor {
+ /** {@inheritDoc} */
+ @Override public Object invoke(String mtd, Object[] args, ServiceContext ctx, Callable<Object> next) throws Exception {
+ return next.call();
+ }
+}