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)