/*
* Copyright 2014 Avanza Bank AB
*
* Licensed 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 com.avanza.astrix.beans.service;
import static org.junit.Assert.assertEquals;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.avanza.astrix.beans.factory.MissingBeanProviderException;
import com.avanza.astrix.context.AstrixContext;
import com.avanza.astrix.context.TestAstrixConfigurer;
import com.avanza.astrix.provider.core.AstrixApiProvider;
import com.avanza.astrix.provider.core.AstrixConfigDiscovery;
import com.avanza.astrix.provider.core.Service;
public class AsyncServiceTest {
private PingAsync ping;
private AstrixContext context;
private BlockingPing server = new BlockingPing();
@Before
public void setup() {
SingleServiceComponent singleService = new SingleServiceComponent(PingAsync.class, server);
TestAstrixConfigurer astrixConfigurer = new TestAstrixConfigurer();
astrixConfigurer.enableFaultTolerance(true);
astrixConfigurer.registerApiProvider(PingApi.class);
astrixConfigurer.registerPlugin(ServiceComponent.class, singleService);
astrixConfigurer.set("pingUri", singleService.getName() + ":");
context = astrixConfigurer.configure();
ping = context.getBean(PingAsync.class);
}
@After
public void after() {
context.destroy();
}
@Test
public void asyncServiceInvocationShouldRunAsynchronouslyWithMethodCalll() throws Exception {
Future<String> response = ping.ping("foo");
server.setResponse("bar");
assertEquals("bar", response.get());
}
@Test
public void asyncServiceInvocationShouldStartSynchronouslyWithMethodCalll() throws Exception {
PingAsync ping = context.getBean(PingAsync.class);
@SuppressWarnings("unused")
Future<String> response = ping.ping("foo");
assertEquals("Service invocation should be started synchronously with method call. Last server invocation: ",
"foo", server.pingRequests.poll(1, TimeUnit.SECONDS));
}
@Test(expected = MissingBeanProviderException.class)
public void validatesAllThatAllMethodsInReactiveTypeAreReactive() throws Exception {
AstrixContext context = new TestAstrixConfigurer().registerApiProvider(BrokenPingApi.class)
.configure();
context.getBean(BrokenPingAsync.class);
}
@Test(expected = MissingBeanProviderException.class)
public void validatesAllThatAllMethodsInReactiveTypeCorrespondToSyncVersion() throws Exception {
AstrixContext context = new TestAstrixConfigurer().registerApiProvider(InconsistentPingApi.class)
.configure();
context.getBean(InconsistentPingAsync.class);
}
public static final class BlockingPing implements PingAsync {
private final BlockingQueue<String> pingResponses = new LinkedBlockingQueue<>();
private final BlockingQueue<String> pingRequests = new LinkedBlockingQueue<>();
@Override
public CompletableFuture<String> ping(String msg) {
pingRequests.add(msg);
CompletableFuture<String> result = new CompletableFuture<String>();
new Thread(() -> {
try {
String response = pingResponses.poll(1, TimeUnit.SECONDS);
if (response != null) {
result.complete(response);
} else {
result.completeExceptionally(new IllegalStateException("TIMEOUT"));
}
} catch (InterruptedException e) {
result.completeExceptionally(new IllegalStateException("TIMEOUT"));
}
}).start();
return result;
}
public void setResponse(String response) {
this.pingResponses.add(response);
}
}
public interface Ping {
String ping(String msg);
}
public interface PingAsync {
CompletableFuture<String> ping(String msg);
}
public interface BrokenPing {
String invalidPing(String msg);
String validPing(String msg);
}
public interface BrokenPingAsync {
Future<String> invalidPing(String msg); // Future is not a reactive type
CompletableFuture<String> validPing(String msg);
}
public interface InconsistentPing {
String ping(String msg);
}
public interface InconsistentPingAsync {
CompletableFuture<String> inconsistendPingMethod(String msg);
}
@AstrixApiProvider
public static interface PingApi {
@AstrixConfigDiscovery("pingUri")
@Service
Ping ping();
}
@AstrixApiProvider
public static interface BrokenPingApi {
@AstrixConfigDiscovery("pingUri")
@Service
BrokenPing ping();
}
@AstrixApiProvider
public static interface InconsistentPingApi {
@AstrixConfigDiscovery("pingUri")
@Service
InconsistentPing ping();
}
private static class SingleServiceComponent implements ServiceComponent {
private Class<?> api;
private Object instance;
public SingleServiceComponent(Class<?> api, Object instance) {
this.api = api;
this.instance = instance;
}
@SuppressWarnings("unchecked")
@Override
public <T> BoundServiceBeanInstance<T> bind(ServiceDefinition<T> serviceDefinition, ServiceProperties serviceProperties) {
return new SimpleBoundServiceBeanInstance<T>((T) instance);
}
@Override
public ServiceProperties parseServiceProviderUri(String serviceProviderUri) {
return new ServiceProperties();
}
@Override
public <T> ServiceProperties createServiceProperties(ServiceDefinition<T> exportedServiceDefinition) {
return new ServiceProperties();
}
@Override
public String getName() {
return "single-service";
}
@Override
public boolean canBindType(Class<?> type) {
return type.equals(api);
}
@Override
public <T> void exportService(Class<T> providedApi, T provider, ServiceDefinition<T> serviceDefinition) {
throw new UnsupportedOperationException();
}
@Override
public boolean requiresProviderInstance() {
return false;
}
}
}