package org.jboss.resteasy.plugins.spring; import org.jboss.resteasy.core.Dispatcher; import org.jboss.resteasy.plugins.spring.i18n.Messages; import org.jboss.resteasy.spi.*; import org.jboss.resteasy.util.GetRestful; import org.springframework.aop.support.AopUtils; import org.springframework.beans.BeansException; import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.PropertyValue; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.annotation.Required; import org.springframework.beans.factory.config.*; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationEvent; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.context.event.SmartApplicationListener; import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; import javax.ws.rs.ext.MessageBodyReader; import javax.ws.rs.ext.MessageBodyWriter; import javax.ws.rs.ext.Provider; import java.lang.reflect.Method; import java.util.*; /** * <p> * The processor will register any bean annotated with @Path or @Provider into * the Resteasy framework. * </p> * <p> * It also sets up Registry and ResteasyProviderFactory to be autowirable via @Autowire * in Controllers/service layers. * </p> * <p/> * <p> * There's quite a bit of spring integration functionality under the covers: * </p> * <ol> * <li>@Providers, such as RESTEasy interceptors and String converters have to * be registered in RESTEasy before resources and registers. That gets a bit * tricky, so depends-on functionality is used as well</li> * <p/> * <li> * </ol> * <p/> * * @author <a href="mailto:sduskis@gmail.com">Solomon Duskis</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @version $Revision: 1 $ */ public class SpringBeanProcessor implements BeanFactoryPostProcessor, SmartApplicationListener { protected Registry registry; protected ResteasyProviderFactory providerFactory; protected Dispatcher dispatcher; protected ResteasyDeployment deployment; protected Set<String> resourceFactoryNames; protected Map<String, SpringResourceFactory> resourceFactories = new HashMap<String, SpringResourceFactory>(); protected Set<String> providerNames = new HashSet<String>(); private Set<String> registrations = new HashSet<String>(); private int order; protected class ResteasyBeanPostProcessor implements BeanPostProcessor { private ConfigurableListableBeanFactory beanFactory; protected ResteasyBeanPostProcessor(ConfigurableListableBeanFactory beanFactory) { this.beanFactory = beanFactory; } public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } /** * This method is invoked after postProcessBeanFactory. * <p/> * this method is invoked when ever a new bean is created. This will * perform the following: * <p/> * <ol> * <p/> * <li>RESTEasy injection of singleton @Providers, as well as @Provider * registration</li> * <p/> * <li>either singleton or request/prototype RESTeasy injection... but not * registration. The RESTEasy registration happens in the * onApplicationEvent() below, which happens at the end of the Spring * life-cycle</li> * <p/> * <li>merges the {@link ResteasyDeployment} bean with the user deployment</li> * <p/> * </ol> * * @see SpringBeanProcessor.postProcessBeanFactory */ public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (providerNames.contains(beanName)) { PropertyInjector injector = getInjector(AopUtils.getTargetClass(bean)); injector.inject(bean); providerFactory.registerProviderInstance(bean); } else if(registrations.contains(beanName) && bean instanceof ResteasyRegistration) { ResteasyRegistration registration = (ResteasyRegistration)bean; String registeredBeanName = registration.getBeanName(); BeanDefinition beanDef = beanFactory.getBeanDefinition(registeredBeanName); Class<?> beanClass = getBeanClass(registeredBeanName, beanDef, beanFactory); SpringResourceFactory resourceFactory = new SpringResourceFactory(registeredBeanName, beanFactory, beanClass); resourceFactory.setContext(registration.getContext()); resourceFactories.put(registeredBeanName, resourceFactory); } else if (bean instanceof ResteasyDeployment && deployment != null) { ResteasyDeployment beanDeployment = (ResteasyDeployment) bean; deployment.merge(beanDeployment); deployment.start(); } else { SpringResourceFactory resourceFactory = resourceFactories.get(beanName); if (resourceFactory != null) { inject(beanName, bean, getInjector(resourceFactory.getScannableClass())); } } return bean; } public PropertyInjector getInjector(Class<?> clazz) { return providerFactory.getInjectorFactory().createPropertyInjector(clazz, providerFactory); } public void inject(String beanName, Object bean, PropertyInjector propertyInjector) { if (propertyInjector == null) { return; } HttpRequest request = ResteasyProviderFactory.getContextData(HttpRequest.class); if (request == null || isSingleton(beanName)) { propertyInjector.inject(bean); } else { HttpResponse response = ResteasyProviderFactory.getContextData(HttpResponse.class); propertyInjector.inject(request, response, bean); } } private boolean isSingleton(String beanName) { boolean isSingleton = false; try { BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName); isSingleton = beanDef.isSingleton(); } catch (org.springframework.beans.factory.NoSuchBeanDefinitionException nsbde) { // cannot distinguish between singleton & prototype } return isSingleton; } } public SpringBeanProcessor(ResteasyDeployment deployment) { this(deployment.getDispatcher(), deployment.getRegistry(), deployment.getProviderFactory()); this.deployment = deployment; } public SpringBeanProcessor(Dispatcher dispatcher) { this(dispatcher, dispatcher.getRegistry(), dispatcher.getProviderFactory()); } public SpringBeanProcessor(Dispatcher dispatcher, Registry registry, ResteasyProviderFactory providerFactory) { this.setRegistry(registry); this.setProviderFactory(providerFactory); this.setDispatcher(dispatcher); } public SpringBeanProcessor() { } @Required public Registry getRegistry() { return registry; } public void setRegistry(Registry registry) { this.registry = registry; } public ResteasyProviderFactory getProviderFactory() { return providerFactory; } public void setProviderFactory(ResteasyProviderFactory providerFactory) { this.providerFactory = providerFactory; } public Dispatcher getDispatcher() { return dispatcher; } public void setDispatcher(Dispatcher dispatcher) { this.dispatcher = dispatcher; } /** * <p> * This method keeps track of @Provider and resources for future use. It also * registers the RESTEasy Registry, ProviderFactry, and Dispatcher for @Autowire * injection. * </p> * <p/> * <p> * Beyond tracking, this will ensure that non-MessageBody(Reader|Writer) @Providers * are created by Spring before any resources by having the resources * "depends-on" the @Providers. * </p> */ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { beanFactory.registerResolvableDependency(Registry.class, getRegistry()); beanFactory.registerResolvableDependency(ResteasyProviderFactory.class, getProviderFactory()); if (dispatcher != null) { beanFactory.registerResolvableDependency(Dispatcher.class, getDispatcher()); } beanFactory.addBeanPostProcessor(new ResteasyBeanPostProcessor(beanFactory)); List<String> dependsOnBeans = new ArrayList<String>(); for (String name : beanFactory.getBeanDefinitionNames()) { BeanDefinition beanDef = beanFactory.getBeanDefinition(name); if ( (beanDef.getBeanClassName() != null || beanDef.getFactoryBeanName() != null) && !beanDef.isAbstract()) processBean(beanFactory, dependsOnBeans, name, beanDef); } dependsOnBeans.addAll(registrations); String[] dependsOnArray = dependsOnBeans.toArray(new String[dependsOnBeans.size()]); if (dependsOnArray.length > 0) { for (SpringResourceFactory resourceFactory : resourceFactories.values()) { BeanDefinition beanDef = beanFactory.getBeanDefinition(resourceFactory.getBeanName()); beanDef.setDependsOn(concat(beanDef.getDependsOn(), dependsOnArray)); } } } /** * process a single @Provider or a single resource. */ protected Class<?> processBean(final ConfigurableListableBeanFactory beanFactory, List<String> dependsOnProviders, String name, BeanDefinition beanDef) { Class<?> beanClass = getBeanClass(name, beanDef, beanFactory); if (beanClass.isAnnotationPresent(Provider.class)) { if (!isSingleton(beanDef)) { throw new RuntimeException(Messages.MESSAGES.providerIsNotSingleton(name)); } providerNames.add(name); if (!MessageBodyWriter.class.isAssignableFrom(beanClass) && !MessageBodyReader.class.isAssignableFrom(beanClass)) { dependsOnProviders.add(name); } } if (GetRestful.isRootResource(beanClass)) { resourceFactories.put(name, new SpringResourceFactory(name, beanFactory, beanClass)); } if(beanClass == ResteasyRegistration.class) { registrations.add(name); } return beanClass; } public String getPropertyValue( MutablePropertyValues registrationPropertyValues, String propertyName) { if(registrationPropertyValues == null) { return null; } PropertyValue propertyValue = registrationPropertyValues.getPropertyValue(propertyName); if(propertyValue == null) { return null; } Object value = propertyValue.getValue(); if(value == null) { return null; } if(value.getClass() == String.class) { return (String) value; } if(value instanceof BeanReference) { return ((BeanReference)value).getBeanName(); } throw new IllegalStateException(Messages.MESSAGES.resteasyRegistrationReferences()); } /** * merge two arrays. * * @param dependsOn * @param dependsOnProviders * @return */ private static String[] concat(String[] dependsOn, String[] dependsOnProviders) { if (dependsOn == null || dependsOn.length == 0) { return dependsOnProviders; } String[] result = new String[dependsOn.length + dependsOnProviders.length]; System.arraycopy(dependsOn, 0, result, 0, dependsOn.length); System.arraycopy(dependsOnProviders, 0, result, dependsOn.length, dependsOnProviders.length); return result; } /** * Get the bean class, and take @Configuration @Beans into consideration * * @param beanDef * @param beanFactory * @return */ private static Class<?> getBeanClass(String name, BeanDefinition beanDef, ConfigurableListableBeanFactory beanFactory) { if (beanDef instanceof RootBeanDefinition) { RootBeanDefinition rootBeanDef = (RootBeanDefinition) beanDef; try { if (rootBeanDef.getBeanClass() != null) { return rootBeanDef.getBeanClass(); } } catch (IllegalStateException e) { // do nothing. This gets thrown for factory beans } } // final String factoryBeanName = beanDef.getFactoryBeanName(); final String factoryMethodName = beanDef.getFactoryMethodName(); if (beanDef.getBeanClassName() != null && factoryMethodName == null) { return getBeanClass(beanDef.getBeanClassName()); } if (factoryMethodName != null) { String factoryClassName = null; if (beanDef instanceof AnnotatedBeanDefinition) { factoryClassName = ((AnnotatedBeanDefinition) beanDef).getMetadata().getClassName(); } else { // Checks if beanDefinition has a factorybean defined. If so, lookup the classname of that bean // definition and use that as the factory class name. if (beanDef.getFactoryBeanName() != null) { factoryClassName = beanFactory.getBeanDefinition(beanDef.getFactoryBeanName()).getBeanClassName(); } else { factoryClassName = beanDef.getBeanClassName(); } } final Class<?> beanClass = getBeanClass(factoryClassName); final Method[] methods = ReflectionUtils.getAllDeclaredMethods(beanClass); for (Method method : methods) { if (method.getName().equals(factoryMethodName)) { return method.getReturnType(); } } /* https://github.com/resteasy/Resteasy/issues/585 If we haven't found the correct factoryMethod using the previous method, fallback to the default FactoryBean getObject method. Case in which this tends to happen: 1. A bean (Bean A) exists which provides factoryMethods for retrieving 1 or more other beans (Bean B, Bean C, ...) example: <bean id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean"/> 2. Bean B is retrieved by telling Spring that the Factory-Bean is Bean A and that there is a method X to retrieve Bean B. example: <bean id="repositoryService" factory-bean="processEngine" factory-method="getRepositoryService"/> 3. When resteasy has to inject Bean B it tries to lookup method X on Bean A instead of Bean B using the above code. As a fix for this, we retrieve the return type for Bean A from the FactoryBean, which later on can be used to retrieve the other beans. */ if (FactoryBean.class.isAssignableFrom(beanClass)) { String defaultFactoryMethod = "getObject"; Class<?> returnType = null; for (Method method : methods) { if (method.getName().equals(defaultFactoryMethod)) { returnType = method.getReturnType(); if (returnType != Object.class) { break; } } } if (returnType != null) { return returnType; } } } throw new IllegalStateException(Messages.MESSAGES.couldNotFindTypeForBean(name)); } private static boolean isSingleton(BeanDefinition beanDef) { try { return beanDef.isSingleton(); } catch (NoSuchBeanDefinitionException nsbde) { // cannot distinguish between singleton & prototype return false; } } private static Class<?> getBeanClass(final String beanClassName) { try { return ClassUtils.forName(beanClassName, Thread.currentThread().getContextClassLoader()); } catch (final ClassNotFoundException e) { throw new IllegalStateException(Messages.MESSAGES.couldNotConvertBeanToClass(beanClassName), e); } } /** * Register all of the resources into RESTEasy only when Spring finishes it's * life-cycle and the spring singleton bean creation is completed */ @Override public void onApplicationEvent(ApplicationEvent event) { for (SpringResourceFactory resourceFactory : resourceFactories.values()) { getRegistry().removeRegistrations(resourceFactory.getScannableClass()); } // The following code would re-process the bean factory, in case the configuration changed. // However, it needs work. // if (event.getSource() instanceof XmlWebApplicationContext) // { // ConfigurableListableBeanFactory beanFactory = ((XmlWebApplicationContext) event.getSource()).getBeanFactory(); // postProcessBeanFactory(beanFactory); // } for (SpringResourceFactory resourceFactory : resourceFactories.values()) { getRegistry().addResourceFactory(resourceFactory, resourceFactory.getContext()); } } @Override public int getOrder() { return this.order; } public void setOrder(int order) { this.order = order; } @Override public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) { return eventType == ContextRefreshedEvent.class; } @Override public boolean supportsSourceType(Class<?> sourceType) { return ApplicationContext.class.isAssignableFrom(sourceType); } }