Skip to content
This repository was archived by the owner on Dec 19, 2023. It is now read-only.

Commit 95663d4

Browse files
authored
Merge pull request #206 from Sparow199/feature/altair-starter
feat(Altair starter): Add Altair client starter
2 parents 6f08ecc + efd7236 commit 95663d4

File tree

90 files changed

+4334
-11
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

90 files changed

+4334
-11
lines changed

README.md

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,16 @@
77
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
88
**Table of Contents**
99

10-
- [Intro](#intro)
10+
- [GraphQL and Graph*i*QL Spring Framework Boot Starters](#graphql-and-graphiql-spring-framework-boot-starters)
11+
- [WARNING: NoClassDefFoundError when using GraphQL Java Tools > 5.4.x](#warning-noclassdeffounderror-when-using-graphql-java-tools--54x)
12+
- [Using Gradle](#using-gradle)
13+
- [Using Maven](#using-maven)
14+
- [Documentation](#documentation)
1115
- [Requirements and Downloads](#requirements-and-downloads)
1216
- [Enable GraphQL Servlet](#enable-graphql-servlet)
13-
- [Enable GraphiQL](#enable-graphiql)
17+
- [Enable Graph*i*QL](#enable-graphiql)
1418
- [Supported GraphQL-Java Libraries](#supported-graphql-java-libraries)
15-
- [GraphQL Java Tools](#graphql-java-tools) - [https://github.com/graphql-java-kickstart/graphql-java-tools](https://github.com/graphql-java-kickstart/graphql-java-tools)
16-
- [GraphQL Spring Common](#graphql-spring-common) - [https://github.com/oembedler/spring-graphql-common](https://github.com/oembedler/spring-graphql-common)
19+
- [GraphQL Java Tools](#graphql-java-tools)
1720
- [Contributions](#contributions)
1821
- [Licenses](#licenses)
1922

@@ -49,7 +52,9 @@ See our new [Documentation](https://www.graphql-java-kickstart.com/spring-boot/)
4952
Repository contains:
5053

5154
* `graphql-spring-boot-starter` to turn your boot application into GraphQL server (see [graphql-java-servlet](https://github.com/graphql-java-kickstart/graphql-java-servlet))
55+
* `altair-spring-boot-starter`to embed `Altair` tool for schema introspection and query debugging (see [altair](https://github.com/imolorhe/altair))
5256
* `graphiql-spring-boot-starter`to embed `GraphiQL` tool for schema introspection and query debugging (see [graphiql](https://github.com/graphql/graphiql))
57+
* `voyager-spring-boot-starter`to embed `Voyager` tool for visually explore GraphQL APIs as an interactive graph (see [voyger](https://github.com/APIs-guru/graphql-voyager))
5358

5459
# Requirements and Downloads
5560

@@ -68,6 +73,9 @@ repositories {
6873
dependencies {
6974
compile 'com.graphql-java-kickstart:graphql-spring-boot-starter:5.6.1'
7075
76+
// to embed Altair tool
77+
compile 'com.altair-java-kickstart:graphiql-spring-boot-starter:5.6.1'
78+
7179
// to embed GraphiQL tool
7280
compile 'com.graphql-java-kickstart:graphiql-spring-boot-starter:5.6.1'
7381
@@ -87,6 +95,13 @@ Maven:
8795
<version>5.6.1</version>
8896
</dependency>
8997

98+
<!-- to embed Altair tool -->
99+
<dependency>
100+
<groupId>com.graphql-java-kickstart</groupId>
101+
<artifactId>altair-spring-boot-starter</artifactId>
102+
<version>5.6.1</version>
103+
</dependency>
104+
90105
<!-- to embed GraphiQL tool -->
91106
<dependency>
92107
<groupId>com.graphql-java-kickstart</groupId>
@@ -163,13 +178,16 @@ graphiql:
163178
endpoint:
164179
graphql: /graphql
165180
subscriptions: /subscriptions
181+
subscriptions:
182+
timeout: 30
183+
reconnect: false
166184
static:
167185
basePath: /
168186
enabled: true
169187
pageTitle: GraphiQL
170188
cdn:
171189
enabled: false
172-
version: 0.11.11
190+
version: 0.13.0
173191
props:
174192
resources:
175193
query: query.graphql
@@ -220,7 +238,7 @@ Contributions are welcome. Please respect the [Code of Conduct](http://contribu
220238

221239
# Licenses
222240

223-
`graphql-spring-boot-starter` and `graphiql-spring-boot-starter` are licensed under the MIT License. See [LICENSE](LICENSE.md) for details.
241+
`graphql-spring-boot-starter`, `altair-spring-boot-starter`, `graphiql-spring-boot-starter`and `voyager-spring-boot-starter` are licensed under the MIT License. See [LICENSE](LICENSE.md) for details.
224242

225243
[graphql-java License](https://github.com/andimarek/graphql-java/blob/master/LICENSE.md)
226244

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2016 Oembedler Inc. and Contributors
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6+
7+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8+
9+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* The MIT License (MIT)
3+
*
4+
* Copyright (c) 2016 oEmbedler Inc. and Contributors
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
7+
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
8+
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
9+
* persons to whom the Software is furnished to do so, subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
12+
*
13+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
14+
* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15+
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
16+
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
17+
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
18+
*/
19+
dependencies{
20+
annotationProcessor "org.springframework.boot:spring-boot-configuration-processor:$LIB_SPRING_BOOT_VER"
21+
22+
compile "org.springframework.boot:spring-boot-autoconfigure:$LIB_SPRING_BOOT_VER"
23+
compile "org.apache.commons:commons-text:1.1"
24+
compileOnly "org.springframework.boot:spring-boot-starter-web:$LIB_SPRING_BOOT_VER"
25+
26+
testCompile "org.springframework.boot:spring-boot-starter-web:$LIB_SPRING_BOOT_VER"
27+
testCompile "org.springframework.boot:spring-boot-starter-test:$LIB_SPRING_BOOT_VER"
28+
}
29+
30+
compileJava.dependsOn(processResources)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.oembedler.moon.altair.boot;
2+
3+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
4+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
5+
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
6+
import org.springframework.context.annotation.Bean;
7+
import org.springframework.context.annotation.Configuration;
8+
import org.springframework.web.servlet.DispatcherServlet;
9+
10+
/**
11+
* @author Moncef AOUDIA
12+
*/
13+
@Configuration
14+
@ConditionalOnWebApplication
15+
@ConditionalOnClass(DispatcherServlet.class)
16+
public class AltairAutoConfiguration {
17+
@Bean
18+
@ConditionalOnProperty(value = "altair.enabled", havingValue = "true", matchIfMissing = true)
19+
AltairController altairController() {
20+
return new AltairController();
21+
}
22+
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
package com.oembedler.moon.altair.boot;
2+
3+
import com.fasterxml.jackson.core.JsonProcessingException;
4+
import com.fasterxml.jackson.databind.ObjectMapper;
5+
import org.apache.commons.lang3.StringUtils;
6+
import org.apache.commons.lang3.text.StrSubstitutor;
7+
import org.springframework.beans.factory.annotation.Autowired;
8+
import org.springframework.beans.factory.annotation.Value;
9+
import org.springframework.core.env.Environment;
10+
import org.springframework.core.io.ClassPathResource;
11+
import org.springframework.http.MediaType;
12+
import org.springframework.stereotype.Controller;
13+
import org.springframework.util.StreamUtils;
14+
import org.springframework.web.bind.annotation.PathVariable;
15+
import org.springframework.web.bind.annotation.RequestMapping;
16+
import org.springframework.web.bind.annotation.RequestParam;
17+
18+
import javax.annotation.PostConstruct;
19+
import javax.servlet.http.HttpServletRequest;
20+
import javax.servlet.http.HttpServletResponse;
21+
import java.io.IOException;
22+
import java.io.InputStream;
23+
import java.nio.charset.Charset;
24+
import java.util.HashMap;
25+
import java.util.Map;
26+
import java.util.Properties;
27+
28+
/**
29+
* @author Moncef AOUDIA
30+
*/
31+
@Controller
32+
public class AltairController {
33+
34+
private static final String CDN_UNPKG = "//unpkg.com/";
35+
private static final String ALTAIR = "altair-static";
36+
37+
@Value("${altair.endpoint.graphql:/graphql}")
38+
private String graphqlEndpoint;
39+
40+
@Value("${altair.endpoint.subscriptions:/subscriptions}")
41+
private String subscriptionsEndpoint;
42+
43+
@Value("${altair.static.basePath:/}")
44+
private String staticBasePath;
45+
46+
@Value("${altair.pageTitle:Altair}")
47+
private String pageTitle;
48+
49+
@Value("${altair.cdn.enabled:false}")
50+
private Boolean altairCdnEnabled;
51+
52+
@Value("${altair.cdn.version:2.1.1}")
53+
private String altairCdnVersion;
54+
55+
@Autowired
56+
private Environment environment;
57+
58+
private String template;
59+
private String props;
60+
61+
@PostConstruct
62+
public void onceConstructed() throws IOException {
63+
loadTemplate();
64+
loadProps();
65+
}
66+
67+
private void loadTemplate() throws IOException {
68+
try (InputStream inputStream = new ClassPathResource("altair.html").getInputStream()) {
69+
template = StreamUtils.copyToString(inputStream, Charset.defaultCharset());
70+
}
71+
}
72+
73+
private void loadProps() throws IOException {
74+
props = new PropsLoader(environment).load();
75+
}
76+
77+
private void addIfAbsent(Properties headerProperties, String header) {
78+
if (!headerProperties.containsKey(header)) {
79+
headerProperties.setProperty(header, MediaType.APPLICATION_JSON_VALUE);
80+
}
81+
}
82+
83+
@RequestMapping(value = "${altair.mapping:/altair}")
84+
public void altair(HttpServletRequest request, HttpServletResponse response, @PathVariable Map<String, String> params) throws IOException {
85+
response.setContentType("text/html; charset=UTF-8");
86+
87+
Map<String, String> replacements = getReplacements(
88+
constructGraphQlEndpoint(request, params),
89+
request.getContextPath() + subscriptionsEndpoint
90+
);
91+
92+
String populatedTemplate = StrSubstitutor.replace(template, replacements);
93+
response.getOutputStream().write(populatedTemplate.getBytes(Charset.defaultCharset()));
94+
}
95+
96+
private Map<String, String> getReplacements(String graphqlEndpoint, String subscriptionsEndpoint) {
97+
Map<String, String> replacements = new HashMap<>();
98+
replacements.put("graphqlEndpoint", graphqlEndpoint);
99+
replacements.put("subscriptionsEndpoint", subscriptionsEndpoint);
100+
replacements.put("pageTitle", pageTitle);
101+
replacements.put("pageFavicon", getResourceUrl("favicon.ico", "favicon.ico"));
102+
replacements.put("altairBaseUrl", getResourceUrl("/vendor/altair/",
103+
joinJsUnpkgPath(ALTAIR, altairCdnVersion, "build/dist/")));
104+
replacements.put("altairLogoUrl", getResourceUrl("assets/img/logo_350.svg", "assets/img/logo_350.svg"));
105+
replacements.put("altairCssUrl", getResourceUrl("styles.css", "styles.css"));
106+
replacements.put("altairMainJsUrl", getResourceUrl("main.js", "main.js"));
107+
replacements.put("altairPolyfillsJsUrl", getResourceUrl("polyfills.js", "polyfills.js"));
108+
replacements.put("altairRuntimeJsUrl", getResourceUrl("runtime.js", "runtime.js"));
109+
replacements.put("props", props);
110+
return replacements;
111+
}
112+
113+
private String getResourceUrl(String staticFileName, String cdnUrl) {
114+
if (altairCdnEnabled && StringUtils.isNotBlank(cdnUrl)) {
115+
return cdnUrl;
116+
}
117+
return staticFileName;
118+
}
119+
120+
private String joinJsUnpkgPath(String library, String cdnVersion, String cdnFileName) {
121+
return CDN_UNPKG + library + "@" + cdnVersion + "/" + cdnFileName;
122+
}
123+
124+
private String constructGraphQlEndpoint(HttpServletRequest request, @RequestParam Map<String, String> params) {
125+
String endpoint = graphqlEndpoint;
126+
for (Map.Entry<String, String> param : params.entrySet()) {
127+
endpoint = endpoint.replaceAll("\\{" + param.getKey() + "}", param.getValue());
128+
}
129+
if (StringUtils.isNotBlank(request.getContextPath()) && !endpoint.startsWith(request.getContextPath())) {
130+
return request.getContextPath() + endpoint;
131+
}
132+
return endpoint;
133+
}
134+
135+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package com.oembedler.moon.altair.boot;
2+
3+
import org.springframework.core.env.ConfigurableEnvironment;
4+
import org.springframework.core.env.EnumerablePropertySource;
5+
import org.springframework.core.env.Environment;
6+
import org.springframework.core.env.PropertySource;
7+
8+
import java.util.*;
9+
import java.util.stream.Stream;
10+
import java.util.stream.StreamSupport;
11+
12+
class PropertyGroupReader {
13+
14+
private Environment environment;
15+
private String prefix;
16+
private Properties props;
17+
18+
PropertyGroupReader(Environment environment, String prefix) {
19+
this.environment = Objects.requireNonNull(environment);
20+
this.prefix = Optional.ofNullable(prefix).orElse("");
21+
}
22+
23+
Properties load() {
24+
if (props == null) {
25+
props = new Properties();
26+
loadProps();
27+
}
28+
return props;
29+
}
30+
31+
private void loadProps() {
32+
streamOfPropertySources().forEach(propertySource ->
33+
Arrays.stream(propertySource.getPropertyNames())
34+
.filter(this::isWanted)
35+
.forEach(key -> add(propertySource, key)));
36+
}
37+
38+
private Stream<EnumerablePropertySource> streamOfPropertySources() {
39+
if (environment instanceof ConfigurableEnvironment) {
40+
Iterator<PropertySource<?>> iterator = ((ConfigurableEnvironment) environment).getPropertySources().iterator();
41+
Iterable<PropertySource<?>> iterable = () -> iterator;
42+
return StreamSupport.stream(iterable.spliterator(), false)
43+
.filter(EnumerablePropertySource.class::isInstance)
44+
.map(EnumerablePropertySource.class::cast);
45+
}
46+
return Stream.empty();
47+
}
48+
49+
private String withoutPrefix(String key) {
50+
return key.replace(prefix, "");
51+
}
52+
53+
private boolean isWanted(String key) {
54+
return key.startsWith(prefix);
55+
}
56+
57+
private Object add(EnumerablePropertySource propertySource, String key) {
58+
return props.put(withoutPrefix(key), propertySource.getProperty(key));
59+
}
60+
61+
}
62+

0 commit comments

Comments
 (0)