Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* 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.examples;

import org.apache.ignite.services.Service;

public interface EchoService extends Service {
/**
* Shows how to read parameter implicitly passed to the service.
*/
String hello();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* 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.examples;

import java.util.concurrent.Callable;
import org.apache.ignite.services.ServiceCallContext;
import org.apache.ignite.services.ServiceCallInterceptor;
import org.apache.ignite.services.ServiceContext;

//tag::service-call-interceptor[]
public class EchoServiceAccessInterceptor implements ServiceCallInterceptor {
/** {@inheritDoc} */
@Override public Object invoke(String mtd, Object[] args, ServiceContext ctx, Callable<Object> next) throws Exception {
ServiceCallContext callCtx = ctx.currentCallContext();

if (callCtx == null || callCtx.attribute("user") == null)
throw new SecurityException("Anonymous access is restricted.");

return next.call();
}
}
//end::service-call-interceptor[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* 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.examples;

import org.apache.ignite.resources.ServiceContextResource;
import org.apache.ignite.services.ServiceCallContext;
import org.apache.ignite.services.ServiceContext;

public class EchoServiceImpl implements EchoService {
/** Serial version UID. */
private static final long serialVersionUID = 0L;

//tag::service-call-context-read[]
@ServiceContextResource
private ServiceContext ctx;

/** {@inheritDoc} */
@Override public String hello() {
ServiceCallContext callCtx = ctx.currentCallContext();

String proxyUser = callCtx != null ? callCtx.attribute("user") : null;

return String.format("Hello %s!", (proxyUser == null || proxyUser.isEmpty() ? "anonymous" : proxyUser));
}
//end::service-call-context-read[]
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import org.apache.ignite.configuration.IgniteConfiguration;
import org.apache.ignite.lang.IgnitePredicate;
import org.apache.ignite.services.ServiceConfiguration;
import org.junit.jupiter.api.Test;
import org.junit.Test;

public class ServiceExample {

Expand Down Expand Up @@ -173,7 +173,7 @@ void startWithStatistics() {
ignite.services().deploy(serviceCfg);

// NOTE: work via proxy. Direct references like 'IgniteServices#service()' corrupt the statistics.
MyCounterService svc = ignite.services().serviceProxy("myService", MyCounterService.class, true)
MyCounterService svc = ignite.services().serviceProxy("myService", MyCounterService.class, true);
//end::start-with-statistics[]

ignite.close();
Expand All @@ -197,4 +197,27 @@ void serviceConfiguration() {
//end::service-configuration[]
ignite.close();
}

@Test
public void serviceCallContextExample() {
//tag::service-call-context-create[]
try (Ignite ignite = Ignition.start()) {
ignite.services().deployClusterSingleton("EchoService", new EchoServiceImpl());

// Create context.
ServiceCallContext johnCtx = ServiceCallContext.builder().put("user", "John").build();
ServiceCallContext maryCtx = ServiceCallContext.builder().put("user", "Mary").build();

// Bind it to the service proxy.
EchoService johnProxy = ignite.services().serviceProxy("EchoService", EchoService.class, false, johnCtx);
EchoService maryProxy = ignite.services().serviceProxy("EchoService", EchoService.class, false, maryCtx);

// Prints "Hello John!".
System.out.println(johnProxy.hello());

// Prints "Hello Mary!".
System.out.println(maryProxy.hello());
}
//end::service-call-context-create[]
}
}
Binary file added docs/_docs/images/service-intercept--order.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
75 changes: 75 additions & 0 deletions docs/_docs/services/services.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
= Services

:javaFile: {javaCodeDir}/services/ServiceExample.java
:javaEchoServiceFile: {javaCodeDir}/services/EchoServiceImpl.java
:javaServiceInterceptorFile: {javaCodeDir}/services/EchoServiceAccessInterceptor.java


== Overview
A service is a piece of functionality that can be deployed to an Ignite cluster and execute specific operations.
Expand Down Expand Up @@ -291,6 +294,78 @@ instead (`IgniteServices.serviceProxy(...)`).
an issue for real jobs like working with a DB or a cache.
====

== Service Middleware

WARNING: This feature is experimental and may change in future releases.

[CAUTION]
====
This feature may affect performance of service execution.
====

Apache Ignite allows users to implement custom middleware logic for their services and provides the following features:

1. Ability to implicitly pass custom immutable parameters from proxy to service (similar to request headers).
2. Ability to define custom interceptor for service method calls.

=== Service Call Context
This feature allows users to implicilty pass parameters to any service method without service re-deployment.

The user can create context and bind it to the service proxy. After that, each call to the proxy method will also implicitly pass context parameters to the service.

[tabs]
--
tab:Java[]
[source, java]
----
include::{javaFile}[tags=service-call-context-create, indent=0]
----
tab:C#/.NET[]
--

The user can read the `call context` using the `currentCallContext` method of `ServiceContext`.

NOTE: Service call context is only accessible from the current thread during the execution of a service method.

[tabs]
--
tab:Java[]
[source, java]
----
include::{javaEchoServiceFile}[tags=service-call-context-read, indent=0]
----
tab:C#/.NET[]
--

=== Service Interceptor
This feature allows users to intercept the call to any service method except
lifecycle methods (`init()`, `execute()` and `cancel()`).

The user can specify one or more interceptors in the `ServiceConfiguration`. They are deployed with the service using the same class loader.

Each interceptor invokes the next interceptor in the chain using a delegated call, the last interceptor calls the service method.
So, the interceptor specified first in the configuration is the last interceptor processing the result of the service method execution.

image::images/service-intercept--order.png[invocation order]

[CAUTION]
====
An incorrect interceptor implementation can lead to undefined behaviour of the service execution.
====

[tabs]
--
tab:Java[]
[source, java]
----
include::{javaServiceInterceptorFile}[tags=service-call-interceptor, indent=0]
----
tab:C#/.NET[]
--

=== Complete Example
link:{githubUrl}/examples/src/main/java/org/apache/ignite/examples/servicegrid/ServiceMiddlewareExample.java[ServiceMiddlewareExample.java, window=_blank]

// TODO: add how to call java services from .NET


Loading