package com.googlecode.jsonrpc4j.spring; import com.fasterxml.jackson.databind.ObjectMapper; import com.googlecode.jsonrpc4j.ConvertedParameterTransformer; import com.googlecode.jsonrpc4j.ErrorResolver; import com.googlecode.jsonrpc4j.HttpStatusCodeProvider; import com.googlecode.jsonrpc4j.InvocationListener; import com.googlecode.jsonrpc4j.JsonRpcService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import static java.lang.String.format; import static org.springframework.util.ClassUtils.forName; import static org.springframework.util.ClassUtils.getAllInterfacesForClass; /** * <p>This exporter class is deprecated because it exposes all beans from a spring context that has the * {@link JsonRpcService} annotation. If that context is also consuming JSON-RPC services from a remote * system and has proxy clients instantiated in the same context then those proxy clients will also * be (inadvertently) exposed by {@link AutoJsonRpcServiceExporter}. To avoid this, switch over to use * {@link AutoJsonRpcServiceImplExporter} which exposes specific implementations of the JSON-RPC services' * interfaces rather than all beans that implement {@link JsonRpcService}.</p> * * @deprecated use {@link AutoJsonRpcServiceImplExporter} instead. */ @Deprecated @SuppressWarnings("unused") public class AutoJsonRpcServiceExporter implements BeanFactoryPostProcessor { private static final Logger logger = LoggerFactory.getLogger(AutoJsonRpcServiceExporter.class); private static final String PATH_PREFIX = "/"; private ObjectMapper objectMapper; private ErrorResolver errorResolver = null; private Boolean registerTraceInterceptor; private boolean backwardsCompatible = true; private boolean rethrowExceptions = false; private boolean allowExtraParams = false; private boolean allowLessParams = false; private InvocationListener invocationListener = null; private HttpStatusCodeProvider httpStatusCodeProvider = null; private ConvertedParameterTransformer convertedParameterTransformer = null; /** * Finds the beans to expose * map. * <p> * Searches parent factories as well. */ private static Map<String, String> findServiceBeanDefinitions(ConfigurableListableBeanFactory beanFactory) { final Map<String, String> serviceBeanNames = new HashMap<>(); for (String beanName : beanFactory.getBeanDefinitionNames()) { JsonRpcService jsonRpcPath = beanFactory.findAnnotationOnBean(beanName, JsonRpcService.class); if (hasServiceAnnotation(jsonRpcPath)) { String pathValue = jsonRpcPath.value(); logger.debug("Found JSON-RPC path '{}' for bean [{}].", pathValue, beanName); if (isNotDuplicateService(serviceBeanNames, beanName, pathValue)) serviceBeanNames.put(pathValue, beanName); } } collectFromParentBeans(beanFactory, serviceBeanNames); return serviceBeanNames; } @SuppressWarnings("Convert2streamapi") private static void collectFromParentBeans(ConfigurableListableBeanFactory beanFactory, Map<String, String> serviceBeanNames) { BeanFactory parentBeanFactory = beanFactory.getParentBeanFactory(); if (parentBeanFactory != null && ConfigurableListableBeanFactory.class.isInstance(parentBeanFactory)) { for (Entry<String, String> entry : findServiceBeanDefinitions((ConfigurableListableBeanFactory) parentBeanFactory).entrySet()) { if (isNotDuplicateService(serviceBeanNames, entry.getKey(), entry.getValue())) serviceBeanNames.put(entry.getKey(), entry.getValue()); } } } private static boolean isNotDuplicateService(Map<String, String> serviceBeanNames, String beanName, String pathValue) { if (serviceBeanNames.containsKey(pathValue)) { String otherBeanName = serviceBeanNames.get(pathValue); logger.debug("Duplicate JSON-RPC path specification: found {} on both [{}] and [{}].", pathValue, beanName, otherBeanName); return false; } return true; } private static boolean hasServiceAnnotation(JsonRpcService jsonRpcPath) { return jsonRpcPath != null; } public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) beanFactory; Map<String, String> servicePathToBeanName = findServiceBeanDefinitions(defaultListableBeanFactory); for (Entry<String, String> entry : servicePathToBeanName.entrySet()) { registerServiceProxy(defaultListableBeanFactory, makeUrlPath(entry.getKey()), entry.getValue()); } } /** * To make the * {@link org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping} * export a bean automatically, the name should start with a '/'. */ private String makeUrlPath(String servicePath) { return PATH_PREFIX.concat(servicePath); } /** * Registers the new beans with the bean factory. */ private void registerServiceProxy(DefaultListableBeanFactory defaultListableBeanFactory, String servicePath, String serviceBeanName) { BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(JsonServiceExporter.class).addPropertyReference("service", serviceBeanName); BeanDefinition serviceBeanDefinition = findBeanDefinition(defaultListableBeanFactory, serviceBeanName); for (Class<?> currentInterface : getBeanInterfaces(serviceBeanDefinition, defaultListableBeanFactory.getBeanClassLoader())) { if (currentInterface.isAnnotationPresent(JsonRpcService.class)) { String serviceInterface = currentInterface.getName(); logger.debug("Registering interface '{}' for JSON-RPC bean [{}].", serviceInterface, serviceBeanName); builder.addPropertyValue("serviceInterface", serviceInterface); break; } } if (objectMapper != null) { builder.addPropertyValue("objectMapper", objectMapper); } if (errorResolver != null) { builder.addPropertyValue("errorResolver", errorResolver); } if (invocationListener != null) { builder.addPropertyValue("invocationListener", invocationListener); } if (registerTraceInterceptor != null) { builder.addPropertyValue("registerTraceInterceptor", registerTraceInterceptor); } if (httpStatusCodeProvider != null) { builder.addPropertyValue("httpStatusCodeProvider", httpStatusCodeProvider); } if (convertedParameterTransformer != null) { builder.addPropertyValue("convertedParameterTransformer", convertedParameterTransformer); } builder.addPropertyValue("backwardsCompatible", backwardsCompatible); builder.addPropertyValue("rethrowExceptions", rethrowExceptions); builder.addPropertyValue("allowExtraParams", allowExtraParams); builder.addPropertyValue("allowLessParams", allowLessParams); defaultListableBeanFactory.registerBeanDefinition(servicePath, builder.getBeanDefinition()); } /** * Find a {@link BeanDefinition} in the {@link BeanFactory} or it's parents. */ private BeanDefinition findBeanDefinition(ConfigurableListableBeanFactory beanFactory, String serviceBeanName) { if (beanFactory.containsLocalBean(serviceBeanName)) return beanFactory.getBeanDefinition(serviceBeanName); BeanFactory parentBeanFactory = beanFactory.getParentBeanFactory(); if (parentBeanFactory != null && ConfigurableListableBeanFactory.class.isInstance(parentBeanFactory)) return findBeanDefinition((ConfigurableListableBeanFactory) parentBeanFactory, serviceBeanName); throw new RuntimeException(format("Bean with name '%s' can no longer be found.", serviceBeanName)); } private Class<?>[] getBeanInterfaces(BeanDefinition serviceBeanDefinition, ClassLoader beanClassLoader) { String beanClassName = serviceBeanDefinition.getBeanClassName(); try { Class<?> beanClass = forName(beanClassName, beanClassLoader); return getAllInterfacesForClass(beanClass, beanClassLoader); } catch (ClassNotFoundException | LinkageError e) { throw new RuntimeException(format("Cannot find bean class '%s'.", beanClassName), e); } } /** * @param objectMapper the objectMapper to set */ public void setObjectMapper(ObjectMapper objectMapper) { this.objectMapper = objectMapper; } /** * @param errorResolver the errorResolver to set */ public void setErrorResolver(ErrorResolver errorResolver) { this.errorResolver = errorResolver; } /** * @param backwardsCompatible the backwardsCompatible to set */ public void setBackwardsCompatible(boolean backwardsCompatible) { this.backwardsCompatible = backwardsCompatible; } /** * @param rethrowExceptions the rethrowExceptions to set */ public void setRethrowExceptions(boolean rethrowExceptions) { this.rethrowExceptions = rethrowExceptions; } /** * @param allowExtraParams the allowExtraParams to set */ public void setAllowExtraParams(boolean allowExtraParams) { this.allowExtraParams = allowExtraParams; } /** * @param allowLessParams the allowLessParams to set */ public void setAllowLessParams(boolean allowLessParams) { this.allowLessParams = allowLessParams; } /** * See {@link org.springframework.remoting.support.RemoteExporter#setRegisterTraceInterceptor(boolean)} * * @param registerTraceInterceptor the registerTraceInterceptor value to set */ public void setRegisterTraceInterceptor(boolean registerTraceInterceptor) { this.registerTraceInterceptor = registerTraceInterceptor; } /** * @param invocationListener the invocationListener to set */ public void setInvocationListener(InvocationListener invocationListener) { this.invocationListener = invocationListener; } /** * @param httpStatusCodeProvider the HttpStatusCodeProvider to set */ public void setHttpStatusCodeProvider(HttpStatusCodeProvider httpStatusCodeProvider) { this.httpStatusCodeProvider = httpStatusCodeProvider; } /** * @param convertedParameterTransformer the convertedParameterTransformer to set */ public void setConvertedParameterTransformer(ConvertedParameterTransformer convertedParameterTransformer) { this.convertedParameterTransformer = convertedParameterTransformer; } }