Skip to content

Commit 087e853

Browse files
committed
Refine GraphQL server auto-configuration
Prior to this commit, launching a GraphQL application without any schema file or customizer bean would result in an exception caught by a FailureAnalyzer telling the developer about configured locations. Since then, a new client has been introduced in Spring GraphQL and the mere presence of the GraphQL starter does not mean anymore that the intent is to create a GraphQL API in the app: we could instead just consume an existing, remote API. This commit refines the GraphQL server auto-configuration so that it is enabled only if: * there is at least one schema file in the configured locations * or a `GraphQlSourceCustomizer` bean has been defined in the app These changes make the custom FailureAnalyzer useless and is also removed as part of this commit. Closes gh-30035
1 parent bf79d6b commit 087e853

File tree

10 files changed

+259
-297
lines changed

10 files changed

+259
-297
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2012-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.graphql;
18+
19+
import java.lang.annotation.Documented;
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Retention;
22+
import java.lang.annotation.RetentionPolicy;
23+
import java.lang.annotation.Target;
24+
25+
import org.springframework.context.annotation.Conditional;
26+
27+
/**
28+
* {@link Conditional @Conditional} that only matches when a GraphQL schema is defined for
29+
* the application, via schema files or infrastructure beans.
30+
*
31+
* @author Brian Clozel
32+
* @since 2.7.0
33+
*/
34+
@Target({ ElementType.TYPE, ElementType.METHOD })
35+
@Retention(RetentionPolicy.RUNTIME)
36+
@Documented
37+
@Conditional(DefaultGraphQlSchemaCondition.class)
38+
public @interface ConditionalOnGraphQlSchema {
39+
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*
2+
* Copyright 2012-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.graphql;
18+
19+
import java.io.IOException;
20+
import java.util.ArrayList;
21+
import java.util.Arrays;
22+
import java.util.Collections;
23+
import java.util.List;
24+
25+
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
26+
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
27+
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
28+
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
29+
import org.springframework.boot.context.properties.bind.Binder;
30+
import org.springframework.context.annotation.Condition;
31+
import org.springframework.context.annotation.ConditionContext;
32+
import org.springframework.context.annotation.ConfigurationCondition;
33+
import org.springframework.core.io.Resource;
34+
import org.springframework.core.io.support.ResourcePatternResolver;
35+
import org.springframework.core.io.support.ResourcePatternUtils;
36+
import org.springframework.core.type.AnnotatedTypeMetadata;
37+
38+
/**
39+
* {@link Condition} that checks whether a GraphQL schema has been defined in the
40+
* application. This is looking for:
41+
* <ul>
42+
* <li>schema files in the {@link GraphQlProperties configured locations}</li>
43+
* <li>or infrastructure beans such as {@link GraphQlSourceBuilderCustomizer}</li>
44+
* </ul>
45+
*
46+
* @author Brian Clozel
47+
* @see ConditionalOnGraphQlSchema
48+
*/
49+
class DefaultGraphQlSchemaCondition extends SpringBootCondition implements ConfigurationCondition {
50+
51+
@Override
52+
public ConfigurationCondition.ConfigurationPhase getConfigurationPhase() {
53+
return ConfigurationCondition.ConfigurationPhase.REGISTER_BEAN;
54+
}
55+
56+
@Override
57+
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
58+
boolean match = false;
59+
List<ConditionMessage> messages = new ArrayList<>(2);
60+
ConditionMessage.Builder message = ConditionMessage.forCondition(ConditionalOnGraphQlSchema.class);
61+
62+
Binder binder = Binder.get(context.getEnvironment());
63+
GraphQlProperties.Schema schema = binder.bind("spring.graphql.schema", GraphQlProperties.Schema.class)
64+
.orElse(new GraphQlProperties.Schema());
65+
ResourcePatternResolver resourcePatternResolver = ResourcePatternUtils
66+
.getResourcePatternResolver(context.getResourceLoader());
67+
List<Resource> schemaResources = resolveSchemaResources(resourcePatternResolver, schema.getLocations(),
68+
schema.getFileExtensions());
69+
if (!schemaResources.isEmpty()) {
70+
match = true;
71+
messages.add(message.found("schema", "schemas").items(ConditionMessage.Style.QUOTE, schemaResources));
72+
}
73+
else {
74+
messages.add(message.didNotFind("schema files in locations").items(ConditionMessage.Style.QUOTE,
75+
Arrays.asList(schema.getLocations())));
76+
}
77+
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
78+
String[] customizerBeans = beanFactory.getBeanNamesForType(GraphQlSourceBuilderCustomizer.class, false, false);
79+
if (customizerBeans.length != 0) {
80+
match = true;
81+
messages.add(message.found("customizer", "customizers").items(Arrays.asList(customizerBeans)));
82+
}
83+
else {
84+
messages.add((message.didNotFind("GraphQlSourceBuilderCustomizer").atAll()));
85+
}
86+
return new ConditionOutcome(match, ConditionMessage.of(messages));
87+
}
88+
89+
private List<Resource> resolveSchemaResources(ResourcePatternResolver resolver, String[] locations,
90+
String[] extensions) {
91+
List<Resource> resources = new ArrayList<>();
92+
for (String location : locations) {
93+
for (String extension : extensions) {
94+
resources.addAll(resolveSchemaResources(resolver, location + "*" + extension));
95+
}
96+
}
97+
return resources;
98+
}
99+
100+
private List<Resource> resolveSchemaResources(ResourcePatternResolver resolver, String pattern) {
101+
try {
102+
return Arrays.asList(resolver.getResources(pattern));
103+
}
104+
catch (IOException ex) {
105+
return Collections.emptyList();
106+
}
107+
}
108+
109+
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/GraphQlAutoConfiguration.java

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@
4848
import org.springframework.graphql.execution.DefaultBatchLoaderRegistry;
4949
import org.springframework.graphql.execution.ExecutionGraphQlService;
5050
import org.springframework.graphql.execution.GraphQlSource;
51-
import org.springframework.graphql.execution.MissingSchemaException;
5251
import org.springframework.graphql.execution.RuntimeWiringConfigurer;
5352

5453
/**
@@ -60,6 +59,7 @@
6059
*/
6160
@AutoConfiguration
6261
@ConditionalOnClass({ GraphQL.class, GraphQlSource.class })
62+
@ConditionalOnGraphQlSchema
6363
@EnableConfigurationProperties(GraphQlProperties.class)
6464
public class GraphQlAutoConfiguration {
6565

@@ -87,12 +87,7 @@ public GraphQlSource graphQlSource(ResourcePatternResolver resourcePatternResolv
8787
}
8888
wiringConfigurers.orderedStream().forEach(builder::configureRuntimeWiring);
8989
sourceCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
90-
try {
91-
return builder.build();
92-
}
93-
catch (MissingSchemaException ex) {
94-
throw new InvalidSchemaLocationsException(schemaLocations, resourcePatternResolver, ex);
95-
}
90+
return builder.build();
9691
}
9792

9893
private Builder enableIntrospection(Builder wiring) {

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/InvalidSchemaLocationsException.java

Lines changed: 0 additions & 101 deletions
This file was deleted.

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/graphql/InvalidSchemaLocationsExceptionFailureAnalyzer.java

Lines changed: 0 additions & 42 deletions
This file was deleted.

spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ org.springframework.boot.diagnostics.FailureAnalyzer=\
2626
org.springframework.boot.autoconfigure.data.redis.RedisUrlSyntaxFailureAnalyzer,\
2727
org.springframework.boot.autoconfigure.diagnostics.analyzer.NoSuchBeanDefinitionFailureAnalyzer,\
2828
org.springframework.boot.autoconfigure.flyway.FlywayMigrationScriptMissingFailureAnalyzer,\
29-
org.springframework.boot.autoconfigure.graphql.InvalidSchemaLocationsExceptionFailureAnalyzer,\
3029
org.springframework.boot.autoconfigure.jdbc.DataSourceBeanCreationFailureAnalyzer,\
3130
org.springframework.boot.autoconfigure.jdbc.HikariDriverConfigurationFailureAnalyzer,\
3231
org.springframework.boot.autoconfigure.jooq.NoDslContextBeanFailureAnalyzer,\

0 commit comments

Comments
 (0)