From ce2839b0588d3f3b8a9f51d1fb86bb9abdea7a6c Mon Sep 17 00:00:00 2001 From: Muhammad Abdullah Azam Khan Date: Thu, 21 Apr 2022 16:01:36 +0400 Subject: [PATCH] Return Map from GraphQL (#11966) * Return Map from GraphQL Three techniques have been used 1. Return as Json String 2. Return Json using GraphQL scalar type 3. Return as list of key-value pair * Adding custom scalar in plugin configuration * Build failure fix 1. Integrating the .graphqls files 2. Updating the respective query resolvers * Build failure Fix 1. Removed the extra .graphql file 2. Added ExtendedGraphQLScalarType class because the parent class didn't have default constructor and the client code generation plugin was requiring it. * Code refactoring * Code refactoring --- graphql/graphql-java/pom.xml | 34 +++++++++++- .../baeldung/graphql/server/GraphQLQuery.java | 9 ++++ .../baeldung/graphqlreturnmap/AppHandler.java | 52 +++++++++++++++++++ .../ExtendedGraphQLScalarType.java | 24 +++++++++ .../graphqlreturnmap/GraphqlReturnMap.java | 12 +++++ .../graphqlreturnmap/entity/Attribute.java | 46 ++++++++++++++++ .../graphqlreturnmap/entity/Product.java | 48 +++++++++++++++++ .../model/AttributeKeyValueModel.java | 30 +++++++++++ .../repository/ProductRepository.java | 12 +++++ .../impl/ProductRepositoryImpl.java | 43 +++++++++++++++ .../resolver/ProductResolver.java | 30 +++++++++++ .../graphqlreturnmap/resolver/Query.java | 33 ++++++++++++ .../src/main/resources/schema.graphqls | 26 ++++++++++ 13 files changed, 398 insertions(+), 1 deletion(-) create mode 100644 graphql/graphql-java/src/main/java/com/baeldung/graphqlreturnmap/AppHandler.java create mode 100644 graphql/graphql-java/src/main/java/com/baeldung/graphqlreturnmap/ExtendedGraphQLScalarType.java create mode 100644 graphql/graphql-java/src/main/java/com/baeldung/graphqlreturnmap/GraphqlReturnMap.java create mode 100644 graphql/graphql-java/src/main/java/com/baeldung/graphqlreturnmap/entity/Attribute.java create mode 100644 graphql/graphql-java/src/main/java/com/baeldung/graphqlreturnmap/entity/Product.java create mode 100644 graphql/graphql-java/src/main/java/com/baeldung/graphqlreturnmap/model/AttributeKeyValueModel.java create mode 100644 graphql/graphql-java/src/main/java/com/baeldung/graphqlreturnmap/repository/ProductRepository.java create mode 100644 graphql/graphql-java/src/main/java/com/baeldung/graphqlreturnmap/repository/impl/ProductRepositoryImpl.java create mode 100644 graphql/graphql-java/src/main/java/com/baeldung/graphqlreturnmap/resolver/ProductResolver.java create mode 100644 graphql/graphql-java/src/main/java/com/baeldung/graphqlreturnmap/resolver/Query.java diff --git a/graphql/graphql-java/pom.xml b/graphql/graphql-java/pom.xml index 5808dd17fb..068cb04ea8 100644 --- a/graphql/graphql-java/pom.xml +++ b/graphql/graphql-java/pom.xml @@ -36,6 +36,21 @@ graphql-java-annotations ${graphql-java-annotations.version} + + io.ratpack + ratpack-core + ${ratpack-core.version} + + + com.github.americanexpress.nodes + nodes + 0.5.0 + + + com.graphql-java + graphql-java + 11.0 + com.graphql-java graphql-java-tools @@ -95,6 +110,14 @@ ${mockserver-client-java.version} test + + + com.graphql-java + graphql-java-extended-scalars + ${graphql-java-extended-scalars.version} + + + @@ -133,6 +156,13 @@ false false true + + + JSON + java.util.Map + com.baeldung.graphqlreturnmap.ExtendedGraphQLScalarType + + @@ -154,6 +184,8 @@ 1.8 1.8 + 1.18 + 2022-04-06T00-10-27-a70541e - \ No newline at end of file + diff --git a/graphql/graphql-java/src/main/java/com/baeldung/graphql/server/GraphQLQuery.java b/graphql/graphql-java/src/main/java/com/baeldung/graphql/server/GraphQLQuery.java index 8ba9fa25c5..f2ac792e80 100644 --- a/graphql/graphql-java/src/main/java/com/baeldung/graphql/server/GraphQLQuery.java +++ b/graphql/graphql-java/src/main/java/com/baeldung/graphql/server/GraphQLQuery.java @@ -2,6 +2,7 @@ package com.baeldung.graphql.server; import com.baeldung.graphql.data.Book; import com.baeldung.graphql.data.BookRepository; +import com.baeldung.graphqlreturnmap.entity.Product; import com.coxautodev.graphql.tools.GraphQLQueryResolver; import java.util.List; @@ -18,4 +19,12 @@ public class GraphQLQuery implements GraphQLQueryResolver { return repository.getAllBooks(); } + public List getProducts(int pageSize, int pageNumber) { + return null; + } + + public Product getProduct(int id) { + return null; + } + } diff --git a/graphql/graphql-java/src/main/java/com/baeldung/graphqlreturnmap/AppHandler.java b/graphql/graphql-java/src/main/java/com/baeldung/graphqlreturnmap/AppHandler.java new file mode 100644 index 0000000000..26ad0eef2c --- /dev/null +++ b/graphql/graphql-java/src/main/java/com/baeldung/graphqlreturnmap/AppHandler.java @@ -0,0 +1,52 @@ +package com.baeldung.graphqlreturnmap; + +import com.baeldung.graphql.utils.SchemaUtils; +import com.baeldung.graphqlreturnmap.resolver.ProductResolver; +import com.baeldung.graphqlreturnmap.resolver.Query; +import com.coxautodev.graphql.tools.SchemaParser; +import graphql.ExecutionResult; +import graphql.GraphQL; +import graphql.scalars.ExtendedScalars; +import graphql.schema.GraphQLSchema; +import ratpack.handling.Context; +import ratpack.handling.Handler; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.logging.Logger; + +import static ratpack.jackson.Jackson.json; + +public class AppHandler implements Handler { + private static final Logger LOGGER = Logger.getLogger(AppHandler.class.getSimpleName()); + private GraphQL graphql; + + public AppHandler() throws Exception { + GraphQLSchema schema = SchemaParser.newParser() + .resolvers(new Query(), new ProductResolver()) + .scalars(ExtendedScalars.Json) + .file("schema.graphqls") + .build() + .makeExecutableSchema(); + graphql = GraphQL.newGraphQL(schema).build(); + } + + @Override + public void handle(Context context) throws Exception { + context.parse(Map.class) + .then(payload -> { + ExecutionResult executionResult = graphql.execute(payload.get(SchemaUtils.QUERY) + .toString(), null, this, Collections.emptyMap()); + Map result = new LinkedHashMap<>(); + if (executionResult.getErrors() + .isEmpty()) { + result.put(SchemaUtils.DATA, executionResult.getData()); + } else { + result.put(SchemaUtils.ERRORS, executionResult.getErrors()); + LOGGER.warning("Errors: " + executionResult.getErrors()); + } + context.render(json(result)); + }); + } +} diff --git a/graphql/graphql-java/src/main/java/com/baeldung/graphqlreturnmap/ExtendedGraphQLScalarType.java b/graphql/graphql-java/src/main/java/com/baeldung/graphqlreturnmap/ExtendedGraphQLScalarType.java new file mode 100644 index 0000000000..5cdc72b13d --- /dev/null +++ b/graphql/graphql-java/src/main/java/com/baeldung/graphqlreturnmap/ExtendedGraphQLScalarType.java @@ -0,0 +1,24 @@ +package com.baeldung.graphqlreturnmap; + +import graphql.language.ScalarTypeDefinition; +import graphql.schema.Coercing; +import graphql.schema.GraphQLDirective; +import graphql.schema.GraphQLScalarType; + +import java.util.List; + +public class ExtendedGraphQLScalarType extends GraphQLScalarType { + + public ExtendedGraphQLScalarType(){ + super("","",null); + + } + + public ExtendedGraphQLScalarType(String name, String description, Coercing coercing) { + super(name, description, coercing); + } + + public ExtendedGraphQLScalarType(String name, String description, Coercing coercing, List directives, ScalarTypeDefinition definition) { + super(name, description, coercing, directives, definition); + } +} diff --git a/graphql/graphql-java/src/main/java/com/baeldung/graphqlreturnmap/GraphqlReturnMap.java b/graphql/graphql-java/src/main/java/com/baeldung/graphqlreturnmap/GraphqlReturnMap.java new file mode 100644 index 0000000000..8717fcb722 --- /dev/null +++ b/graphql/graphql-java/src/main/java/com/baeldung/graphqlreturnmap/GraphqlReturnMap.java @@ -0,0 +1,12 @@ +package com.baeldung.graphqlreturnmap; + +import ratpack.server.RatpackServer; + +public class GraphqlReturnMap { + + public static void main(String[] args) throws Exception { + final RatpackServer server = RatpackServer.of(s -> s.handlers(chain -> chain.post("product", new AppHandler()))); + server.start(); + } + +} \ No newline at end of file diff --git a/graphql/graphql-java/src/main/java/com/baeldung/graphqlreturnmap/entity/Attribute.java b/graphql/graphql-java/src/main/java/com/baeldung/graphqlreturnmap/entity/Attribute.java new file mode 100644 index 0000000000..dd2766afa5 --- /dev/null +++ b/graphql/graphql-java/src/main/java/com/baeldung/graphqlreturnmap/entity/Attribute.java @@ -0,0 +1,46 @@ +package com.baeldung.graphqlreturnmap.entity; + +public class Attribute { + private String name; + private String description; + private String unit; + + public Attribute(String name, String description, String unit){ + this.name = name; + this.description = description; + this.unit = unit; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getUnit() { + return unit; + } + + public void setUnit(String unit) { + this.unit = unit; + } + + @Override + public String toString() { + return "Attribute{" + + "name='" + name + '\'' + + ", description='" + description + '\'' + + ", unit='" + unit + '\'' + + '}'; + } +} diff --git a/graphql/graphql-java/src/main/java/com/baeldung/graphqlreturnmap/entity/Product.java b/graphql/graphql-java/src/main/java/com/baeldung/graphqlreturnmap/entity/Product.java new file mode 100644 index 0000000000..db39d763de --- /dev/null +++ b/graphql/graphql-java/src/main/java/com/baeldung/graphqlreturnmap/entity/Product.java @@ -0,0 +1,48 @@ +package com.baeldung.graphqlreturnmap.entity; + + +import java.util.Map; + +public class Product { + private Integer id; + private String name; + private String description; + private Map attributes; + + + public Product(){ + + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Map getAttributes() { + return attributes; + } + + public void setAttributes(Map attributes) { + this.attributes = attributes; + } +} diff --git a/graphql/graphql-java/src/main/java/com/baeldung/graphqlreturnmap/model/AttributeKeyValueModel.java b/graphql/graphql-java/src/main/java/com/baeldung/graphqlreturnmap/model/AttributeKeyValueModel.java new file mode 100644 index 0000000000..bb7641143b --- /dev/null +++ b/graphql/graphql-java/src/main/java/com/baeldung/graphqlreturnmap/model/AttributeKeyValueModel.java @@ -0,0 +1,30 @@ +package com.baeldung.graphqlreturnmap.model; + + +import com.baeldung.graphqlreturnmap.entity.Attribute; + +public class AttributeKeyValueModel { + private String key; + private Attribute value; + + public AttributeKeyValueModel(String key, Attribute value) { + this.key = key; + this.value = value; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public Attribute getValue() { + return value; + } + + public void setValue(Attribute value) { + this.value = value; + } +} diff --git a/graphql/graphql-java/src/main/java/com/baeldung/graphqlreturnmap/repository/ProductRepository.java b/graphql/graphql-java/src/main/java/com/baeldung/graphqlreturnmap/repository/ProductRepository.java new file mode 100644 index 0000000000..c751efc183 --- /dev/null +++ b/graphql/graphql-java/src/main/java/com/baeldung/graphqlreturnmap/repository/ProductRepository.java @@ -0,0 +1,12 @@ +package com.baeldung.graphqlreturnmap.repository; + + +import com.baeldung.graphqlreturnmap.entity.Product; + +import java.util.List; + +public interface ProductRepository { + List getProducts(Integer pageSize, Integer pageNumber); + Product getProduct(Integer id); + +} diff --git a/graphql/graphql-java/src/main/java/com/baeldung/graphqlreturnmap/repository/impl/ProductRepositoryImpl.java b/graphql/graphql-java/src/main/java/com/baeldung/graphqlreturnmap/repository/impl/ProductRepositoryImpl.java new file mode 100644 index 0000000000..466a2149f4 --- /dev/null +++ b/graphql/graphql-java/src/main/java/com/baeldung/graphqlreturnmap/repository/impl/ProductRepositoryImpl.java @@ -0,0 +1,43 @@ +package com.baeldung.graphqlreturnmap.repository.impl; + +import com.baeldung.graphqlreturnmap.entity.Attribute; +import com.baeldung.graphqlreturnmap.entity.Product; +import com.baeldung.graphqlreturnmap.repository.ProductRepository; +import org.springframework.stereotype.Repository; + +import java.util.*; +import java.util.stream.Collectors; + +@Repository +public class ProductRepositoryImpl implements ProductRepository { + + private static List productList = new ArrayList<>(); + + public ProductRepositoryImpl() { + for (int i = 1; i <= 10; i++){ + Product product = new Product(); + product.setId(i); + product.setName(String.format("Product %d", i)); + product.setDescription(String.format("Product %d description", i)); + product.setAttributes(createAttributes(i)); + productList.add(product); + } + } + + private Map createAttributes(int i) { + Map attributeMap = new HashMap<>(); + attributeMap.put(String.format("attribute_%d",i), new Attribute(String.format("Attribute%d name",i),"This is custom attribute description","This is custom attribute unit")); + attributeMap.put("size", new Attribute((i & 1) == 0 ? "Small" : "Large","This is custom attribute description","This is custom attribute unit")); + return attributeMap; + } + + @Override + public List getProducts(Integer pageSize, Integer pageNumber) { + return productList.stream().skip(pageSize*pageNumber).limit(pageSize).collect(Collectors.toList()); + } + + @Override + public Product getProduct(Integer id) { + return productList.stream().filter(product -> product.getId().equals(id)).findFirst().orElse(null); + } +} diff --git a/graphql/graphql-java/src/main/java/com/baeldung/graphqlreturnmap/resolver/ProductResolver.java b/graphql/graphql-java/src/main/java/com/baeldung/graphqlreturnmap/resolver/ProductResolver.java new file mode 100644 index 0000000000..d9789ea0c6 --- /dev/null +++ b/graphql/graphql-java/src/main/java/com/baeldung/graphqlreturnmap/resolver/ProductResolver.java @@ -0,0 +1,30 @@ +package com.baeldung.graphqlreturnmap.resolver; + +import com.baeldung.graphqlreturnmap.entity.Product; +import com.baeldung.graphqlreturnmap.model.AttributeKeyValueModel; +import com.coxautodev.graphql.tools.GraphQLResolver; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.util.LinkedList; +import java.util.List; + +public class ProductResolver implements GraphQLResolver { + public ProductResolver(){ + } + + public List getAttribute_list(Product product){ + List attributeModelList = new LinkedList<>(); + product.getAttributes().forEach((key, val) -> attributeModelList.add(new AttributeKeyValueModel(key, val))); + return attributeModelList; + } + + public String getAttribute_string(Product product){ + try { + return new ObjectMapper().writeValueAsString(product.getAttributes()); + } catch (JsonProcessingException e) { + e.printStackTrace(); + return ""; + } + } +} diff --git a/graphql/graphql-java/src/main/java/com/baeldung/graphqlreturnmap/resolver/Query.java b/graphql/graphql-java/src/main/java/com/baeldung/graphqlreturnmap/resolver/Query.java new file mode 100644 index 0000000000..cbcff6056b --- /dev/null +++ b/graphql/graphql-java/src/main/java/com/baeldung/graphqlreturnmap/resolver/Query.java @@ -0,0 +1,33 @@ +package com.baeldung.graphqlreturnmap.resolver; + +import com.baeldung.graphql.data.Book; +import com.baeldung.graphqlreturnmap.entity.Product; +import com.baeldung.graphqlreturnmap.repository.ProductRepository; +import com.baeldung.graphqlreturnmap.repository.impl.ProductRepositoryImpl; +import com.coxautodev.graphql.tools.GraphQLQueryResolver; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; + +public class Query implements GraphQLQueryResolver { + + @Autowired + private ProductRepository productRepository; + public Query(){ + productRepository = new ProductRepositoryImpl(); + } + + public List getProducts(int pageSize, int pageNumber) { + return productRepository.getProducts(pageSize, pageNumber); + } + + public Product getProduct(int id) { + return productRepository.getProduct(id); + } + + public List allBooks() { + return null; + } + + +} diff --git a/graphql/graphql-java/src/main/resources/schema.graphqls b/graphql/graphql-java/src/main/resources/schema.graphqls index b0834e04b7..da10cd18bd 100644 --- a/graphql/graphql-java/src/main/resources/schema.graphqls +++ b/graphql/graphql-java/src/main/resources/schema.graphqls @@ -10,8 +10,34 @@ type Author { type Query { allBooks: [Book] + products(size: Int, page: Int): [Product]! + product(id: Int): Product! } + + +type Product { + id: ID + name: String! + description: String + attribute_string:String + attribute_list:[AttributeKeyValuePair] + attributes: JSON +} + +type AttributeKeyValuePair { + key:String + value:Attribute +} + +type Attribute { + name:String + description:String + unit:String +} +scalar JSON + + schema { query: Query } \ No newline at end of file