All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.avanza.astrix.beans.api.ApiProviderBeanPublisherPlugin Maven / Gradle / Ivy

/*
 * 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.api;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

import javax.annotation.PreDestroy;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.avanza.astrix.beans.config.AstrixConfig;
import com.avanza.astrix.beans.core.AstrixConfigAware;
import com.avanza.astrix.beans.core.ReactiveTypeConverter;
import com.avanza.astrix.beans.factory.StandardFactoryBean;
import com.avanza.astrix.beans.ft.BeanFaultToleranceFactory;
import com.avanza.astrix.beans.publish.ApiProviderClass;
import com.avanza.astrix.beans.publish.BeanDefinitionMethod;
import com.avanza.astrix.beans.publish.BeanPublisherPlugin;
import com.avanza.astrix.beans.publish.LibraryBeanDefinition;
import com.avanza.astrix.beans.publish.ServiceBeanDefinition;
import com.avanza.astrix.beans.service.ServiceDefinition;
import com.avanza.astrix.beans.service.ServiceDefinitionSource;
import com.avanza.astrix.beans.service.ServiceDiscoveryDefinition;
import com.avanza.astrix.core.util.ReflectionUtil;
import com.avanza.astrix.modules.ObjectCache;
import com.avanza.astrix.provider.core.AstrixApiProvider;
import com.avanza.astrix.provider.core.Service;
import com.avanza.astrix.versioning.core.AstrixObjectSerializerConfig;
import com.avanza.astrix.versioning.core.ObjectSerializerDefinition;
import com.avanza.astrix.versioning.core.Versioned;

import rx.Observable;

/**
 * 
 * @author Elias Lindholm
 *
 */
final class ApiProviderBeanPublisherPlugin implements BeanPublisherPlugin {

	private static final Logger log = LoggerFactory.getLogger(ApiProviderBeanPublisherPlugin.class);
	
	private final ObjectCache provideInstanceCache = new ObjectCache();
	private final BeanFaultToleranceFactory faultToleranceFactory;
	private final AstrixConfig config;
	private final ReactiveTypeConverter reactiveTypeConverter;
	
	public ApiProviderBeanPublisherPlugin(
			BeanFaultToleranceFactory faultToleranceFactory,
			AstrixConfig config,
			ReactiveTypeConverter reactiveTypeConverter) {
		this.faultToleranceFactory = faultToleranceFactory;
		this.config = config;
		this.reactiveTypeConverter = reactiveTypeConverter;
	}
	
	@Override
	public void publishBeans(BeanPublisher publisher, ApiProviderClass apiProviderClass) {
		// Create factory for each exported bean in api.
		for (Method astrixBeanDefinitionMethod : apiProviderClass.getProviderClass().getMethods()) {
			BeanDefinitionMethod beanDefinitionMethod = BeanDefinitionMethod.create(astrixBeanDefinitionMethod);
			if (beanDefinitionMethod.isLibrary()) {
				StandardFactoryBean factory = createLibraryFactory(apiProviderClass, astrixBeanDefinitionMethod, beanDefinitionMethod);
				publisher.publishLibrary(new LibraryBeanDefinition<>(factory, beanDefinitionMethod.getDefaultBeanSettings()));
				continue;
			}
			if (beanDefinitionMethod.isService()) {
				ServiceDefinition serviceDefinition = createServiceDefinition(apiProviderClass, beanDefinitionMethod);
				ServiceDiscoveryDefinition serviceDiscoveryDefinition = new ServiceDiscoveryDefinition(beanDefinitionMethod.getServiceDiscoveryProperties(), serviceDefinition.getBeanKey());
				publisher.publishService(new ServiceBeanDefinition<>(beanDefinitionMethod.getDefaultBeanSettings(), serviceDefinition, serviceDiscoveryDefinition));
				Class reactiveInterface = loadInterfaceIfExists(beanDefinitionMethod.getBeanType().getName() + "Async");
				if (reactiveInterface != null && isValidReactiveInterface(beanDefinitionMethod.getBeanType(), reactiveInterface)) {
					ServiceDefinition asyncDefinition = serviceDefinition.asyncDefinition(reactiveInterface);
					publisher.publishService(new ServiceBeanDefinition<>(beanDefinitionMethod.getDefaultBeanSettings(), 
							asyncDefinition, 
							serviceDiscoveryDefinition)); // Use same discovery as for sync version
				}
				continue;
			}
		}
	}
	
