Skip to content
This repository was archived by the owner on Dec 19, 2023. It is now read-only.

Commit 7fdc5c8

Browse files
authored
Merge pull request #504 from devopsix/feature/502-ignore-abstract-implementations
Ignore abstract classes when detecting GraphQL interface implementations #502
2 parents 3dcde01 + 46066b5 commit 7fdc5c8

File tree

7 files changed

+137
-11
lines changed

7 files changed

+137
-11
lines changed

graphql-kickstart-spring-boot-autoconfigure-graphql-annotations/src/main/java/graphql/kickstart/graphql/annotations/GraphQLAnnotationsAutoConfiguration.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@
1717
import graphql.schema.GraphQLSchema;
1818
import java.lang.annotation.Annotation;
1919
import java.lang.reflect.Method;
20+
import java.lang.reflect.Modifier;
2021
import java.util.Collections;
2122
import java.util.List;
2223
import java.util.Optional;
2324
import java.util.Set;
25+
import java.util.function.Predicate;
2426
import lombok.RequiredArgsConstructor;
2527
import lombok.extern.slf4j.Slf4j;
2628
import org.reflections.Reflections;
@@ -179,11 +181,15 @@ private void registerGraphQLInterfaceImplementations(
179181
final Reflections reflections,
180182
final AnnotationsSchemaCreator.Builder builder
181183
) {
184+
Predicate<Class<?>> implementationQualifiesForInclusion =
185+
type -> !(graphQLAnnotationsProperties.isIgnoreAbstractInterfaceImplementations()
186+
&& Modifier.isAbstract(type.getModifiers()));
182187
reflections.getMethodsAnnotatedWith(GraphQLField.class).stream()
183188
.map(Method::getDeclaringClass)
184189
.filter(Class::isInterface)
185190
.forEach(graphQLInterface ->
186-
reflections.getSubTypesOf(graphQLInterface)
191+
reflections.getSubTypesOf(graphQLInterface).stream()
192+
.filter(implementationQualifiesForInclusion)
187193
.forEach(implementation -> {
188194
log.info("Registering {} as an implementation of GraphQL interface {}",
189195
implementation,

graphql-kickstart-spring-boot-autoconfigure-graphql-annotations/src/main/java/graphql/kickstart/graphql/annotations/GraphQLAnnotationsProperties.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,11 @@ public class GraphQLAnnotationsProperties {
2727
*/
2828
@Builder.Default
2929
private boolean alwaysPrettify = true;
30+
31+
/**
32+
* If set to <code>true</code> abstract classes implementing a GraphQL interface will not be added to the schema.
33+
* Defaults to <code>false</code> for backward compatibility.
34+
*/
35+
@Builder.Default
36+
private boolean ignoreAbstractInterfaceImplementations = false;
3037
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package graphql.kickstart.graphql.annotations;
2+
3+
import com.graphql.spring.boot.test.GraphQLResponse;
4+
import com.graphql.spring.boot.test.GraphQLTestTemplate;
5+
import graphql.kickstart.graphql.annotations.test.interfaces.Car;
6+
import graphql.kickstart.graphql.annotations.test.interfaces.Truck;
7+
import graphql.schema.GraphQLNamedType;
8+
import graphql.schema.GraphQLScalarType;
9+
import graphql.schema.GraphQLSchema;
10+
import org.junit.jupiter.api.DisplayName;
11+
import org.junit.jupiter.api.Test;
12+
import org.springframework.beans.factory.annotation.Autowired;
13+
import org.springframework.boot.test.context.SpringBootTest;
14+
import org.springframework.test.context.ActiveProfiles;
15+
16+
import java.io.IOException;
17+
import java.util.Set;
18+
import java.util.stream.Collectors;
19+
20+
import static org.assertj.core.api.Assertions.assertThat;
21+
22+
@DisplayName("Testing interface handling (ignore abstract implementations).")
23+
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
24+
properties = "graphql.annotations.ignore-abstract-interface-implementations=true")
25+
@ActiveProfiles({"test", "interface-test"})
26+
class GraphQLInterfaceQueryIgnoreAbstractInterfaceImplementationsTest {
27+
28+
@Autowired
29+
private GraphQLTestTemplate graphQLTestTemplate;
30+
31+
@Autowired
32+
private GraphQLSchema graphQLSchema;
33+
34+
@Test
35+
@DisplayName("Assert that GraphQL interfaces and their implementations are registered correctly.")
36+
void testInterfaceQuery() throws IOException {
37+
// WHEN
38+
final GraphQLResponse actual = graphQLTestTemplate
39+
.postForResource("queries/test-interface-query.graphql");
40+
// THEN
41+
assertThat(actual.get("$.data.vehicles[0]", Car.class))
42+
.usingRecursiveComparison().ignoringAllOverriddenEquals()
43+
.isEqualTo(Car.builder().numberOfSeats(4).registrationNumber("ABC-123").build());
44+
assertThat(actual.get("$.data.vehicles[1]", Truck.class))
45+
.usingRecursiveComparison().ignoringAllOverriddenEquals()
46+
.isEqualTo(Truck.builder().cargoWeightCapacity(12).registrationNumber("CBA-321").build());
47+
}
48+
49+
@Test
50+
@DisplayName("Assert that abstract GraphQL interface implementations are excluded from the schema.")
51+
void testInterfaceImplementationDetection() {
52+
// THEN
53+
Set<String> vehicleDomainTypes = graphQLSchema.getAllTypesAsList().stream()
54+
.filter(type -> !(type instanceof GraphQLScalarType))
55+
.map(GraphQLNamedType::getName)
56+
.filter(name -> !name.startsWith("__"))
57+
.filter(name -> !"PageInfo".equals(name))
58+
.collect(Collectors.toSet());
59+
// Must not contain "AbstractVehicle"
60+
assertThat(vehicleDomainTypes)
61+
.containsExactlyInAnyOrder("InterfaceQuery", "Vehicle", "Car", "Truck");
62+
}
63+
}
64+

graphql-kickstart-spring-boot-autoconfigure-graphql-annotations/src/test/java/graphql/kickstart/graphql/annotations/GraphQLInterfaceQueryTest.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@
77
import graphql.kickstart.graphql.annotations.test.interfaces.Car;
88
import graphql.kickstart.graphql.annotations.test.interfaces.Truck;
99
import java.io.IOException;
10+
import java.util.Set;
11+
import java.util.stream.Collectors;
12+
import graphql.schema.GraphQLNamedType;
13+
import graphql.schema.GraphQLScalarType;
14+
import graphql.schema.GraphQLSchema;
1015
import org.junit.jupiter.api.DisplayName;
1116
import org.junit.jupiter.api.Test;
1217
import org.springframework.beans.factory.annotation.Autowired;
@@ -21,6 +26,9 @@ class GraphQLInterfaceQueryTest {
2126
@Autowired
2227
private GraphQLTestTemplate graphQLTestTemplate;
2328

29+
@Autowired
30+
private GraphQLSchema graphQLSchema;
31+
2432
@Test
2533
@DisplayName("Assert that GraphQL interfaces and their implementations are registered correctly.")
2634
void testInterfaceQuery() throws IOException {
@@ -35,5 +43,20 @@ void testInterfaceQuery() throws IOException {
3543
.usingRecursiveComparison().ignoringAllOverriddenEquals()
3644
.isEqualTo(Truck.builder().cargoWeightCapacity(12).registrationNumber("CBA-321").build());
3745
}
46+
47+
@Test
48+
@DisplayName("Assert that abstract GraphQL interface implementations are added to the schema.")
49+
void testInterfaceImplementationDetection() {
50+
// THEN
51+
Set<String> vehicleDomainTypes = graphQLSchema.getAllTypesAsList().stream()
52+
.filter(type -> !(type instanceof GraphQLScalarType))
53+
.map(GraphQLNamedType::getName)
54+
.filter(name -> !name.startsWith("__"))
55+
.filter(name -> !"PageInfo".equals(name))
56+
.collect(Collectors.toSet());
57+
// Should contain "AbstractVehicle"
58+
assertThat(vehicleDomainTypes)
59+
.containsExactlyInAnyOrder("InterfaceQuery", "Vehicle", "AbstractVehicle", "Car", "Truck");
60+
}
3861
}
3962

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package graphql.kickstart.graphql.annotations.test.interfaces;
2+
3+
import graphql.annotations.annotationTypes.GraphQLField;
4+
import graphql.annotations.annotationTypes.GraphQLNonNull;
5+
import lombok.AllArgsConstructor;
6+
import lombok.Data;
7+
import lombok.NoArgsConstructor;
8+
import lombok.experimental.SuperBuilder;
9+
10+
@Data
11+
@SuperBuilder
12+
@AllArgsConstructor
13+
@NoArgsConstructor
14+
public abstract class AbstractVehicle implements Vehicle {
15+
16+
/**
17+
* Note that you have to repeat the annotations from the interface method!
18+
*/
19+
@GraphQLField
20+
@GraphQLNonNull
21+
private String registrationNumber;
22+
}

graphql-kickstart-spring-boot-autoconfigure-graphql-annotations/src/test/java/graphql/kickstart/graphql/annotations/test/interfaces/Car.java

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,26 @@
33
import graphql.annotations.annotationTypes.GraphQLField;
44
import graphql.annotations.annotationTypes.GraphQLNonNull;
55
import lombok.AllArgsConstructor;
6-
import lombok.Builder;
76
import lombok.Data;
7+
import lombok.EqualsAndHashCode;
88
import lombok.NoArgsConstructor;
9+
import lombok.experimental.SuperBuilder;
910

1011
@Data
11-
@Builder
12+
@SuperBuilder
1213
@AllArgsConstructor
1314
@NoArgsConstructor
14-
public class Car implements Vehicle {
15-
16-
/**
17-
* Note that you have to repeat the annotations from the interface method!
18-
*/
19-
@GraphQLField
20-
@GraphQLNonNull
21-
private String registrationNumber;
15+
@EqualsAndHashCode(callSuper = true)
16+
// “implements Vehicle” has to be repeated here although already inherited from AbstractVehicle
17+
// because otherwise GraphQL-Java Annotations would not find this class.
18+
public class Car extends AbstractVehicle implements Vehicle {
2219

2320
@GraphQLField
2421
@GraphQLNonNull
2522
private int numberOfSeats;
23+
24+
public Car(String registrationNumber, int numberOfSeats) {
25+
super(registrationNumber);
26+
this.numberOfSeats = numberOfSeats;
27+
}
2628
}

graphql-kickstart-spring-boot-autoconfigure-graphql-annotations/src/test/java/graphql/kickstart/graphql/annotations/test/interfaces/Truck.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
@Builder
1212
@NoArgsConstructor
1313
@AllArgsConstructor
14+
// Truck intentionally does not extend AbstractVehicle in order to have one inheritance
15+
// hierarchy free from abstract classes.
1416
public class Truck implements Vehicle {
1517

1618
/**

0 commit comments

Comments
 (0)