1
+ package graphql .kickstart .graphql .annotations ;
2
+
3
+ import graphql .annotations .AnnotationsSchemaCreator ;
4
+ import graphql .annotations .annotationTypes .GraphQLField ;
5
+ import graphql .annotations .annotationTypes .GraphQLTypeExtension ;
6
+ import graphql .annotations .annotationTypes .directives .definition .GraphQLDirectiveDefinition ;
7
+ import graphql .annotations .processor .GraphQLAnnotations ;
8
+ import graphql .annotations .processor .typeFunctions .TypeFunction ;
9
+ import graphql .kickstart .graphql .annotations .exceptions .MissingQueryResolverException ;
10
+ import graphql .kickstart .graphql .annotations .exceptions .MultipleMutationResolversException ;
11
+ import graphql .kickstart .graphql .annotations .exceptions .MultipleQueryResolversException ;
12
+ import graphql .kickstart .graphql .annotations .exceptions .MultipleSubscriptionResolversException ;
13
+ import graphql .relay .Relay ;
14
+ import graphql .schema .GraphQLScalarType ;
15
+ import graphql .schema .GraphQLSchema ;
16
+ import lombok .RequiredArgsConstructor ;
17
+ import lombok .extern .slf4j .Slf4j ;
18
+ import org .reflections .Reflections ;
19
+ import org .reflections .ReflectionsException ;
20
+ import org .reflections .scanners .MethodAnnotationsScanner ;
21
+ import org .reflections .scanners .SubTypesScanner ;
22
+ import org .reflections .scanners .TypeAnnotationsScanner ;
23
+ import org .springframework .boot .autoconfigure .condition .ConditionalOnMissingBean ;
24
+ import org .springframework .boot .context .properties .EnableConfigurationProperties ;
25
+ import org .springframework .context .annotation .Bean ;
26
+ import org .springframework .context .annotation .Configuration ;
27
+
28
+ import java .lang .annotation .Annotation ;
29
+ import java .lang .reflect .Method ;
30
+ import java .util .Collections ;
31
+ import java .util .List ;
32
+ import java .util .Optional ;
33
+ import java .util .Set ;
34
+
35
+ import static graphql .annotations .AnnotationsSchemaCreator .newAnnotationsSchema ;
36
+
37
+ @ Configuration
38
+ @ EnableConfigurationProperties (GraphQLAnnotationsProperties .class )
39
+ @ RequiredArgsConstructor
40
+ @ Slf4j
41
+ public class GraphQLAnnotationsAutoConfiguration {
42
+
43
+ private final GraphQLAnnotationsProperties graphQLAnnotationsProperties ;
44
+ private final Optional <Relay > relay ;
45
+ private final List <TypeFunction > typeFunctions ;
46
+ private final List <GraphQLScalarType > customScalarTypes ;
47
+
48
+ @ Bean
49
+ public GraphQLInterfaceTypeResolver graphQLInterfaceTypeResolver () {
50
+ return new GraphQLInterfaceTypeResolver ();
51
+ }
52
+
53
+ @ Bean
54
+ @ ConditionalOnMissingBean
55
+ public GraphQLAnnotations graphQLAnnotations () {
56
+ return new GraphQLAnnotations ();
57
+ }
58
+
59
+ @ Bean
60
+ public GraphQLSchema graphQLSchema (final GraphQLAnnotations graphQLAnnotations ) {
61
+ log .info ("Using GraphQL Annotations library to build the schema. Schema definition files will be ignored." );
62
+ log .info ("GraphQL classes are searched in the following package (including subpackages): {}" ,
63
+ graphQLAnnotationsProperties .getBasePackage ());
64
+ final AnnotationsSchemaCreator .Builder builder = newAnnotationsSchema ();
65
+ final Reflections reflections = new Reflections (graphQLAnnotationsProperties .getBasePackage (),
66
+ new MethodAnnotationsScanner (), new SubTypesScanner (), new TypeAnnotationsScanner ());
67
+ builder .setAlwaysPrettify (graphQLAnnotationsProperties .isAlwaysPrettify ());
68
+ setQueryResolverClass (builder , reflections );
69
+ setMutationResolverClass (builder , reflections );
70
+ setSubscriptionResolverClass (builder , reflections );
71
+ getTypesAnnotatedWith (reflections , GraphQLDirectiveDefinition .class ).forEach (directive -> {
72
+ log .info ("Registering directive {}" , directive );
73
+ builder .directive (directive );
74
+ });
75
+ getTypesAnnotatedWith (reflections , GraphQLTypeExtension .class ).forEach (typeExtension -> {
76
+ log .info ("Registering type extension {}" , typeExtension );
77
+ builder .typeExtension (typeExtension );
78
+ });
79
+ typeFunctions .forEach (typeFunction -> {
80
+ log .info ("Registering type function {}" , typeFunction .getClass ());
81
+ builder .typeFunction (typeFunction );
82
+ });
83
+ if (!customScalarTypes .isEmpty ()) {
84
+ builder .typeFunction (new GraphQLScalarTypeFunction (customScalarTypes ));
85
+ }
86
+ if (graphQLAnnotations .getClass ().equals (GraphQLAnnotations .class )) {
87
+ log .info ("Using default GraphQL Annotation processor." );
88
+ } else {
89
+ log .info ("Using custom annotation process of type {}" , graphQLAnnotations .getClass ());
90
+ }
91
+ builder .setAnnotationsProcessor (graphQLAnnotations );
92
+ relay .ifPresent (r -> {
93
+ log .info ("Registering relay {}" , r .getClass ());
94
+ builder .setRelay (r );
95
+ });
96
+ registerGraphQLInterfaceImplementations (reflections , builder );
97
+ return builder .build ();
98
+ }
99
+
100
+ private void setSubscriptionResolverClass (
101
+ final AnnotationsSchemaCreator .Builder builder ,
102
+ final Reflections reflections
103
+ ) {
104
+ final Set <Class <?>> subscriptionResolvers
105
+ = getTypesAnnotatedWith (reflections , GraphQLSubscriptionResolver .class );
106
+ if (subscriptionResolvers .size () > 1 ) {
107
+ throw new MultipleSubscriptionResolversException ();
108
+ }
109
+ subscriptionResolvers .stream ().findFirst ().ifPresent (subscriptionClass -> {
110
+ log .info ("Registering subscription resolver class: {}" , subscriptionClass );
111
+ builder .subscription (subscriptionClass );
112
+ });
113
+ }
114
+
115
+ private void setMutationResolverClass (
116
+ final AnnotationsSchemaCreator .Builder builder ,
117
+ final Reflections reflections
118
+ ) {
119
+ final Set <Class <?>> mutationResolvers
120
+ = getTypesAnnotatedWith (reflections , GraphQLMutationResolver .class );
121
+ if (mutationResolvers .size () > 1 ) {
122
+ throw new MultipleMutationResolversException ();
123
+ }
124
+ mutationResolvers .stream ().findFirst ().ifPresent (mutationClass -> {
125
+ log .info ("Registering mutation resolver class: {}" , mutationClass );
126
+ builder .mutation (mutationClass );
127
+ });
128
+ }
129
+
130
+ private void setQueryResolverClass (
131
+ final AnnotationsSchemaCreator .Builder builder ,
132
+ final Reflections reflections
133
+ ) {
134
+ final Set <Class <?>> queryResolvers
135
+ = getTypesAnnotatedWith (reflections , GraphQLQueryResolver .class );
136
+ if (queryResolvers .size () == 0 ) {
137
+ throw new MissingQueryResolverException ();
138
+ }
139
+ if (queryResolvers .size () > 1 ) {
140
+ throw new MultipleQueryResolversException ();
141
+ }
142
+ queryResolvers .stream ().findFirst ().ifPresent (queryClass -> {
143
+ log .info ("Registering query resolver class: {}" , queryClass );
144
+ builder .query (queryClass );
145
+ });
146
+ }
147
+
148
+ /**
149
+ * Workaround for a bug in Reflections - {@link Reflections#getTypesAnnotatedWith)} will throw a
150
+ * {@link ReflectionsException} if there are no types with annotations in the specified package.
151
+ * @param reflections the {@link Reflections} instance
152
+ * @param annotation the annotation class
153
+ * @return The set of classes annotated with the specified annotation, or an empty set if no annotated classes
154
+ * found.
155
+ */
156
+ private Set <Class <?>> getTypesAnnotatedWith (
157
+ final Reflections reflections ,
158
+ final Class <? extends Annotation > annotation
159
+ ) {
160
+ try {
161
+ return reflections .getTypesAnnotatedWith (annotation );
162
+ } catch (ReflectionsException e ) {
163
+ return Collections .emptySet ();
164
+ }
165
+ }
166
+
167
+ /**
168
+ * This is required, because normally implementations of interfaces are not explicitly returned by any resolver
169
+ * method, and therefor not added to the schema automatically.
170
+ *
171
+ * All interfaces are considered GraphQL interfaces if they are declared in the configured package and
172
+ * have at least one {@link GraphQLField}-annotated methods.
173
+ *
174
+ * @param reflections the reflections instance.
175
+ * @param builder the schema builder instance.
176
+ */
177
+ private void registerGraphQLInterfaceImplementations (
178
+ final Reflections reflections ,
179
+ final AnnotationsSchemaCreator .Builder builder
180
+ ) {
181
+ reflections .getMethodsAnnotatedWith (GraphQLField .class ).stream ()
182
+ .map (Method ::getDeclaringClass )
183
+ .filter (Class ::isInterface )
184
+ .forEach (graphQLInterface ->
185
+ reflections .getSubTypesOf (graphQLInterface )
186
+ .forEach (implementation -> {
187
+ log .info ("Registering {} as an implementation of GraphQL interface {}" , implementation ,
188
+ graphQLInterface );
189
+ builder .additionalType (implementation );
190
+ }));
191
+ }
192
+ }
0 commit comments