Skip to content

Commit 5977b2d

Browse files
committed
Register runtime hints for @EntityMapping methods
Since the introduction of `@EntityMapping` support on controller handlers, we need to support such variants for the GraalVM Native case. This commit registers the relevant hints for runtime reflection support with GraalVM Native. Closes gh-928
1 parent c955603 commit 5977b2d

File tree

2 files changed

+115
-5
lines changed

2 files changed

+115
-5
lines changed

spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/SchemaMappingBeanFactoryInitializationAotProcessor.java

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2020-2023 the original author or authors.
2+
* Copyright 2020-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -44,6 +44,7 @@
4444
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
4545
import org.springframework.data.projection.TargetAware;
4646
import org.springframework.graphql.data.ArgumentValue;
47+
import org.springframework.graphql.data.federation.EntityMapping;
4748
import org.springframework.graphql.data.method.HandlerMethodArgumentResolver;
4849
import org.springframework.graphql.data.method.HandlerMethodArgumentResolverComposite;
4950
import org.springframework.graphql.data.method.annotation.BatchMapping;
@@ -58,8 +59,8 @@
5859
* {@link BeanFactoryInitializationAotProcessor} implementation for registering
5960
* runtime hints discoverable through GraphQL controllers, such as:
6061
* <ul>
61-
* <li>invocation reflection on {@code @SchemaMapping} and {@code @BatchMapping}
62-
* annotated controllers methods
62+
* <li>invocation reflection on {@code @SchemaMapping}, {@code @BatchMapping}
63+
* and {@code @EntityMapping} annotated controllers methods
6364
* <li>invocation reflection on {@code @GraphQlExceptionHandler} methods
6465
* in {@code @Controller} and {@code @ControllerAdvice} beans
6566
* <li>binding reflection on controller method arguments, needed for binding or
@@ -167,7 +168,8 @@ private void registerSpringDataSpelSupport(RuntimeHints runtimeHints) {
167168

168169
private boolean isGraphQlHandlerMethod(AnnotatedElement element) {
169170
MergedAnnotations annotations = MergedAnnotations.from(element, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY);
170-
return annotations.isPresent(SchemaMapping.class) || annotations.isPresent(BatchMapping.class);
171+
return annotations.isPresent(SchemaMapping.class) || annotations.isPresent(BatchMapping.class)
172+
|| annotations.isPresent(EntityMapping.class);
171173
}
172174

173175
private boolean isExceptionHandlerMethod(AnnotatedElement element) {

spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/SchemaMappingBeanFactoryInitializationAotProcessorTests.java

+109-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2020-2023 the original author or authors.
2+
* Copyright 2020-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -59,6 +59,7 @@
5959
import org.springframework.graphql.Author;
6060
import org.springframework.graphql.Book;
6161
import org.springframework.graphql.data.ArgumentValue;
62+
import org.springframework.graphql.data.federation.EntityMapping;
6263
import org.springframework.graphql.data.method.annotation.Argument;
6364
import org.springframework.graphql.data.method.annotation.BatchMapping;
6465
import org.springframework.graphql.data.method.annotation.ContextValue;
@@ -451,6 +452,113 @@ public GraphQLError handleBindException(BindException exc) {
451452
}
452453
}
453454

455+
@Nested
456+
class EntityMappingTests {
457+
458+
@Test
459+
void registerBindingReflectionOnReturnType() {
460+
processBeanClasses(ReturnTypeController.class);
461+
assertThatIntrospectionOnMethodsHintRegisteredForType(ReturnTypeController.class);
462+
assertThatInvocationHintRegisteredForMethods(ReturnTypeController.class, "bookById");
463+
assertThatHintsForJavaBeanBindingRegisteredForTypes(Book.class);
464+
}
465+
466+
@Test
467+
void registerBindingReflectionOnOnNamedValue() {
468+
processBeanClasses(NamedValueController.class);
469+
assertThatIntrospectionOnMethodsHintRegisteredForType(NamedValueController.class);
470+
assertThatInvocationHintRegisteredForMethods(NamedValueController.class, "book");
471+
assertThatHintsForJavaBeanBindingRegisteredForTypes(Book.class, Identifier.class);
472+
}
473+
474+
@Test
475+
void registerBindingReflectionOnOnNamedValues() {
476+
processBeanClasses(NamedValuesController.class);
477+
assertThatIntrospectionOnMethodsHintRegisteredForType(NamedValuesController.class);
478+
assertThatInvocationHintRegisteredForMethods(NamedValuesController.class, "books");
479+
assertThatHintsForJavaBeanBindingRegisteredForTypes(Book.class, Identifier.class);
480+
}
481+
482+
@Test
483+
void registerBindingReflectionOnAsyncReturnType() {
484+
processBeanClasses(AsyncReturnTypeController.class);
485+
assertThatIntrospectionOnMethodsHintRegisteredForType(AsyncReturnTypeController.class);
486+
assertThatInvocationHintRegisteredForMethods(AsyncReturnTypeController.class, "author");
487+
assertThatHintsForJavaBeanBindingRegisteredForTypes(Author.class);
488+
}
489+
490+
@Test
491+
void doNotRegisterBindingForContextArguments() {
492+
processBeanClasses(ContextArgumentsController.class);
493+
assertThatIntrospectionOnMethodsHintRegisteredForType(ContextArgumentsController.class);
494+
assertThatInvocationHintRegisteredForMethods(ContextArgumentsController.class, "dataFetchingEnvironment");
495+
assertThatHintsAreNotRegisteredForTypes(GraphQLContext.class, DataFetchingFieldSelectionSet.class, Locale.class);
496+
}
497+
498+
@Test
499+
void doNotRegisterBindingForAnnotatedContextArguments() {
500+
processBeanClasses(AnnotatedContextArgumentController.class);
501+
assertThatIntrospectionOnMethodsHintRegisteredForType(AnnotatedContextArgumentController.class);
502+
assertThatInvocationHintRegisteredForMethods(AnnotatedContextArgumentController.class, "contextValue");
503+
assertThatHintsAreNotRegisteredForTypes(Book.class);
504+
}
505+
506+
507+
@Controller
508+
static class ReturnTypeController {
509+
@EntityMapping
510+
public Book bookById(@Argument Long id) {
511+
return null;
512+
}
513+
514+
}
515+
516+
@Controller
517+
static class NamedValueController {
518+
@EntityMapping
519+
public Book book(@Argument Identifier id) {
520+
return null;
521+
}
522+
523+
}
524+
525+
@Controller
526+
static class NamedValuesController {
527+
@EntityMapping
528+
public List<Book> books(@Argument List<Identifier> idList) {
529+
return null;
530+
}
531+
532+
}
533+
534+
@Controller
535+
static class AsyncReturnTypeController {
536+
@EntityMapping
537+
public CompletableFuture<Author> author(Long bookId) {
538+
return null;
539+
}
540+
541+
}
542+
543+
@Controller
544+
static class ContextArgumentsController {
545+
@EntityMapping
546+
public void dataFetchingEnvironment(GraphQLContext context, DataFetchingFieldSelectionSet selectionSet, Locale locale) {
547+
}
548+
}
549+
550+
@Controller
551+
class AnnotatedContextArgumentController {
552+
@EntityMapping
553+
public void contextValue(@ContextValue Book book, @LocalContextValue Book localBook) {
554+
}
555+
}
556+
557+
record Identifier(String id) {
558+
559+
}
560+
}
561+
454562

455563
private void processBeanClasses(Class<?>... beanClasses) {
456564
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

0 commit comments

Comments
 (0)