/* * 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.remoting.server; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; import java.util.stream.IntStream; import org.junit.Assert; import com.avanza.astrix.beans.core.ReactiveTypeConverter; import com.avanza.astrix.beans.core.ReactiveTypeConverterImpl; import com.avanza.astrix.beans.core.ReactiveTypeHandlerPlugin; import com.avanza.astrix.config.DynamicBooleanProperty; import com.avanza.astrix.context.JavaSerializationSerializer; import com.avanza.astrix.context.mbeans.MBeanExporter; import com.avanza.astrix.context.metrics.Metrics; import com.avanza.astrix.context.metrics.Timer; import com.avanza.astrix.context.metrics.TimerSnaphot; import com.avanza.astrix.context.metrics.TimerSpi; import com.avanza.astrix.core.AstrixBroadcast; import com.avanza.astrix.core.function.CheckedCommand; import com.avanza.astrix.core.remoting.Router; import com.avanza.astrix.core.remoting.RoutingKey; import com.avanza.astrix.core.remoting.RoutingStrategy; import com.avanza.astrix.remoting.client.AstrixServiceInvocationRequest; import com.avanza.astrix.remoting.client.AstrixServiceInvocationResponse; import com.avanza.astrix.remoting.client.RemotingProxy; import com.avanza.astrix.remoting.client.RemotingTransport; import com.avanza.astrix.remoting.client.RemotingTransportSpi; import com.avanza.astrix.remoting.client.RoutedServiceInvocationRequest; import com.avanza.astrix.versioning.core.AstrixObjectSerializer; import rx.Observable; import rx.Subscriber; public class AstrixRemotingDriver { private AstrixObjectSerializer objectSerializer = new JavaSerializationSerializer(1); private final ConcurrentMap<MBeanKey, Object> mbeanByKey = new ConcurrentHashMap<>(); private final Metrics metrics = new Metrics() { @Override public Timer createTimer() { return new Timer(new FakeTimer()); } }; private MBeanExporter exporter = new MBeanExporter() { @Override public void registerMBean(Object mbean, String folder, String name) { mbeanByKey.putIfAbsent(new MBeanKey(folder, name), mbean); } }; private ReactiveTypeConverter reactiveTypeConverter = new ReactiveTypeConverterImpl(Collections.<ReactiveTypeHandlerPlugin<?>>emptyList()); private DynamicBooleanProperty exportedServiceMetricsEnabled = new DynamicBooleanProperty(true); private AstrixServiceActivatorImpl[] partitions; public AstrixRemotingDriver() { this(1); } public AstrixRemotingDriver(int partitionCount) { this.partitions = new AstrixServiceActivatorImpl[partitionCount]; IntStream.range(0, partitionCount).forEach(index -> partitions[index] = new AstrixServiceActivatorImpl(exportedServiceMetricsEnabled, metrics, exporter)); } public <T> T hasExportedMbeanOfType(Class<T> expectedType, MBeanKey key) { Object mbean = this.mbeanByKey.get(key); Assert.assertNotNull("Expected an exported mbean with key: " + key, mbean); Assert.assertTrue("Mbean type, expected: " + expectedType + ", got: " + mbean.getClass(), expectedType.isAssignableFrom(mbean.getClass())); return expectedType.cast(mbean); } public <T> T createRemotingProxy(Class<T> proxyAndTargetApi) { return createRemotingProxy(proxyAndTargetApi, proxyAndTargetApi); } public <T> T createRemotingProxy(Class<T> proxyApi, Class<?> targetApi) { return RemotingProxy.create(proxyApi, targetApi, directTransport(), objectSerializer, new NoRoutingStrategy(), reactiveTypeConverter); } public <T> T createRemotingProxy(Class<T> proxyApi, Class<?> targetApi, AstrixObjectSerializer objectSerializerOverride) { return RemotingProxy.create(proxyApi, targetApi, directTransport(), objectSerializerOverride, new NoRoutingStrategy(), reactiveTypeConverter); } public <T> T createRemotingProxy(Class<T> proxyApi, Class<?> targetApi, RoutingStrategy routingStrategyOverride) { return RemotingProxy.create(proxyApi, targetApi, directTransport(), objectSerializer, routingStrategyOverride, reactiveTypeConverter); } /** * Registers a service in the first partition */ public <T> void registerServer(Class<T> publishedApi, Object provider) { this.partitions[0].register(provider, objectSerializer, publishedApi); } public <T> void registerServerPartition(int paritionIndex, Class<T> publishedApi, T provider) { this.partitions[paritionIndex].register(provider, objectSerializer, publishedApi); } private RemotingTransport directTransport() { return RemotingTransport.create(new PartitionedDirectTransport(Arrays.asList(this.partitions))); } private static class PartitionedDirectTransport implements RemotingTransportSpi { private List<AstrixServiceActivatorImpl> partitions; public PartitionedDirectTransport(List<AstrixServiceActivatorImpl> partitions) { this.partitions = partitions; } @Override public Observable<AstrixServiceInvocationResponse> submitRoutedRequest(AstrixServiceInvocationRequest request, RoutingKey routingKey){ final AstrixServiceInvocationResponse response = getActivator(routingKey).invokeService(request); return Observable.create(new Observable.OnSubscribe<AstrixServiceInvocationResponse>() { @Override public void call(Subscriber<? super AstrixServiceInvocationResponse> t1) { t1.onNext(response); t1.onCompleted(); } }); } private AstrixServiceActivatorImpl getActivator(RoutingKey routingKey) { return partitions.get(routingKey.hashCode() % partitions.size()); } @Override public Observable<List<AstrixServiceInvocationResponse>> submitBroadcastRequest(AstrixServiceInvocationRequest request) { final List<AstrixServiceInvocationResponse> responses = new ArrayList<>(); for (AstrixServiceActivatorImpl partition : partitions) { responses.add(partition.invokeService(request)); } return Observable.create(new Observable.OnSubscribe<AstrixServiceInvocationResponse>() { @Override public void call(Subscriber<? super AstrixServiceInvocationResponse> t1) { for (AstrixServiceInvocationResponse r : responses) { t1.onNext(r); } t1.onCompleted(); } }).toList(); } @Override public int partitionCount() { return this.partitions.size(); } @Override public Observable<List<AstrixServiceInvocationResponse>> submitRoutedRequests(Collection<RoutedServiceInvocationRequest> requests) { Observable<AstrixServiceInvocationResponse> result = Observable.empty(); for (RoutedServiceInvocationRequest request : requests) { result = result.mergeWith(submitRoutedRequest(request.getRequest(), request.getRoutingkey())); } return result.toList(); } } private static class NoRoutingStrategy implements RoutingStrategy { @Override public Router create(Method serviceMethod) { if (serviceMethod.isAnnotationPresent(AstrixBroadcast.class)) { return new Router() { @Override public RoutingKey getRoutingKey(Object[] args) throws Exception { return null; // Broadcast } }; } return new Router() { @Override public RoutingKey getRoutingKey(Object[] args) throws Exception { return RoutingKey.create(1); // Constant routing } }; } } private static class FakeTimer implements TimerSpi { private AtomicInteger invocationCount = new AtomicInteger(0); @Override public <T> CheckedCommand<T> timeExecution(CheckedCommand<T> execution) { invocationCount.incrementAndGet(); return execution; } @Override public <T> Supplier<Observable<T>> timeObservable(Supplier<Observable<T>> observableFactory) { return () -> { invocationCount.incrementAndGet(); return observableFactory.get(); }; } @Override public TimerSnaphot getSnapshot() { return new TimerSnaphot.Builder().count(invocationCount.get()).build(); } } public void setExportedServiceMetricsEnabled(boolean enabled) { this.exportedServiceMetricsEnabled.set(enabled); } }