1+ package graphql .kickstart .graphql .annotations ;
2+
3+ import graphql .annotations .AnnotationsSchemaCreator ;
4+ import graphql .annotations .annotationTypes .GraphQLTypeExtension ;
5+ import graphql .annotations .annotationTypes .directives .definition .GraphQLDirectiveDefinition ;
6+ import graphql .annotations .processor .GraphQLAnnotations ;
7+ import graphql .annotations .processor .typeFunctions .TypeFunction ;
8+ import graphql .kickstart .graphql .annotations .exceptions .MissingQueryResolverException ;
9+ import graphql .kickstart .graphql .annotations .exceptions .MultipleMutationResolversException ;
10+ import graphql .kickstart .graphql .annotations .exceptions .MultipleQueryResolversException ;
11+ import graphql .kickstart .graphql .annotations .exceptions .MultipleSubscriptionResolversException ;
12+ import graphql .kickstart .tools .GraphQLMutationResolver ;
13+ import graphql .kickstart .tools .GraphQLQueryResolver ;
14+ import graphql .kickstart .tools .GraphQLSubscriptionResolver ;
15+ import graphql .kickstart .tools .boot .GraphQLJavaToolsAutoConfiguration ;
16+ import graphql .relay .Relay ;
17+ import graphql .schema .GraphQLScalarType ;
18+ import graphql .schema .GraphQLSchema ;
19+ import lombok .RequiredArgsConstructor ;
20+ import lombok .extern .slf4j .Slf4j ;
21+ import org .reflections .Reflections ;
22+ import org .reflections .ReflectionsException ;
23+ import org .springframework .boot .autoconfigure .AutoConfigureBefore ;
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 .util .Collections ;
30+ import java .util .List ;
31+ import java .util .Optional ;
32+ import java .util .Set ;
33+
34+ import static graphql .annotations .AnnotationsSchemaCreator .newAnnotationsSchema ;
35+ import static java .util .Objects .isNull ;
36+
37+ @ Configuration
38+ @ AutoConfigureBefore ({GraphQLJavaToolsAutoConfiguration .class })
39+ @ EnableConfigurationProperties (GraphQLAnnotationsProperties .class )
40+ @ RequiredArgsConstructor
41+ @ Slf4j
42+ public class GraphQLAnnotationsAutoConfiguration {
43+
44+ private final GraphQLAnnotationsProperties graphQLAnnotationsProperties ;
45+ private final Optional <Relay > relay ;
46+ private final Optional <GraphQLAnnotations > customAnnotationProcessor ;
47+ private final List <TypeFunction > typeFunctions ;
48+ private final List <GraphQLScalarType > customScalarTypes ;
49+
50+ @ Bean
51+ public GraphQLSchema graphQLSchema () {
52+ log .info ("Using GraphQL Annotations library to build the schema. Schema definition files will be ignored." );
53+ log .info ("GraphQL classes are searched in the following package (including subpackages): {}" ,
54+ graphQLAnnotationsProperties .getBasePackage ());
55+ final AnnotationsSchemaCreator .Builder builder = newAnnotationsSchema ();
56+ final Reflections reflections = new Reflections (graphQLAnnotationsProperties .getBasePackage ());
57+ builder .setAlwaysPrettify (graphQLAnnotationsProperties .isAlwaysPrettify ());
58+ setQueryResolverClass (builder , reflections );
59+ setMutationResolverClass (builder , reflections );
60+ setSubscriptionResolverClass (builder , reflections );
61+ getTypesAnnotatedWith (reflections , GraphQLDirectiveDefinition .class ).forEach (directive -> {
62+ log .info ("Registering directive {}" , directive );
63+ builder .directive (directive );
64+ });
65+ getTypesAnnotatedWith (reflections , GraphQLTypeExtension .class ).forEach (typeExtension -> {
66+ log .info ("Registering type extension {}" , typeExtension );
67+ builder .typeExtension (typeExtension );
68+ });
69+ typeFunctions .forEach (typeFunction -> {
70+ log .info ("Registering type function {}" , typeFunction .getClass ());
71+ builder .typeFunction (typeFunction );
72+ });
73+ if (!customScalarTypes .isEmpty ()) {
74+ builder .typeFunction (new GraphQLScalarTypeFunction (customScalarTypes ));
75+ }
76+ customAnnotationProcessor .ifPresent (graphQLAnnotations -> {
77+ log .info ("Registering custom GraphQL annotations processor {}" , graphQLAnnotations .getClass ());
78+ builder .setAnnotationsProcessor (graphQLAnnotations );
79+ });
80+ if (isNull (builder .getGraphQLAnnotations ())) {
81+ // before setting a relay, we have to set the annotation processor, otherwise a NPE will occur
82+ builder .setAnnotationsProcessor (new GraphQLAnnotations ());
83+ }
84+ relay .ifPresent (r -> {
85+ log .info ("Registering relay {}" , r .getClass ());
86+ builder .setRelay (r );
87+ });
88+ return builder .build ();
89+ }
90+
91+ private void setSubscriptionResolverClass (
92+ final AnnotationsSchemaCreator .Builder builder ,
93+ final Reflections reflections
94+ ) {
95+ final Set <Class <? extends GraphQLSubscriptionResolver >> subscriptionResolvers
96+ = getSubTypesOf (reflections , GraphQLSubscriptionResolver .class );
97+ if (subscriptionResolvers .size () > 1 ) {
98+ throw new MultipleSubscriptionResolversException ();
99+ }
100+ subscriptionResolvers .stream ().findFirst ().ifPresent (subscriptionClass -> {
101+ log .info ("Registering subscription resolver class: {}" , subscriptionClass );
102+ builder .subscription (subscriptionClass );
103+ });
104+ }
105+
106+ private void setMutationResolverClass (
107+ final AnnotationsSchemaCreator .Builder builder ,
108+ final Reflections reflections
109+ ) {
110+ final Set <Class <? extends GraphQLMutationResolver >> mutationResolvers
111+ = getSubTypesOf (reflections , GraphQLMutationResolver .class );
112+ if (mutationResolvers .size () > 1 ) {
113+ throw new MultipleMutationResolversException ();
114+ }
115+ mutationResolvers .stream ().findFirst ().ifPresent (mutationClass -> {
116+ log .info ("Registering mutation resolver class: {}" , mutationClass );
117+ builder .mutation (mutationClass );
118+ });
119+ }
120+
121+ private void setQueryResolverClass (
122+ final AnnotationsSchemaCreator .Builder builder ,
123+ final Reflections reflections
124+ ) {
125+ final Set <Class <? extends GraphQLQueryResolver >> queryResolvers
126+ = getSubTypesOf (reflections , GraphQLQueryResolver .class );
127+ if (queryResolvers .size () == 0 ) {
128+ throw new MissingQueryResolverException ();
129+ }
130+ if (queryResolvers .size () > 1 ) {
131+ throw new MultipleQueryResolversException ();
132+ }
133+ queryResolvers .stream ().findFirst ().ifPresent (queryClass -> {
134+ log .info ("Registering query resolver class: {}" , queryClass );
135+ builder .query (queryClass );
136+ });
137+ }
138+
139+ /**
140+ * Workaround for a bug in Reflections - {@link Reflections#getTypesAnnotatedWith)} will throw a
141+ * {@link ReflectionsException} if there are no types with annotations in the specified package.
142+ * @param reflections the {@link Reflections} instance
143+ * @param annotation the annotation class
144+ * @return The set of classes annotated with that annotations, or empty set if no annotations found.
145+ */
146+ private Set <Class <?>> getTypesAnnotatedWith (
147+ final Reflections reflections ,
148+ final Class <? extends Annotation > annotation
149+ ) {
150+ try {
151+ return reflections .getTypesAnnotatedWith (annotation );
152+ } catch (ReflectionsException e ) {
153+ return Collections .emptySet ();
154+ }
155+ }
156+
157+ /**
158+ * Workaround for a bug in Reflections - {@link Reflections#getSubTypesOf(Class)} )} will throw a
159+ * {@link ReflectionsException} if there are no class in the specified package.
160+ * @param reflections the {@link Reflections} instance
161+ * @param aClass a class
162+ * @return The set of classes that are subclasses of the specified class, or empty set if no annotations found.
163+ * @see <a href="https://github.com/ronmamo/reflections/issues/273">Issue #273</>
164+ */
165+ private <T > Set <Class <? extends T >> getSubTypesOf (
166+ final Reflections reflections ,
167+ final Class <T > aClass
168+ ) {
169+ try {
170+ return reflections .getSubTypesOf (aClass );
171+ } catch (ReflectionsException e ) {
172+ return Collections .emptySet ();
173+ }
174+ }
175+ }
0 commit comments