From 629a55ae9340165ecddf443e01537d0dc027d410 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Vajner?= Date: Tue, 5 Oct 2021 21:30:31 +0200 Subject: [PATCH] fix(#726): fix GraphiQL configuration There was some inconsistency in configuration handling. The `GraphiQLProperties` were not used for headers and GraphiQL props. Instead, the properties were manually loaded. This part, however, was not updated with the starter reorganization, and expected the variables in the old place (`graphiql.props.variables.`). This commit fixes this by using the props / headers from the configuration properties. --- README.md | 47 ++++++------- .../editor/PropertyGroupReader.java | 67 ------------------- .../autoconfigure/editor/PropsLoader.java | 51 -------------- .../graphiql/GraphiQLAutoConfiguration.java | 8 +-- .../editor/graphiql/GraphiQLController.java | 59 ++++++++++------ .../editor/graphiql/GraphiQLProperties.java | 32 ++++++--- .../graphiql/ReactiveGraphiQLController.java | 4 ++ .../graphiql/ServletGraphiQLController.java | 4 ++ .../main/resources/templates/graphiql.html | 4 +- 9 files changed, 99 insertions(+), 177 deletions(-) delete mode 100644 graphql-spring-boot-autoconfigure/src/main/java/graphql/kickstart/autoconfigure/editor/PropertyGroupReader.java delete mode 100644 graphql-spring-boot-autoconfigure/src/main/java/graphql/kickstart/autoconfigure/editor/PropsLoader.java diff --git a/README.md b/README.md index 78b6254e..fd103b1b 100644 --- a/README.md +++ b/README.md @@ -193,29 +193,30 @@ Available Spring Boot configuration parameters (either `application.yml` or `application.properties`): ```yaml -graphiql: - mapping: /graphiql - endpoint: - graphql: /graphql - subscriptions: /subscriptions - subscriptions: - timeout: 30 - reconnect: false - basePath: / - enabled: true - pageTitle: GraphiQL - cdn: - enabled: false - version: latest - props: - resources: - query: query.graphql - defaultQuery: defaultQuery.graphql - variables: variables.graphql - variables: - editorTheme: "solarized light" - headers: - Authorization: "Bearer " +graphql: + graphiql: + mapping: /graphiql + endpoint: + graphql: /graphql + subscriptions: /subscriptions + subscriptions: + timeout: 30 + reconnect: false + basePath: / + enabled: true + pageTitle: GraphiQL + cdn: + enabled: false + version: latest + props: + resources: + query: query.graphql + defaultQuery: defaultQuery.graphql + variables: variables.json + variables: + editorTheme: "solarized light" + headers: + Authorization: "Bearer " ``` By default GraphiQL is served from within the package. This can be configured to be served from CDN diff --git a/graphql-spring-boot-autoconfigure/src/main/java/graphql/kickstart/autoconfigure/editor/PropertyGroupReader.java b/graphql-spring-boot-autoconfigure/src/main/java/graphql/kickstart/autoconfigure/editor/PropertyGroupReader.java deleted file mode 100644 index deb3ad66..00000000 --- a/graphql-spring-boot-autoconfigure/src/main/java/graphql/kickstart/autoconfigure/editor/PropertyGroupReader.java +++ /dev/null @@ -1,67 +0,0 @@ -package graphql.kickstart.autoconfigure.editor; - -import java.util.Arrays; -import java.util.Iterator; -import java.util.Objects; -import java.util.Optional; -import java.util.Properties; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; -import org.springframework.core.env.ConfigurableEnvironment; -import org.springframework.core.env.EnumerablePropertySource; -import org.springframework.core.env.Environment; -import org.springframework.core.env.PropertySource; - -public class PropertyGroupReader { - - private final Environment environment; - private final String prefix; - private Properties props; - - public PropertyGroupReader(Environment environment, String prefix) { - this.environment = Objects.requireNonNull(environment); - this.prefix = Optional.ofNullable(prefix).orElse(""); - } - - public Properties load() { - if (props == null) { - props = new Properties(); - loadProps(); - } - return props; - } - - private void loadProps() { - streamOfPropertySources() - .forEach( - propertySource -> - Arrays.stream(propertySource.getPropertyNames()) - .filter(this::isWanted) - .forEach(key -> add(propertySource, key))); - } - - @SuppressWarnings("unchecked") - private Stream> streamOfPropertySources() { - if (environment instanceof ConfigurableEnvironment) { - Iterator> iterator = - ((ConfigurableEnvironment) environment).getPropertySources().iterator(); - Iterable> iterable = () -> iterator; - return StreamSupport.stream(iterable.spliterator(), false) - .filter(EnumerablePropertySource.class::isInstance) - .map(EnumerablePropertySource.class::cast); - } - return Stream.empty(); - } - - private String withoutPrefix(String key) { - return key.replace(prefix, ""); - } - - private boolean isWanted(String key) { - return key.startsWith(prefix); - } - - private void add(EnumerablePropertySource propertySource, String key) { - props.put(withoutPrefix(key), propertySource.getProperty(key)); - } -} diff --git a/graphql-spring-boot-autoconfigure/src/main/java/graphql/kickstart/autoconfigure/editor/PropsLoader.java b/graphql-spring-boot-autoconfigure/src/main/java/graphql/kickstart/autoconfigure/editor/PropsLoader.java deleted file mode 100644 index 9d7ff017..00000000 --- a/graphql-spring-boot-autoconfigure/src/main/java/graphql/kickstart/autoconfigure/editor/PropsLoader.java +++ /dev/null @@ -1,51 +0,0 @@ -package graphql.kickstart.autoconfigure.editor; - -import com.fasterxml.jackson.databind.ObjectMapper; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.util.Optional; -import java.util.Properties; -import lombok.SneakyThrows; -import org.springframework.core.env.Environment; -import org.springframework.core.io.ClassPathResource; -import org.springframework.core.io.Resource; -import org.springframework.util.StreamUtils; - -public class PropsLoader { - - private final Environment environment; - private final String resourcesPrefix; - private final String valuesPrefix; - - public PropsLoader(Environment environment, String resourcesPrefix, String valuesPrefix) { - this.environment = environment; - this.resourcesPrefix = resourcesPrefix; - this.valuesPrefix = valuesPrefix; - } - - public String load() throws IOException { - PropertyGroupReader reader = new PropertyGroupReader(environment, valuesPrefix); - Properties props = reader.load(); - - ObjectMapper objectMapper = new ObjectMapper(); - loadPropFromResource("defaultQuery").ifPresent(it -> props.put("defaultQuery", it)); - loadPropFromResource("query").ifPresent(it -> props.put("query", it)); - loadPropFromResource("variables").ifPresent(it -> props.put("variables", it)); - return objectMapper.writeValueAsString(props); - } - - private Optional loadPropFromResource(String prop) { - String property = resourcesPrefix + prop; - return Optional.ofNullable(environment.getProperty(property)) - .map(ClassPathResource::new) - .map(this::loadResource); - } - - @SneakyThrows - private String loadResource(Resource resource) { - try (InputStream inputStream = resource.getInputStream()) { - return StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8); - } - } -} diff --git a/graphql-spring-boot-autoconfigure/src/main/java/graphql/kickstart/autoconfigure/editor/graphiql/GraphiQLAutoConfiguration.java b/graphql-spring-boot-autoconfigure/src/main/java/graphql/kickstart/autoconfigure/editor/graphiql/GraphiQLAutoConfiguration.java index 70b5fc85..32d79668 100644 --- a/graphql-spring-boot-autoconfigure/src/main/java/graphql/kickstart/autoconfigure/editor/graphiql/GraphiQLAutoConfiguration.java +++ b/graphql-spring-boot-autoconfigure/src/main/java/graphql/kickstart/autoconfigure/editor/graphiql/GraphiQLAutoConfiguration.java @@ -21,14 +21,14 @@ public class GraphiQLAutoConfiguration { @Bean(name = "graphiQLController") @ConditionalOnWebApplication(type = SERVLET) - ServletGraphiQLController servletGraphiQLController() { - return new ServletGraphiQLController(); + ServletGraphiQLController servletGraphiQLController(GraphiQLProperties properties) { + return new ServletGraphiQLController(properties); } @Bean(name = "graphiQLController") @ConditionalOnMissingBean(ServletGraphiQLController.class) @ConditionalOnWebApplication(type = REACTIVE) - ReactiveGraphiQLController reactiveGraphiQLController() { - return new ReactiveGraphiQLController(); + ReactiveGraphiQLController reactiveGraphiQLController(GraphiQLProperties properties) { + return new ReactiveGraphiQLController(properties); } } diff --git a/graphql-spring-boot-autoconfigure/src/main/java/graphql/kickstart/autoconfigure/editor/graphiql/GraphiQLController.java b/graphql-spring-boot-autoconfigure/src/main/java/graphql/kickstart/autoconfigure/editor/graphiql/GraphiQLController.java index d4c316b1..f62e8cd4 100644 --- a/graphql-spring-boot-autoconfigure/src/main/java/graphql/kickstart/autoconfigure/editor/graphiql/GraphiQLController.java +++ b/graphql-spring-boot-autoconfigure/src/main/java/graphql/kickstart/autoconfigure/editor/graphiql/GraphiQLController.java @@ -1,20 +1,24 @@ package graphql.kickstart.autoconfigure.editor.graphiql; +import static java.util.Objects.nonNull; + import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import graphql.kickstart.autoconfigure.editor.PropertyGroupReader; -import graphql.kickstart.autoconfigure.editor.PropsLoader; +import graphql.kickstart.autoconfigure.editor.graphiql.GraphiQLProperties.Props.GraphiQLVariables; +import graphql.kickstart.autoconfigure.editor.graphiql.GraphiQLProperties.Resources; import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.Properties; +import java.util.Optional; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.commons.text.StringSubstitutor; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.env.Environment; import org.springframework.core.io.ClassPathResource; import org.springframework.security.web.csrf.CsrfToken; import org.springframework.util.StreamUtils; @@ -23,6 +27,7 @@ /** @author Andrew Potter */ @Slf4j +@RequiredArgsConstructor public abstract class GraphiQLController { private static final String CDNJS_CLOUDFLARE_COM_AJAX_LIBS = "//cdnjs.cloudflare.com/ajax/libs/"; @@ -30,18 +35,14 @@ public abstract class GraphiQLController { private static final String GRAPHIQL = "graphiql"; private static final String FAVICON_GRAPHQL_ORG = "//graphql.org/img/favicon.png"; - @Autowired private Environment environment; - - @Autowired private GraphiQLProperties graphiQLProperties; + private final GraphiQLProperties graphiQLProperties; private String template; private String props; - private Properties headerProperties; public void onceConstructed() throws IOException { loadTemplate(); loadProps(); - loadHeaders(); } private void loadTemplate() throws IOException { @@ -52,35 +53,45 @@ private void loadTemplate() throws IOException { } private void loadProps() throws IOException { - props = - new PropsLoader(environment, "graphiql.props.resources.", "graphiql.props.variables.") - .load(); - } - - private void loadHeaders() { - PropertyGroupReader propertyReader = new PropertyGroupReader(environment, "graphiql.headers."); - headerProperties = propertyReader.load(); + Resources resources = graphiQLProperties.getProps().getResources(); + GraphiQLVariables combinedVariables = graphiQLProperties.getProps().getVariables(); + if (nonNull(resources.getVariables())) { + combinedVariables = combinedVariables.withVariables(getContent(resources.getVariables())); + } + if (nonNull(resources.getDefaultQuery())) { + combinedVariables + = combinedVariables.withDefaultQuery(getContent(resources.getDefaultQuery())); + } + if (nonNull(resources.getQuery())) { + combinedVariables = combinedVariables.withQuery(getContent(resources.getQuery())); + } + this.props = new ObjectMapper().writeValueAsString(combinedVariables); } public byte[] graphiql( String contextPath, @PathVariable Map params, Object csrf) { + Map finalHeaders = Optional.ofNullable(graphiQLProperties.getHeaders()) + .orElseGet(Collections::emptyMap); if (csrf != null) { CsrfToken csrfToken = (CsrfToken) csrf; - headerProperties.setProperty(csrfToken.getHeaderName(), csrfToken.getToken()); + finalHeaders = new HashMap<>(finalHeaders); + finalHeaders.put(csrfToken.getHeaderName(), csrfToken.getToken()); } Map replacements = getReplacements( constructGraphQlEndpoint(contextPath, params), contextPath + graphiQLProperties.getEndpoint().getSubscriptions(), - contextPath + graphiQLProperties.getBasePath()); + contextPath + graphiQLProperties.getBasePath(), + finalHeaders); String populatedTemplate = StringSubstitutor.replace(template, replacements); return populatedTemplate.getBytes(Charset.defaultCharset()); } private Map getReplacements( - String graphqlEndpoint, String subscriptionsEndpoint, String staticBasePath) { + String graphqlEndpoint, String subscriptionsEndpoint, String staticBasePath, + Map headers) { Map replacements = new HashMap<>(); replacements.put("graphqlEndpoint", graphqlEndpoint); replacements.put("subscriptionsEndpoint", subscriptionsEndpoint); @@ -137,7 +148,7 @@ private Map getReplacements( joinJsDelivrPath("graphiql-subscriptions-fetcher", "0.0.2", "browser/client.js"))); replacements.put("props", props); try { - replacements.put("headers", new ObjectMapper().writeValueAsString(headerProperties)); + replacements.put("headers", new ObjectMapper().writeValueAsString(headers)); } catch (JsonProcessingException e) { log.error("Cannot serialize headers", e); } @@ -191,4 +202,8 @@ private String constructGraphQlEndpoint( } return endpoint; } + + private String getContent(final ClassPathResource resource) throws IOException { + return new String(Files.readAllBytes(resource.getFile().toPath()), StandardCharsets.UTF_8); + } } diff --git a/graphql-spring-boot-autoconfigure/src/main/java/graphql/kickstart/autoconfigure/editor/graphiql/GraphiQLProperties.java b/graphql-spring-boot-autoconfigure/src/main/java/graphql/kickstart/autoconfigure/editor/graphiql/GraphiQLProperties.java index dd51ecbe..5c5e4b8a 100644 --- a/graphql-spring-boot-autoconfigure/src/main/java/graphql/kickstart/autoconfigure/editor/graphiql/GraphiQLProperties.java +++ b/graphql-spring-boot-autoconfigure/src/main/java/graphql/kickstart/autoconfigure/editor/graphiql/GraphiQLProperties.java @@ -2,13 +2,18 @@ import java.time.Duration; import java.time.temporal.ChronoUnit; +import java.util.Map; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.With; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.convert.DurationUnit; +import org.springframework.core.io.ClassPathResource; @Data @ConfigurationProperties("graphql.graphiql") -class GraphiQLProperties { +public class GraphiQLProperties { private boolean enabled = false; private Endpoint endpoint = new Endpoint(); @@ -19,32 +24,43 @@ class GraphiQLProperties { private Subscriptions subscriptions = new Subscriptions(); private Cdn cdn = new Cdn(); private String basePath = "/"; + private Map headers; @Data - static class Endpoint { + public static class Endpoint { private String graphql = "/graphql"; private String subscriptions = "/subscriptions"; } @Data - static class CodeMirror { + public static class CodeMirror { private String version = "5.47.0"; } @Data - static class Props { + public static class Resources { + private ClassPathResource query; + private ClassPathResource variables; + private ClassPathResource defaultQuery; + } + + @Data + public static class Props { private GraphiQLVariables variables = new GraphiQLVariables(); + private Resources resources = new Resources(); /** See https://github.com/graphql/graphiql/tree/main/packages/graphiql#props */ @Data - static class GraphiQLVariables { + @With + @AllArgsConstructor + @NoArgsConstructor + public static class GraphiQLVariables { private String query; private String variables; - private String headers; private String operationName; private String response; private String defaultQuery; @@ -59,14 +75,14 @@ static class GraphiQLVariables { } @Data - static class Cdn { + public static class Cdn { private boolean enabled = false; private String version = "1.0.6"; } @Data - static class Subscriptions { + public static class Subscriptions { /** * Subscription timeout. If a duration suffix is not specified, second will be used. diff --git a/graphql-spring-boot-autoconfigure/src/main/java/graphql/kickstart/autoconfigure/editor/graphiql/ReactiveGraphiQLController.java b/graphql-spring-boot-autoconfigure/src/main/java/graphql/kickstart/autoconfigure/editor/graphiql/ReactiveGraphiQLController.java index d65060c7..4244c6e3 100644 --- a/graphql-spring-boot-autoconfigure/src/main/java/graphql/kickstart/autoconfigure/editor/graphiql/ReactiveGraphiQLController.java +++ b/graphql-spring-boot-autoconfigure/src/main/java/graphql/kickstart/autoconfigure/editor/graphiql/ReactiveGraphiQLController.java @@ -20,6 +20,10 @@ public class ReactiveGraphiQLController extends GraphiQLController { private final DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory(); + public ReactiveGraphiQLController( GraphiQLProperties graphiQLProperties) { + super(graphiQLProperties); + } + @Override @PostConstruct public void onceConstructed() throws IOException { diff --git a/graphql-spring-boot-autoconfigure/src/main/java/graphql/kickstart/autoconfigure/editor/graphiql/ServletGraphiQLController.java b/graphql-spring-boot-autoconfigure/src/main/java/graphql/kickstart/autoconfigure/editor/graphiql/ServletGraphiQLController.java index 88a6424b..085a421f 100644 --- a/graphql-spring-boot-autoconfigure/src/main/java/graphql/kickstart/autoconfigure/editor/graphiql/ServletGraphiQLController.java +++ b/graphql-spring-boot-autoconfigure/src/main/java/graphql/kickstart/autoconfigure/editor/graphiql/ServletGraphiQLController.java @@ -15,6 +15,10 @@ @Controller public class ServletGraphiQLController extends GraphiQLController { + public ServletGraphiQLController(GraphiQLProperties graphiQLProperties) { + super(graphiQLProperties); + } + @Override @PostConstruct public void onceConstructed() throws IOException { diff --git a/graphql-spring-boot-autoconfigure/src/main/resources/templates/graphiql.html b/graphql-spring-boot-autoconfigure/src/main/resources/templates/graphiql.html index 3979f3f4..d4fe5d24 100644 --- a/graphql-spring-boot-autoconfigure/src/main/resources/templates/graphiql.html +++ b/graphql-spring-boot-autoconfigure/src/main/resources/templates/graphiql.html @@ -173,9 +173,9 @@ props.onEditVariables = onEditVariables props.onEditOperationName = onEditOperationName props.onEditHeaders = onEditHeaders - props.headers = props.headers || '{}' + props.headers = props.headers || {} if (headers) { - var newHeaders = Object.assign({}, JSON.parse(props.headers), headers) + var newHeaders = Object.assign({}, props.headers, headers) props.headers = JSON.stringify(newHeaders, undefined, 2) } onEditHeaders(props.headers)