	private boolean isValidReactiveInterface(Class targetServiceApi, Class reactiveInterface) {
		for (Method reactiveMethod : reactiveInterface.getMethods()) {
			if (!reactiveMethod.getReturnType().equals(Observable.class) && !this.reactiveTypeConverter.isReactiveType(reactiveMethod.getReturnType())) {
				log.warn("Found reactive interface that contains non-reactive methods and will therefore not be registered in the BeanFactory. nonReactiveReturnType={}, reactiveInterfaceCandidate={}, invalidMethod={}"
						 , reactiveMethod.getReturnType().getName(), reactiveInterface.getName(), reactiveMethod.getName());
				return false;
			}
			if (!hasMethod(targetServiceApi, reactiveMethod.getName(), reactiveMethod.getParameterTypes())) {
				log.warn("Found reactive interface that contains methods that does not correpond to a method in the synchronous interface. reactiveInterfaceCandidate={}, invalidMethod={}"
						, reactiveInterface.getName(), reactiveMethod.getName());
				return false;
			}
		}
		return true;
	}

	private boolean hasMethod(Class targetServiceApi, String name, Class[] parameterTypes) {
		try {
			targetServiceApi.getMethod(name, parameterTypes);
			return true;
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
			return false;
		}
	}

	private Class loadInterfaceIfExists(String interfaceName) {
		try {
			Class c = Class.forName(interfaceName);
			if (c.isInterface()) {
				return c;
			}
		} catch (ClassNotFoundException e) {
			// fall through and return null
		}
		return null;
	}

	private  StandardFactoryBean createLibraryFactory(
			ApiProviderClass apiProviderClass,
			Method astrixBeanDefinitionMethod,
			BeanDefinitionMethod beanDefinition) {
		Object libraryProviderInstance = getApiProviderInstance(apiProviderClass.getProviderClass());
		StandardFactoryBean libraryFactory = new AstrixLibraryFactory<>(libraryProviderInstance, astrixBeanDefinitionMethod, beanDefinition.getQualifier());
		if (beanDefinition.applyFtProxy()) {
			libraryFactory = new AstrixFtProxiedFactory(libraryFactory, faultToleranceFactory, beanDefinition, reactiveTypeConverter);
		}
		return libraryFactory;
	}
	
	private ServiceDefinition createServiceDefinition(ApiProviderClass apiProviderClass, BeanDefinitionMethod serviceDefinitionMethod) {
		Class declaringApi = getDeclaringApi(apiProviderClass, serviceDefinitionMethod.getBeanType());
		if (!(declaringApi.isAnnotationPresent(Versioned.class) || serviceDefinitionMethod.isVersioned())) {
			return ServiceDefinition.create(ServiceDefinitionSource.create(serviceDefinitionMethod.getDefiningApi().getName()), serviceDefinitionMethod.getBeanKey(), 
								serviceDefinitionMethod.getServiceConfigClass(), 
								ObjectSerializerDefinition.nonVersioned(), 
								serviceDefinitionMethod.isDynamicQualified());
		}
		if (!apiProviderClass.isAnnotationPresent(AstrixObjectSerializerConfig.class)) {
			throw new IllegalArgumentException("Illegal api-provider. Api is versioned but provider does not declare a @AstrixObjectSerializerConfig." +
					" providedService=" + serviceDefinitionMethod.getBeanType().getName() + ", provider=" + apiProviderClass.getProviderClassName());
		} 
		AstrixObjectSerializerConfig serializerConfig = apiProviderClass.getAnnotation(AstrixObjectSerializerConfig.class);
		return ServiceDefinition.create(ServiceDefinitionSource.create(serviceDefinitionMethod.getDefiningApi().getName()),
										serviceDefinitionMethod.getBeanKey(), 
										serviceDefinitionMethod.getServiceConfigClass(), 
									    ObjectSerializerDefinition.versionedService(serializerConfig.version(), 
									    serializerConfig.objectSerializerConfigurer()), 
									    serviceDefinitionMethod.isDynamicQualified());
	}

	private Class getDeclaringApi(ApiProviderClass apiProviderClass, Class providedService) {
		for (Method m : apiProviderClass.getProviderClass().getMethods()) {
			if (m.isAnnotationPresent(Service.class) && m.getReturnType().equals(providedService)) {
				return apiProviderClass.getProviderClass();
			}
		}
		throw new IllegalArgumentException(String.format("ApiProvider does not provide service. providerClass=%s service=%s", apiProviderClass, providedService.getName()));
	}

	private Object getApiProviderInstance(final Class provider) {
		return provideInstanceCache.getInstance(provider, new ObjectCache.ObjectFactory() {
			@Override
			public Object create() throws Exception {
				Object instance = ReflectionUtil.newInstance(provider);
				if (instance instanceof AstrixConfigAware) {
					AstrixConfigAware.class.cast(instance).setConfig(config.getConfig());
				}
				return instance;
			}
		});
	}

	@Override
	public Class getProviderAnnotationType() {
		return AstrixApiProvider.class;
	}
	
	@PreDestroy
	public void destroy() {
		this.provideInstanceCache.destroy();
	}
	
}