package com.googlecode.jsonrpc4j.spring;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.googlecode.jsonrpc4j.JsonRpcService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
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 org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.io.Resource;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import static java.lang.String.format;
import static org.springframework.util.ClassUtils.convertClassNameToResourcePath;
import static org.springframework.util.ResourceUtils.CLASSPATH_URL_PREFIX;
/**
* Auto-creates proxies for service interfaces annotated with {@link JsonRpcService}.
*/
@SuppressWarnings("unused")
public class AutoJsonRpcClientProxyCreator implements BeanFactoryPostProcessor, ApplicationContextAware {
private static final Logger logger = LoggerFactory.getLogger(AutoJsonRpcClientProxyCreator.class);
private ApplicationContext applicationContext;
private String scanPackage;
private URL baseUrl;
private ObjectMapper objectMapper;
private String contentType;
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
SimpleMetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory(applicationContext);
DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) beanFactory;
String resolvedPath = resolvePackageToScan();
logger.debug("Scanning '{}' for JSON-RPC service interfaces.", resolvedPath);
try {
for (Resource resource : applicationContext.getResources(resolvedPath)) {
if (resource.isReadable()) {
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
ClassMetadata classMetadata = metadataReader.getClassMetadata();
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
String jsonRpcPathAnnotation = JsonRpcService.class.getName();
if (annotationMetadata.isAnnotated(jsonRpcPathAnnotation)) {
String className = classMetadata.getClassName();
String path = (String) annotationMetadata.getAnnotationAttributes(jsonRpcPathAnnotation).get("value");
logger.debug("Found JSON-RPC service to proxy [{}] on path '{}'.", className, path);
registerJsonProxyBean(defaultListableBeanFactory, className, path);
}
}
}
} catch (IOException e) {
throw new RuntimeException(format("Cannot scan package '%s' for classes.", resolvedPath), e);
}
}
/**
* Converts the scanPackage to something that the resource loader can handleRequest.
*/
private String resolvePackageToScan() {
return CLASSPATH_URL_PREFIX + convertClassNameToResourcePath(scanPackage) + "/**/*.class";
}
/**
* Registers a new proxy bean with the bean factory.
*/
private void registerJsonProxyBean(DefaultListableBeanFactory defaultListableBeanFactory, String className, String path) {
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder
.rootBeanDefinition(JsonProxyFactoryBean.class)
.addPropertyValue("serviceUrl", appendBasePath(path))
.addPropertyValue("serviceInterface", className);
if (objectMapper != null) {
beanDefinitionBuilder.addPropertyValue("objectMapper", objectMapper);
}
if (contentType != null) {
beanDefinitionBuilder.addPropertyValue("contentType", contentType);
}
defaultListableBeanFactory.registerBeanDefinition(className + "-clientProxy", beanDefinitionBuilder.getBeanDefinition());
}
/**
* Appends the base path to the path found in the interface.
*/
private String appendBasePath(String path) {
try {
return new URL(baseUrl, path).toString();
} catch (MalformedURLException e) {
throw new RuntimeException(format("Cannot combine URLs '%s' and '%s' to valid URL.", baseUrl, path), e);
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public void setBaseUrl(URL baseUrl) {
this.baseUrl = baseUrl;
}
public void setScanPackage(String scanPackage) {
this.scanPackage = scanPackage;
}
public void setObjectMapper(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
public void setContentType(String contextType) {
this.contentType = contextType;
}
}