package rocks.inspectit.server.spring.exporter; import java.lang.annotation.Annotation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.config.TypedStringValue; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; import rocks.inspectit.shared.all.cmr.service.ServiceExporterType; import rocks.inspectit.shared.all.cmr.service.ServiceInterface; /** * Provides automatic remote export of services found inside a Spring context. * * <p> * When a bean is annotated with {@link Service} <em>and</em> implements an interface annotated with * {@link ServiceInterface}, the bean will be exported as a service with this interface. This is * done <em>after</em> the Spring context is refreshed or started so that we are sure that any * {@link BeanPostProcessor}s and AOP advise is applied. * * <p> * This exporter bean is registered in the spring context so that all lifecycles are passed and this * bean can be used as a reference candidate for other spring beans. The name is either being * specified statically in the annotation or dynamically via the name of the service bean + * "Exporter" at the end. * * <p> * * <b>IMPORTANT:</b> The class code is copied/taken/based from * <a href="http://jira.springframework.org/browse/SPR-3926">Spring JIRA (SPR-3926)</a>. Original * authors are James Douglas and Henno Vermeulen. * * @author James Douglas * @author Henno Vermeulen * @author Patrice Bouillet */ @Component public class RemotingExporter implements BeanFactoryPostProcessor { /** * The logger for this class. Cannot be injected via Spring. */ private static final Logger LOG = LoggerFactory.getLogger(RemotingExporter.class); /** * The annotation defining a class being a service. */ private Class<? extends Annotation> serviceAnnotationType = Service.class; /** * The annotation defining an interface being a service interface. */ private Class<? extends Annotation> serviceInterfaceAnnotationType = ServiceInterface.class; /** * {@inheritDoc} * * We look for the service annotation on the bean class found in the {@link BeanDefinition} * before any {@link BeanPostProcessor}s or AOP advice is applied. */ @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { LOG.info("|-RemoteExporter: Processing Beans for remote export"); for (String beanName : beanFactory.getBeanDefinitionNames()) { BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName); if (beanDef.isAbstract()) { if (LOG.isDebugEnabled()) { LOG.debug("|- RemoteExporter: Skipping abstract Bean '" + beanName + "'"); } continue; } if (null == beanDef.getBeanClassName()) { if (LOG.isDebugEnabled()) { LOG.debug("|- RemoteExporter: Skipping Bean '" + beanName + "' which has no bean class defined"); } continue; } Class<?> serviceInterface = null; Class<?> serviceClass; String beanClassName = beanDef.getBeanClassName(); if (null == beanClassName) { // bean class name is null for beans defined via @Configuration continue; } try { serviceClass = Class.forName(beanClassName); serviceInterface = findServiceInterface(serviceClass); } catch (ClassNotFoundException e) { throw new BeanCreationException("class of bean " + beanName + " not found", e); } if (serviceInterface != null) { if (LOG.isDebugEnabled()) { LOG.debug("|- RemoteExporter: Found service Bean with name '" + beanName + "' and interface " + serviceInterface); } // creating the bean definition so that the bean is registered in the spring // container Annotation annotation = AnnotationUtils.findAnnotation(serviceClass, serviceInterfaceAnnotationType); RootBeanDefinition definition; MutablePropertyValues values = new MutablePropertyValues(); values.add("service", new RuntimeBeanReference(beanName)); values.add("serviceInterface", new TypedStringValue(serviceInterface.getName())); ServiceExporterType type = (ServiceExporterType) AnnotationUtils.getValue(annotation, "exporter"); switch (type) { case RMI: definition = new RootBeanDefinition(KryoNetRmiServiceExporter.class); if (annotationPropertySet(annotation, "serviceId")) { int serviceId = (int) AnnotationUtils.getValue(annotation, "serviceId"); values.add("serviceId", serviceId); } break; case HTTP: definition = new RootBeanDefinition(KryoHttpInvokerServiceExporter.class); break; default: throw new BeanCreationException("Could not create service exporter bean because exporter type is not handled: " + type); } definition.setPropertyValues(values); definition.setAutowireCandidate(true); BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; registry.registerBeanDefinition(getNameForExporterBean(beanName, annotation), definition); if (LOG.isDebugEnabled()) { LOG.debug("|- RemoteExporter: Registered new Bean: " + beanName + "Exporter"); } } else { if (LOG.isDebugEnabled()) { LOG.debug("|- RemoteExporter: Skipping Bean because no remotable interface was found '" + beanName + "'"); } } } } /** * Checks for if an annotation property is set by overriding the default value. * * @param annotation * the annotation to check for. * @param attributeName * the name of the attribute to compare the default and current value. * @return if the annotation property has been set. */ private boolean annotationPropertySet(Annotation annotation, String attributeName) { Object defaultValue = AnnotationUtils.getDefaultValue(annotation, attributeName); Object currentValue = AnnotationUtils.getValue(annotation, attributeName); return !currentValue.equals(defaultValue); } /** * Creates the name of the exporter bean under which it will be exposed in the spring container. * * @param beanName * the original service bean name. * @param annotation * the annotation reference used to extract the name definition - if any. * @return the name of the exporter bean. */ private String getNameForExporterBean(String beanName, Annotation annotation) { String name = (String) AnnotationUtils.getValue(annotation, "name"); if ((null != name) && !"".equals(name.trim())) { return name; } else { return beanName + "Exporter"; } } /** * Searches for a service interface on the passed class. * * @param serviceClass * the class to look for a service interface. * @return returns the service interface. */ private Class<?> findServiceInterface(Class<?> serviceClass) { Class<?> serviceInterface = null; if (AnnotationUtils.isAnnotationDeclaredLocally(serviceAnnotationType, serviceClass)) { for (Class<?> interfaceClass : serviceClass.getInterfaces()) { if (AnnotationUtils.isAnnotationDeclaredLocally(serviceInterfaceAnnotationType, interfaceClass)) { serviceInterface = interfaceClass; } } } return serviceInterface; } }