diff --git a/graphql-sample/build.gradle b/graphql-sample/build.gradle new file mode 100644 index 00000000..acd1592c --- /dev/null +++ b/graphql-sample/build.gradle @@ -0,0 +1,23 @@ +plugins { + id 'org.springframework.boot' version '2.4.0-SNAPSHOT' + id 'io.spring.dependency-management' version '1.0.10.RELEASE' + id 'java' +} +group = 'com.example' +version = '0.0.1-SNAPSHOT' +description = "GraphQL sample application" +sourceCompatibility = '1.8' + +dependencies { + implementation project(':spring-graphql-web') + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-hateoas' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.boot:spring-boot-starter-actuator' + developmentOnly 'org.springframework.boot:spring-boot-devtools' + runtimeOnly 'com.h2database:h2' + testImplementation 'org.springframework.boot:spring-boot-starter-test' +} +test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/graphql-sample/src/main/java/io/spring/sample/graphql/GraphQLSampleApplication.java b/graphql-sample/src/main/java/io/spring/sample/graphql/GraphQLSampleApplication.java new file mode 100644 index 00000000..d25264c3 --- /dev/null +++ b/graphql-sample/src/main/java/io/spring/sample/graphql/GraphQLSampleApplication.java @@ -0,0 +1,27 @@ +/* + * Copyright 2002-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.spring.sample.graphql; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class GraphQLSampleApplication { + public static void main(String[] args) { + SpringApplication.run(GraphQLSampleApplication.class, args); + } +} diff --git a/graphql-sample/src/main/java/io/spring/sample/graphql/project/Project.java b/graphql-sample/src/main/java/io/spring/sample/graphql/project/Project.java new file mode 100644 index 00000000..2f3f1742 --- /dev/null +++ b/graphql-sample/src/main/java/io/spring/sample/graphql/project/Project.java @@ -0,0 +1,56 @@ +package io.spring.sample.graphql.project; + +import java.util.List; + +public class Project { + + private String slug; + + private String name; + + private String repositoryUrl; + + private ProjectStatus status; + + private List releases; + + public String getSlug() { + return this.slug; + } + + public void setSlug(String slug) { + this.slug = slug; + } + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + public String getRepositoryUrl() { + return this.repositoryUrl; + } + + public void setRepositoryUrl(String repositoryUrl) { + this.repositoryUrl = repositoryUrl; + } + + public ProjectStatus getStatus() { + return this.status; + } + + public void setStatus(ProjectStatus status) { + this.status = status; + } + + public List getReleases() { + return this.releases; + } + + public void setReleases(List releases) { + this.releases = releases; + } +} diff --git a/graphql-sample/src/main/java/io/spring/sample/graphql/project/ProjectDataWiring.java b/graphql-sample/src/main/java/io/spring/sample/graphql/project/ProjectDataWiring.java new file mode 100644 index 00000000..bfc73ddd --- /dev/null +++ b/graphql-sample/src/main/java/io/spring/sample/graphql/project/ProjectDataWiring.java @@ -0,0 +1,32 @@ +package io.spring.sample.graphql.project; + +import graphql.schema.idl.RuntimeWiring; + +import org.springframework.boot.graphql.RuntimeWiringCustomizer; +import org.springframework.stereotype.Component; + +@Component +public class ProjectDataWiring implements RuntimeWiringCustomizer { + + private final SpringProjectsClient client; + + public ProjectDataWiring(SpringProjectsClient client) { + this.client = client; + } + + @Override + public void customize(RuntimeWiring.Builder builder) { + builder + .type("QueryType", typeWiring -> + typeWiring.dataFetcher("project", env -> { + String slug = env.getArgument("slug"); + return client.fetchProject(slug); + })) + .type("Project", typeWiring -> + typeWiring.dataFetcher("releases", env -> { + Project project = env.getSource(); + return client.fetchProjectReleases(project.getSlug()); + }) + ); + } +} diff --git a/graphql-sample/src/main/java/io/spring/sample/graphql/project/ProjectStatus.java b/graphql-sample/src/main/java/io/spring/sample/graphql/project/ProjectStatus.java new file mode 100644 index 00000000..0f37b2cd --- /dev/null +++ b/graphql-sample/src/main/java/io/spring/sample/graphql/project/ProjectStatus.java @@ -0,0 +1,16 @@ +package io.spring.sample.graphql.project; + +import java.util.Arrays; + +import com.fasterxml.jackson.annotation.JsonCreator; + +public enum ProjectStatus { + ACTIVE, COMMUNITY, INCUBATING, ATTIC; + + @JsonCreator + public static ProjectStatus fromName(String name) { + return Arrays.stream(ProjectStatus.values()) + .filter(type -> type.name().equals(name)) + .findFirst().orElse(ProjectStatus.ACTIVE); + } +} diff --git a/graphql-sample/src/main/java/io/spring/sample/graphql/project/Release.java b/graphql-sample/src/main/java/io/spring/sample/graphql/project/Release.java new file mode 100644 index 00000000..db5c17b6 --- /dev/null +++ b/graphql-sample/src/main/java/io/spring/sample/graphql/project/Release.java @@ -0,0 +1,54 @@ +package io.spring.sample.graphql.project; + +public class Release { + + private String version; + + private ReleaseStatus status; + + private String referenceDocUrl; + + private String apiDocUrl; + + private boolean current; + + public String getVersion() { + return this.version; + } + + public void setVersion(String version) { + this.version = version; + } + + public ReleaseStatus getStatus() { + return this.status; + } + + public void setStatus(ReleaseStatus status) { + this.status = status; + } + + public String getReferenceDocUrl() { + return this.referenceDocUrl; + } + + public void setReferenceDocUrl(String referenceDocUrl) { + this.referenceDocUrl = referenceDocUrl; + } + + public String getApiDocUrl() { + return this.apiDocUrl; + } + + public void setApiDocUrl(String apiDocUrl) { + this.apiDocUrl = apiDocUrl; + } + + public boolean isCurrent() { + return this.current; + } + + public void setCurrent(boolean current) { + this.current = current; + } +} diff --git a/graphql-sample/src/main/java/io/spring/sample/graphql/project/ReleaseStatus.java b/graphql-sample/src/main/java/io/spring/sample/graphql/project/ReleaseStatus.java new file mode 100644 index 00000000..8c36053c --- /dev/null +++ b/graphql-sample/src/main/java/io/spring/sample/graphql/project/ReleaseStatus.java @@ -0,0 +1,16 @@ +package io.spring.sample.graphql.project; + +import java.util.Arrays; + +import com.fasterxml.jackson.annotation.JsonCreator; + +public enum ReleaseStatus { + GENERAL_AVAILABILITY, MILESTONE, SNAPSHOT; + + @JsonCreator + public static ReleaseStatus fromName(String name) { + return Arrays.stream(ReleaseStatus.values()) + .filter(type -> type.name().equals(name)) + .findFirst().orElse(ReleaseStatus.GENERAL_AVAILABILITY); + } +} diff --git a/graphql-sample/src/main/java/io/spring/sample/graphql/project/SpringProjectsClient.java b/graphql-sample/src/main/java/io/spring/sample/graphql/project/SpringProjectsClient.java new file mode 100644 index 00000000..c2463df4 --- /dev/null +++ b/graphql-sample/src/main/java/io/spring/sample/graphql/project/SpringProjectsClient.java @@ -0,0 +1,47 @@ +package io.spring.sample.graphql.project; + +import java.net.URI; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.hateoas.CollectionModel; +import org.springframework.hateoas.MediaTypes; +import org.springframework.hateoas.client.Hop; +import org.springframework.hateoas.client.Traverson; +import org.springframework.hateoas.server.core.TypeReferences; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +@Component +public class SpringProjectsClient { + + private static final TypeReferences.CollectionModelType releaseCollection = + new TypeReferences.CollectionModelType() { }; + + private final Traverson traverson; + + public SpringProjectsClient(RestTemplateBuilder builder) { + RestTemplate restTemplate = builder.messageConverters(Traverson.getDefaultMessageConverters(MediaTypes.HAL_JSON)).build(); + this.traverson = new Traverson(URI.create("https://spring.io/api/"), MediaTypes.HAL_JSON); + this.traverson.setRestOperations(restTemplate); + } + + public Project fetchProject(String projectSlug) { + return this.traverson.follow("projects") + .follow(Hop.rel("project").withParameter("id", projectSlug)) + .toObject(Project.class); + } + + public List fetchProjectReleases(String projectSlug) { + CollectionModel releases = this.traverson.follow("projects") + .follow(Hop.rel("project").withParameter("id", projectSlug)) + .follow(Hop.rel("releases")) + .toObject(releaseCollection); + return new ArrayList(releases.getContent()); + } + +} diff --git a/graphql-sample/src/main/java/io/spring/sample/graphql/repository/ArtifactRepositories.java b/graphql-sample/src/main/java/io/spring/sample/graphql/repository/ArtifactRepositories.java new file mode 100644 index 00000000..44cd614e --- /dev/null +++ b/graphql-sample/src/main/java/io/spring/sample/graphql/repository/ArtifactRepositories.java @@ -0,0 +1,7 @@ +package io.spring.sample.graphql.repository; + +import org.springframework.data.repository.CrudRepository; + +public interface ArtifactRepositories extends CrudRepository { + +} diff --git a/graphql-sample/src/main/java/io/spring/sample/graphql/repository/ArtifactRepositoriesInitializer.java b/graphql-sample/src/main/java/io/spring/sample/graphql/repository/ArtifactRepositoriesInitializer.java new file mode 100644 index 00000000..46b2fc74 --- /dev/null +++ b/graphql-sample/src/main/java/io/spring/sample/graphql/repository/ArtifactRepositoriesInitializer.java @@ -0,0 +1,28 @@ +package io.spring.sample.graphql.repository; + +import java.util.Arrays; +import java.util.List; + +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.stereotype.Component; + +@Component +public class ArtifactRepositoriesInitializer implements ApplicationRunner { + + private final ArtifactRepositories repositories; + + public ArtifactRepositoriesInitializer(ArtifactRepositories repositories) { + this.repositories = repositories; + } + + @Override + public void run(ApplicationArguments args) throws Exception { + List repositoryList = Arrays.asList( + new ArtifactRepository("spring-releases", "Spring Releases", "https://repo.spring.io/libs-releases"), + new ArtifactRepository("spring-milestones", "Spring Milestones", "https://repo.spring.io/libs-milestones"), + new ArtifactRepository("spring-snapshots", "Spring Snapshots", "https://repo.spring.io/libs-snapshots") + ); + repositories.saveAll(repositoryList); + } +} diff --git a/graphql-sample/src/main/java/io/spring/sample/graphql/repository/ArtifactRepository.java b/graphql-sample/src/main/java/io/spring/sample/graphql/repository/ArtifactRepository.java new file mode 100644 index 00000000..60751f9e --- /dev/null +++ b/graphql-sample/src/main/java/io/spring/sample/graphql/repository/ArtifactRepository.java @@ -0,0 +1,58 @@ +package io.spring.sample.graphql.repository; + +import javax.persistence.Entity; +import javax.persistence.Id; + +@Entity +public class ArtifactRepository { + + @Id + private String id; + + private String name; + + private String url; + + private boolean snapshotsEnabled; + + public ArtifactRepository(String id, String name, String url) { + this.id = id; + this.name = name; + this.url = url; + } + + public ArtifactRepository() { + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public boolean isSnapshotsEnabled() { + return snapshotsEnabled; + } + + public void setSnapshotsEnabled(boolean snapshotsEnabled) { + this.snapshotsEnabled = snapshotsEnabled; + } +} diff --git a/graphql-sample/src/main/java/io/spring/sample/graphql/repository/ArtifactRepositoryDataWiring.java b/graphql-sample/src/main/java/io/spring/sample/graphql/repository/ArtifactRepositoryDataWiring.java new file mode 100644 index 00000000..830c20d4 --- /dev/null +++ b/graphql-sample/src/main/java/io/spring/sample/graphql/repository/ArtifactRepositoryDataWiring.java @@ -0,0 +1,23 @@ +package io.spring.sample.graphql.repository; + +import graphql.schema.idl.RuntimeWiring; + +import org.springframework.boot.graphql.RuntimeWiringCustomizer; +import org.springframework.stereotype.Component; + +@Component +public class ArtifactRepositoryDataWiring implements RuntimeWiringCustomizer { + + private final ArtifactRepositories repositories; + + public ArtifactRepositoryDataWiring(ArtifactRepositories repositories) { + this.repositories = repositories; + } + + @Override + public void customize(RuntimeWiring.Builder builder) { + builder.type("QueryType", typeWiring -> typeWiring + .dataFetcher("artifactRepositories", env -> this.repositories.findAll()) + .dataFetcher("artifactRepository", env -> this.repositories.findById(env.getArgument("id")))); + } +} diff --git a/graphql-sample/src/main/resources/application.properties b/graphql-sample/src/main/resources/application.properties new file mode 100644 index 00000000..d589e7c7 --- /dev/null +++ b/graphql-sample/src/main/resources/application.properties @@ -0,0 +1 @@ +management.endpoints.web.exposure.include=health,metrics,info \ No newline at end of file diff --git a/graphql-sample/src/main/resources/schema.graphqls b/graphql-sample/src/main/resources/schema.graphqls new file mode 100644 index 00000000..c46733ce --- /dev/null +++ b/graphql-sample/src/main/resources/schema.graphqls @@ -0,0 +1,43 @@ +schema { + query: QueryType +} + +type QueryType { + artifactRepositories : [ArtifactRepository] + artifactRepository(id : ID!) : ArtifactRepository + project(slug: ID!): Project +} + +type ArtifactRepository { + id: ID! + name: String! + url: String! + snapshotsEnabled: Boolean +} + +type Project { + slug: ID! + name: String! + repositoryUrl: String! + status: ProjectStatus! + releases: [Release] +} + +type Release { + version: String! + status: ReleaseStatus! + current: Boolean +} + +enum ProjectStatus { + ACTIVE + COMMUNITY + INCUBATING + ATTIC +} + +enum ReleaseStatus { + GENERAL_AVAILABILITY + MILESTONE + SNAPSHOT +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index f561df35..21a5c37d 100644 --- a/settings.gradle +++ b/settings.gradle @@ -15,3 +15,4 @@ pluginManagement { rootProject.name = 'spring-graphql' include 'spring-graphql-web' +include 'graphql-sample'