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