Configure ConversionService for argument conversion

This commit configures a `FormattingConversionService` into the argument
resolution infrastructure, using the expected conversion service for
both MVC and WebFlux.

Closes gh-148
This commit is contained in:
Brian Clozel
2021-09-30 15:26:22 +02:00
parent a66b49540b
commit 6975e6c37b
8 changed files with 75 additions and 11 deletions

View File

@@ -36,7 +36,6 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.graphql.data.method.annotation.support.AnnotatedControllerConfigurer;
import org.springframework.graphql.execution.DataFetcherExceptionResolver;
import org.springframework.graphql.execution.GraphQlSource;
import org.springframework.graphql.execution.MissingSchemaException;
@@ -57,11 +56,6 @@ public class GraphQlAutoConfiguration {
private static final Log logger = LogFactory.getLog(GraphQlAutoConfiguration.class);
@Bean
public AnnotatedControllerConfigurer annotatedControllerConfigurer() {
return new AnnotatedControllerConfigurer();
}
@Bean
public GraphQlSource graphQlSource(ResourcePatternResolver resourcePatternResolver, GraphQlProperties properties,
ObjectProvider<DataFetcherExceptionResolver> exceptionResolversProvider,

View File

@@ -24,6 +24,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
@@ -36,7 +37,9 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.format.support.FormattingConversionService;
import org.springframework.graphql.GraphQlService;
import org.springframework.graphql.data.method.annotation.support.AnnotatedControllerConfigurer;
import org.springframework.graphql.execution.GraphQlSource;
import org.springframework.graphql.web.WebGraphQlHandler;
import org.springframework.graphql.web.WebInterceptor;
@@ -78,6 +81,13 @@ public class GraphQlWebFluxAutoConfiguration {
private static final Log logger = LogFactory.getLog(GraphQlWebFluxAutoConfiguration.class);
@Bean
public AnnotatedControllerConfigurer annotatedControllerConfigurer(@Qualifier("webFluxConversionService") FormattingConversionService conversionService) {
AnnotatedControllerConfigurer annotatedControllerConfigurer = new AnnotatedControllerConfigurer();
annotatedControllerConfigurer.setConversionService(conversionService);
return annotatedControllerConfigurer;
}
@Bean
@ConditionalOnBean(GraphQlService.class)
@ConditionalOnMissingBean

View File

@@ -27,6 +27,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
@@ -40,7 +41,9 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.format.support.FormattingConversionService;
import org.springframework.graphql.GraphQlService;
import org.springframework.graphql.data.method.annotation.support.AnnotatedControllerConfigurer;
import org.springframework.graphql.execution.GraphQlSource;
import org.springframework.graphql.execution.ThreadLocalAccessor;
import org.springframework.graphql.web.WebGraphQlHandler;
@@ -85,6 +88,12 @@ public class GraphQlWebMvcAutoConfiguration {
private static final Log logger = LogFactory.getLog(GraphQlWebMvcAutoConfiguration.class);
@Bean
public AnnotatedControllerConfigurer annotatedControllerConfigurer(@Qualifier("mvcConversionService") FormattingConversionService conversionService) {
AnnotatedControllerConfigurer annotatedControllerConfigurer = new AnnotatedControllerConfigurer();
annotatedControllerConfigurer.setConversionService(conversionService);
return annotatedControllerConfigurer;
}
@Bean
@ConditionalOnBean(GraphQlService.class)

View File

@@ -45,6 +45,7 @@ import org.springframework.core.KotlinDetector;
import org.springframework.core.MethodIntrospector;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.convert.ConversionService;
import org.springframework.graphql.data.method.HandlerMethod;
import org.springframework.graphql.data.method.HandlerMethodArgumentResolver;
import org.springframework.graphql.data.method.HandlerMethodArgumentResolverComposite;
@@ -90,12 +91,19 @@ public class AnnotatedControllerConfigurer
@Nullable
private HandlerMethodArgumentResolverComposite argumentResolvers;
@Nullable
private ConversionService conversionService;
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
public void setConversionService(ConversionService conversionService) {
this.conversionService = conversionService;
}
protected final ApplicationContext obtainApplicationContext() {
Assert.state(this.applicationContext != null, "No ApplicationContext");
return this.applicationContext;
@@ -106,7 +114,7 @@ public class AnnotatedControllerConfigurer
public void afterPropertiesSet() {
this.argumentResolvers = new HandlerMethodArgumentResolverComposite();
this.argumentResolvers.addResolver(new ArgumentMapMethodArgumentResolver());
this.argumentResolvers.addResolver(new ArgumentMethodArgumentResolver());
this.argumentResolvers.addResolver(new ArgumentMethodArgumentResolver(this.conversionService));
this.argumentResolvers.addResolver(new DataFetchingEnvironmentMethodArgumentResolver());
this.argumentResolvers.addResolver(new DataLoaderMethodArgumentResolver());

View File

@@ -23,9 +23,11 @@ import graphql.schema.DataFetchingEnvironment;
import org.springframework.core.CollectionFactory;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.graphql.data.method.HandlerMethodArgumentResolver;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.validation.DataBinder;
@@ -41,7 +43,14 @@ import org.springframework.validation.DataBinder;
*/
public class ArgumentMethodArgumentResolver implements HandlerMethodArgumentResolver {
private final GraphQlArgumentInstantiator instantiator = new GraphQlArgumentInstantiator();
private final GraphQlArgumentInstantiator instantiator;
private final ConversionService conversionService;
public ArgumentMethodArgumentResolver(@Nullable ConversionService conversionService) {
this.conversionService = conversionService;
this.instantiator = new GraphQlArgumentInstantiator(conversionService);
}
@Override
public boolean supportsParameter(MethodParameter parameter) {
@@ -100,6 +109,7 @@ public class ArgumentMethodArgumentResolver implements HandlerMethodArgumentReso
}
else {
DataBinder converter = new DataBinder(null);
converter.setConversionService(this.conversionService);
target = converter.convertIfNecessary(rawValue, targetType);
Assert.isTrue(target != null,
() -> "Value of type [" + rawValue.getClass() + "] cannot be converted to argument of type [" +

View File

@@ -28,7 +28,9 @@ import org.springframework.beans.BeanUtils;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.core.CollectionFactory;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.validation.DataBinder;
@@ -40,7 +42,12 @@ import org.springframework.validation.DataBinder;
*/
class GraphQlArgumentInstantiator {
private final DataBinder converter = new DataBinder(null);
private final DataBinder converter;
public GraphQlArgumentInstantiator(@Nullable ConversionService conversionService) {
this.converter = new DataBinder(null);
this.converter.setConversionService(conversionService);
}
/**
* Instantiate the given target type and bind data from

View File

@@ -30,6 +30,8 @@ import org.junit.jupiter.api.Test;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.format.support.FormattingConversionService;
import org.springframework.graphql.Book;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.MutationMapping;
@@ -48,7 +50,7 @@ class ArgumentMethodArgumentResolverTests {
private final ObjectMapper mapper = new ObjectMapper();
ArgumentMethodArgumentResolver resolver = new ArgumentMethodArgumentResolver();
ArgumentMethodArgumentResolver resolver = new ArgumentMethodArgumentResolver(new DefaultFormattingConversionService());
@Test
void shouldSupportAnnotatedParameters() {
@@ -98,6 +100,17 @@ class ArgumentMethodArgumentResolverTests {
.extracting("name").containsExactly("first", "second");
}
@Test
void shouldResolveArgumentWithConversionService() throws Exception {
Method bookByKeyword = ClassUtils.getMethod(BookController.class, "bookByKeyword", Keyword.class);
String payload = "{\"keyword\": \"test\" }";
DataFetchingEnvironment environment = initEnvironment(payload);
MethodParameter methodParameter = getMethodParameter(bookByKeyword, 0);
Object result = resolver.resolveArgument(methodParameter, environment);
assertThat(result).isNotNull().isInstanceOf(Keyword.class);
assertThat((Keyword) result).hasFieldOrPropertyWithValue("term", "test");
}
private MethodParameter getMethodParameter(Method method, int index) {
MethodParameter methodParameter = new MethodParameter(method, index);
methodParameter.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
@@ -132,6 +145,11 @@ class ArgumentMethodArgumentResolverTests {
return null;
}
@QueryMapping
public List<Book> bookByKeyword(@Argument Keyword keyword) {
return null;
}
}
static class BookInput {
@@ -161,6 +179,14 @@ class ArgumentMethodArgumentResolverTests {
String term;
private Keyword(String term) {
this.term = term;
}
public static Keyword of(String term) {
return new Keyword(term);
}
public String getTerm() {
return this.term;
}

View File

@@ -40,7 +40,7 @@ class GraphQlArgumentInstantiatorTests {
private ObjectMapper mapper = new ObjectMapper();
private GraphQlArgumentInstantiator instantiator = new GraphQlArgumentInstantiator();
private GraphQlArgumentInstantiator instantiator = new GraphQlArgumentInstantiator(null);
@Test
void shouldInstantiateDefaultConstructor() throws Exception {