diff --git a/spring-graphql-docs/src/docs/asciidoc/index.adoc b/spring-graphql-docs/src/docs/asciidoc/index.adoc index e3308692..42dddaa3 100644 --- a/spring-graphql-docs/src/docs/asciidoc/index.adoc +++ b/spring-graphql-docs/src/docs/asciidoc/index.adoc @@ -671,6 +671,10 @@ Schema mapping handler methods can have any of the following method arguments: | For access to field arguments with conversion. See <>. +| `@ProjectedPayload` Interface +| For access to field arguments through a project interface. +See <>. + | Source | For access to the source (i.e. parent/container) instance of the field. See <>. @@ -740,50 +744,23 @@ You can use `@Argument` on a `Map` argument, to obtain all argum values. The name attribute on `@Argument` must not be set. -[[controllers-schema-mapping-source]] -==== Source +[[controllers-schema-mapping-projectedpayload-argument]] +==== `@ProjectPayload` Interface -In GraphQL Java, the `DataFetchingEnvironment` provides access to the source (i.e. -parent/container) instance of the field. To access this, simply declare a method parameter -of the expected target type. - -[source,java,indent=0,subs="verbatim,quotes"] ----- - @Controller - public class BookController { - - @SchemaMapping - public Author author(Book book) { - // ... - } - } ----- - -The source method argument also helps to determine the type name for the mapping. -If the simple name of the Java class matches the GraphQL type, then there is no need to -explicitly specify the type name in the `@SchemaMapping` annotation. - -[TIP] -==== -A <> handler method can batch load all authors for a query, -given a list of source/parent books objects. -==== - - -[[controllers-schema-mapping-argument-projections]] -==== Argument Projections - -When accessing individual arguments from a GraphQL request, interface projections can -be useful to access arguments through a well-defined interface. - -Spring Data's `@ProjectedPayload` can be used to annotate projection interfaces that -can be declared as handler method arguments. Payload projection can work on top-level -arguments (`DataFetchingEnvironment.getArguments()`). Alternatively, projections can -be applied on individual arguments by using `@Argument` with a projected payload interface. - -Argument projections are provided by https://docs.spring.io/spring-data/commons/docs/current/reference/html/#projections.interfaces[Spring Data's Interface projections] +As an alternative to using complete Objects with <>, +you can also use a projection interface to access GraphQL request arguments through a +well-defined, minimal interface. Argument projections are provided by +https://docs.spring.io/spring-data/commons/docs/current/reference/html/#projections.interfaces[Spring Data's Interface projections] when Spring Data is on the class path. +To make use of this, create an interface annotated with `@ProjectedPayload` and declare +it as a controller method parameter. If the parameter is annotated with `@Argument`, +it applies to an individual argument within the `DataFetchingEnvironment.getArguments()` +map. When declared without `@Argument`, the projection works on top-level arguments in +the complete arguments map. + +For example: + [source,java,indent=0,subs="verbatim,quotes"] ---- @Controller @@ -817,6 +794,35 @@ when Spring Data is on the class path. ---- +[[controllers-schema-mapping-source]] +==== Source + +In GraphQL Java, the `DataFetchingEnvironment` provides access to the source (i.e. +parent/container) instance of the field. To access this, simply declare a method parameter +of the expected target type. + +[source,java,indent=0,subs="verbatim,quotes"] +---- + @Controller + public class BookController { + + @SchemaMapping + public Author author(Book book) { + // ... + } + } +---- + +The source method argument also helps to determine the type name for the mapping. +If the simple name of the Java class matches the GraphQL type, then there is no need to +explicitly specify the type name in the `@SchemaMapping` annotation. + +[TIP] +==== +A <> handler method can batch load all authors for a query, +given a list of source/parent books objects. +==== + [[controllers-schema-mapping-data-loader]] ==== `DataLoader` diff --git a/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/AnnotatedControllerConfigurer.java b/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/AnnotatedControllerConfigurer.java index 4220794d..e7cc8c41 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/AnnotatedControllerConfigurer.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/AnnotatedControllerConfigurer.java @@ -87,6 +87,7 @@ public class AnnotatedControllerConfigurer private final static boolean springDataPresent = ClassUtils.isPresent( "org.springframework.data.projection.SpelAwareProxyProjectionFactory", AnnotatedControllerConfigurer.class.getClassLoader()); + private final static boolean springSecurityPresent = ClassUtils.isPresent( "org.springframework.security.core.context.SecurityContext", AnnotatedControllerConfigurer.class.getClassLoader()); @@ -121,6 +122,7 @@ public class AnnotatedControllerConfigurer public void afterPropertiesSet() { this.argumentResolvers = new HandlerMethodArgumentResolverComposite(); if (springDataPresent) { + // This must be ahead of ArgumentMethodArgumentResolver this.argumentResolvers.addResolver(new ProjectedPayloadMethodArgumentResolver()); } this.argumentResolvers.addResolver(new ArgumentMapMethodArgumentResolver()); diff --git a/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/ProjectedPayloadMethodArgumentResolver.java b/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/ProjectedPayloadMethodArgumentResolver.java index 704b4a89..8be50012 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/ProjectedPayloadMethodArgumentResolver.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/ProjectedPayloadMethodArgumentResolver.java @@ -30,14 +30,16 @@ import org.springframework.graphql.data.method.HandlerMethodArgumentResolver; import org.springframework.graphql.data.method.annotation.Argument; /** - * Resolver to obtain a {@link ProjectedPayload @ProjectedPayload} - * for {@link DataFetchingEnvironment#getArguments()}. + * Resolver to obtain an {@link ProjectedPayload @ProjectedPayload}, + * either based on the complete {@link DataFetchingEnvironment#getArguments()} + * map, or based on a specific argument within the map when the method + * parameter is annotated with {@code @Argument}. * - *

Projected payloads consist of the projection interface and accessor methods. - * Projections can be closed or open projections. Closed projections use interface - * getter methods to access underlying properties directly. Open projection methods - * make use of the {@code @Value} annotation to evaluate SpEL expressions against the - * underlying {@code target} object. + *

Projected payloads consist of the projection interface and accessor + * methods. Projections can be closed or open projections. Closed projections + * use interface getter methods to access underlying properties directly. + * Open projection methods make use of the {@code @Value} annotation to + * evaluate SpEL expressions against the underlying {@code target} object. * *

For example: *

@@ -62,6 +64,17 @@ public class ProjectedPayloadMethodArgumentResolver implements HandlerMethodArgu
 	private final SpelAwareProxyProjectionFactory projectionFactory = new SpelAwareProxyProjectionFactory();
 
 
+	@Override
+	public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
+		this.projectionFactory.setBeanFactory(beanFactory);
+	}
+
+	@Override
+	public void setBeanClassLoader(ClassLoader classLoader) {
+		this.projectionFactory.setBeanClassLoader(classLoader);
+	}
+
+
 	@Override
 	public boolean supportsParameter(MethodParameter parameter) {
 		Class type = parameter.getParameterType();
@@ -88,13 +101,4 @@ public class ProjectedPayloadMethodArgumentResolver implements HandlerMethodArgu
 		return this.projectionFactory.createProjection(projectionType, projectionSource);
 	}
 
-	@Override
-	public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
-		this.projectionFactory.setBeanFactory(beanFactory);
-	}
-
-	@Override
-	public void setBeanClassLoader(ClassLoader classLoader) {
-		this.projectionFactory.setBeanClassLoader(classLoader);
-	}
 }
diff --git a/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/SchemaMappingInvocationTests.java b/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/SchemaMappingInvocationTests.java
index fd3fb66f..88d7c27d 100644
--- a/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/SchemaMappingInvocationTests.java
+++ b/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/SchemaMappingInvocationTests.java
@@ -99,7 +99,7 @@ public class SchemaMappingInvocationTests {
 	}
 
 	@Test
-	void queryWithProjectedArgument() {
+	void queryWithProjectionOnArgumentsMap() {
 		String query = "{ " +
 				"  booksByProjectedArguments(author:\"Orwell\") { " +
 				"    id" +
@@ -116,7 +116,7 @@ public class SchemaMappingInvocationTests {
 	}
 
 	@Test
-	void booksByProjectedCriteria() {
+	void queryWithProjectionOnNamedArgument() {
 		String query = "{ " +
 				"  booksByProjectedCriteria(criteria: {author:\"Orwell\"}) { " +
 				"    id" +