/*
* 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 java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicLong;
import com.avanza.astrix.beans.core.AstrixBeanKey;
import com.avanza.astrix.beans.core.ReactiveTypeConverter;
import com.avanza.astrix.core.util.ReflectionUtil;
import com.avanza.astrix.provider.component.AstrixServiceComponentNames;
import com.avanza.astrix.versioning.core.AstrixObjectSerializer;
import com.avanza.astrix.versioning.core.ObjectSerializerDefinition;
import com.avanza.astrix.versioning.core.ObjectSerializerFactory;
import rx.Observable;
/**
*
* @author Elias Lindholm (elilin)
*
*/
public class DirectComponent implements ServiceComponent {
private final static AtomicLong idGen = new AtomicLong();
private final static Map<String, ServiceProvider<?>> providerById = new ConcurrentHashMap<>();
private final ObjectSerializerFactory objectSerializerFactory;
private final List<DirectBoundServiceBeanInstance<?>> nonReleasedInstances = new ArrayList<>();
private final ConcurrentMap<AstrixBeanKey<?>, String> idByExportedBean = new ConcurrentHashMap<>();
private final ReactiveTypeConverter reactiveTypeConverter;
public DirectComponent(ObjectSerializerFactory objectSerializerFactory, ReactiveTypeConverter reactiveTypeConverter) {
this.objectSerializerFactory = objectSerializerFactory;
this.reactiveTypeConverter = reactiveTypeConverter;
}
public List<? extends BoundServiceBeanInstance<?>> getBoundServices() {
return this.nonReleasedInstances;
}
@Override
public <T> BoundServiceBeanInstance<T> bind(ServiceDefinition<T> serviceDefinition, ServiceProperties serviceProperties) {
String providerId = serviceProperties.getProperty("providerId");
ServiceProvider<?> serviceProvider = providerById.get(providerId);
if (serviceProvider == null) {
throw new IllegalStateException("No service provider is registered in the DirectComponent with the given serviceUri. id=" + providerId + ", type=" + serviceDefinition.getServiceType());
}
Object targetProvider = serviceProvider.getProvider(objectSerializerFactory, serviceDefinition.getObjectSerializerDefinition());
T provider;
if (serviceDefinition.getBeanKey().getBeanType().isAssignableFrom(targetProvider.getClass())) {
provider = serviceDefinition.getServiceType().cast(targetProvider);
} else {
// Async return type
provider = createProxy(serviceDefinition.getBeanKey().getBeanType(), targetProvider);
}
DirectBoundServiceBeanInstance<T> directServiceBeanInstance = new DirectBoundServiceBeanInstance<T>(provider);
this.nonReleasedInstances.add(directServiceBeanInstance);
return directServiceBeanInstance;
}
private <T> T createProxy(Class<T> proxyApi, final Object targetProvider) {
return ReflectionUtil.newProxy(proxyApi, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Method targetMethod = targetProvider.getClass().getMethod(method.getName(), method.getParameterTypes());
Observable<Object> observableResult = Observable.create((s) -> {
try {
Object result = ReflectionUtil.invokeMethod(targetMethod, targetProvider, args);
s.onNext(result);
s.onCompleted();
} catch (Throwable e) {
s.onError(e);
}
});
if (method.getReturnType().equals(Future.class)) {
return observableResult.toBlocking().toFuture();
}
if (method.getReturnType().equals(Observable.class)) {
return observableResult;
}
return reactiveTypeConverter.toCustomReactiveType(method.getReturnType(), observableResult);
} catch (NoSuchMethodException e) {
throw new RuntimeException("Target service does not contain method: " + e.getMessage());
}
}
});
}
@Override
public ServiceProperties parseServiceProviderUri(String serviceProviderUri) {
return getServiceProperties(serviceProviderUri);
}
@Override
public String getName() {
return AstrixServiceComponentNames.DIRECT;
}
@Override
public boolean canBindType(Class<?> type) {
return true;
}
public static <T> String register(Class<T> type, T provider) {
String id = String.valueOf(idGen.incrementAndGet());
providerById.put(id, new ServiceProvider<T>(id, type, provider));
return id;
}
public static <T> String register(Class<T> type, T provider, String id) {
providerById.put(id, new ServiceProvider<T>(id, type, provider));
return id;
}
public static <T> void unregister(String id) {
providerById.remove(id);
}
private static <T> String register(Class<T> type, T provider, ObjectSerializerDefinition serverSerializerDefinition) {
String id = String.valueOf(idGen.incrementAndGet());
providerById.put(id, new VersioningAwareServiceProvider<>(id, type, provider, serverSerializerDefinition));
return id;
}
public static <T> ServiceProperties registerAndGetProperties(Class<T> type, T provider) {
String id = register(type, provider);
return getServiceProperties(id);
}
public static ServiceProperties getServiceProperties(String id) {
ServiceProperties serviceProperties = new ServiceProperties();
serviceProperties.setProperty("providerId", id);
serviceProperties.setComponent(AstrixServiceComponentNames.DIRECT);
ServiceProvider<?> serviceProvider = providerById.get(id);
if (serviceProvider == null) {
return serviceProperties; // TODO: Throw exception when no service-provider found. Requires rewrite of two unit-tests.
}
serviceProperties.setApi(serviceProvider.getType());
serviceProperties.setProperty(ServiceProperties.PUBLISHED, "true");
return serviceProperties;
}
public static String getServiceUri(String id) {
ServiceProvider<?> provider = providerById.get(id);
if (provider == null) {
throw new IllegalArgumentException("No provider registered with id: " + id);
}
return AstrixServiceComponentNames.DIRECT + ":" + id;
}
static class ServiceProvider<T> {
private String directId;
private Class<T> type;
private T provider;
public ServiceProvider(String directId, Class<T> type, T provider) {
this.directId = directId;
this.type = type;
this.provider = provider;
}
public String getId() {
return directId;
}
public Class<T> getType() {
return type;
}
public T getProvider(ObjectSerializerFactory objectSerializerFactory, ObjectSerializerDefinition clientSerializerDefinition) {
return provider;
}
}
static class VersioningAwareServiceProvider<T> extends ServiceProvider<T> {
private Class<T> type;
private T provider;
private ObjectSerializerDefinition serverSerializerDefinition;
public VersioningAwareServiceProvider(String id, Class<T> type, T provider, ObjectSerializerDefinition serverSerializerDefinition) {
super(id, type, provider);
this.type = type;
this.provider = provider;
this.serverSerializerDefinition = serverSerializerDefinition;
}
@Override
public T getProvider(ObjectSerializerFactory objectSerializerFactory, ObjectSerializerDefinition serializerDefinition) {
if (serverSerializerDefinition.isVersioned() || serializerDefinition.isVersioned()) {
VersionedServiceProviderProxy serializationHandlingProxy =
new VersionedServiceProviderProxy(provider,
serializerDefinition.version(),
objectSerializerFactory.create(serializerDefinition),
objectSerializerFactory.create(serverSerializerDefinition));
return ReflectionUtil.newProxy(type, serializationHandlingProxy);
}
return provider;
}
}
static class VersionedServiceProviderProxy implements InvocationHandler {
private Object provider;
private AstrixObjectSerializer serverSerializer;
private AstrixObjectSerializer clientSerializer;
private int clientVersion;
public VersionedServiceProviderProxy(Object provider, int clientVersion, AstrixObjectSerializer clientSerializer, AstrixObjectSerializer serverSerializer) {
this.serverSerializer = serverSerializer;
this.clientVersion = clientVersion;
this.provider = provider;
this.clientSerializer = clientSerializer;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object[] marshalledAndUnmarshalledArgs = new Object[args.length];
for (int i = 0; i < args.length; i++) {
// simulate client serialization before sending request over network
Object serialized = clientSerializer.serialize(args[i], clientVersion);
// simulate server deserialization after receiving request from network
Object deserialized = serverSerializer.deserialize(serialized, method.getParameterTypes()[i], clientVersion);
if (args[i] != null && !args[i].getClass().equals(deserialized.getClass())) {
throw new IllegalArgumentException("Deserialization of service arguments failed. clientSerializer="
+ clientSerializer.getClass().getName() + " serverSerializer=" + serverSerializer.getClass().getName());
}
marshalledAndUnmarshalledArgs[i] = deserialized;
}
Object result = ReflectionUtil.invokeMethod(method, provider, marshalledAndUnmarshalledArgs);
// simulate server serialization before sending response over network
Object serialized = serverSerializer.serialize(result, clientVersion);
// simulate client deserialization after receiving response from server.
Object deserialized = clientSerializer.deserialize(serialized, method.getReturnType(), clientVersion);
return deserialized;
}
}
public Collection<ServiceProvider<?>> listProviders() {
return providerById.values();
}
public void clear(String id) {
providerById.remove(id);
}
@Override
public <T> void exportService(Class<T> providedApi, T provider, ServiceDefinition<T> serviceDefinition) {
String id = register(providedApi, provider);
this.idByExportedBean.put(serviceDefinition.getBeanKey(), id);
}
@Override
public boolean requiresProviderInstance() {
return true;
}
@Override
public <T> ServiceProperties createServiceProperties(ServiceDefinition<T> exportedService) {
String id = this.idByExportedBean.get(exportedService.getBeanKey());
return getServiceProperties(id);
}
public static <T> String registerAndGetUri(Class<T> api, T provider) {
String id = register(api, provider);
return getServiceUri(id);
}
/**
* Registers a a provider for a given api and associates it with the given ServiceDefinition. <p>
*
* Using this method activates serialization/deserialization of service argument/return types. <p>
*
* Example:
* pingService.ping("my-arg")
*
*
* 1. Using the ServiceDefinition provided by the api (using @AstrixVersioned) the service arguments will
* be serialized.
* 2. All arguments will then be deserialized using the serverServiceDefinition passed as to this method during registration of the provider
* 3. The service is invoked with the arguments returned from step 2.
* 4. The return type will be serialized using the serverServiceDefinition
* 5. The return type will be deserialized using the ServiceDefinition provided by the api.
* 6. The value is returned.
*
* @param api
* @param provider
* @param serverSerializerDefinition
* @return
*/
public static <T> String registerAndGetUri(Class<T> api, T provider, ObjectSerializerDefinition serverSerializerDefinition) {
String id = register(api, provider, serverSerializerDefinition);
return getServiceUri(id);
}
private class DirectBoundServiceBeanInstance<T> implements BoundServiceBeanInstance<T> {
private final T instance;
public DirectBoundServiceBeanInstance(T instance) {
this.instance = instance;
}
@Override
public T get() {
return instance;
}
@Override
public void release() {
DirectComponent.this.nonReleasedInstances.remove(this);
}
}
}