I'm trying to use GraphQL like client of a REST API. My backend return JSON but in my application client i write graphQL and in client i translate graphQL queries to HTTP call's.
My schema:
type Query {
students: [Student]
}
type Student {
name: String
}
POJO example:
public class Student {
private Integer id;
private String name;
}
My resolver:
public class Query implements GraphQLQueryResolver {
public List<Post> students() {
// HTTP Request
}
}
In all library's implementations i need create a POJO for Student and write a resolver for request in my API.
A way exist to don't need create a POJO and a create global execute resolver?
If you're using libraries like graphql-java-tools (which seems to be the case), you need POJOs, as this is where the library gets its type mappings from. But if you're just using graphql-java itself, you can wire it any way you like - including having a single global resolver (i.e. DataFetcher).
For an idea how to do this, see http://graphql-java.readthedocs.io/en/latest/schema.html#idl
You want something like:
SchemaParser schemaParser = new SchemaParser();
SchemaGenerator schemaGenerator = new SchemaGenerator();
File schemaFile = loadSchema("yourStudentSchema.graphqls");
TypeDefinitionRegistry typeRegistry = schemaParser.parse(schemaFile);
RuntimeWiring wiring = buildRuntimeWiring();
GraphQLSchema graphQLSchema = schemaGenerator.makeExecutableSchema(typeRegistry, wiring);
RuntimeWiring is where you hook the resolvers, e.g:
RuntimeWiring buildRuntimeWiring() {
return RuntimeWiring.newRuntimeWiring()
// this uses builder function lambda syntax
.type("Query", typeWiring -> typeWiring
.dataFetcher("students", env -> fetchDataSomeHow(env)))
.build();
}
So you can provide the same DataFetcher implementation to each dataFetcher call f that's what you're after. graphql-java itself makes no assumptions about way it's wired and implemented, e.g. it does not enforce POJOs or anything else.
Related
I'm developing a back-end web application using Spring Boot with Java and I have the following problem:
A REST service returns me the following JSON:
{
"cap":"98888"
}
This is my JAVA class which models the output based on the content:
#NoArgsConstructor
#Data
public class MyObject {
private String cap;
}
I would like to change the field name for MY service to return the following JSON:
{
"CAP":"98888"
}
In my JAVA code, I make the call via RestTemplate, like this:
return restTemplate.postForObject(uriBuilder.build().toUri(), new HttpEntity<>(request, headers), MyObject.class);
I've tried to use tons of stuff with Jackson, including #JsonProperty, like this:
#NoArgsConstructor
#Data
public class MyObject {
#JsonProperty("CAP")
private String cap;
}
But the result is this:
{
"CAP":null
}
As if it no longer matches the original name of the property. In practice I can only get an output with correct value but original field name (WHICH I DON'T WANT) or an output with the name of the field I want (i.e. CAP) but with a null value.
Which is the right way to rename properties with Spring annotations?
I have a project running with Spring Boot and have implemented a GraphQL API with com.graphql-java.graphql-java:12.0.
I want to set the field visibility for some Mutators and maybe some fields now, but unfortunately I find no tutorial, doc or example where I find a working explanation how to to this.
For explanation, I have following example entries in schema:
type Query {
login(username: String!, password: String!): String
organization(id: Int!): ApiOrganization
}
type Mutation {
updateProfile(profileData: ProfileInputDto): ID
updateAdminStuff(adminData: AdminStuffDto): ID
}
The Query entries shall now be visible in the schema for all users who use the api and also the Mutation to updateProfile shall be visible.
But the Mutation updateAdminStuff shall only be visible if the user is logged in as Admin Role, so that a normal user doesn't even know that this mutation exists. Additionally it can happen that only some fields of some schema types shall be made visible for some roles only.
I found out that there is a chance to set something like that by GraphqlFieldVisibility (https://www.graphql-java.com/documentation/v12/fieldvisibility/). First versions I found said to set it in GraphQLSchema, but there it seems deprecated and I should use GraphQLCodeRegistry to set the visibility. For GraphQLCodeRegistry I found out on https://www.graphql-java.com/documentation/v12/execution/
GraphQLCodeRegistry codeRegistry = newCodeRegistry()
.dataFetcher(
coordinates("CreateReviewForEpisodeMutation", "createReview"),
mutationDataFetcher()
)
.build();
GraphQLSchema schema = GraphQLSchema.newSchema()
.query(queryType)
.mutation(createReviewForEpisodeMutation)
.codeRegistry(codeRegistry)
.build();
But unfortunately I find no way to set this method for the schema generation I use.
Can someone please give me a hint (example, tutorial, documentation) where I can find a tipp for soulution? (if something is possible at all in GraphQL)
Here some additional infos about the project:
I have a schmea definition saved as schema.graphqls. I have a GraphQLProvider and it prepares the Scehma and a GraphQL the following way:
private GraphQL graphQL;
#Bean
public GraphQL graphQL() {
return graphQL;
}
#PostConstruct
public void init() throws IOException {
URL url = Resources.getResource("graphql/schema.graphqls");
String sdl = Resources.toString(url, Charsets.UTF_8);
GraphQLSchema graphQLSchema = buildSchema(sdl);
this.graphQL = GraphQL.newGraphQL(graphQLSchema).build();
}
private GraphQLSchema buildSchema(String sdl) {
TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(sdl);
RuntimeWiring runtimeWiring = buildWiring();
SchemaGenerator schemaGenerator = new SchemaGenerator();
return schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiring);
}
In my controller I fetch the data by
ExecutionInput executionInput = ExecutionInput.newExecutionInput().context(request).query(body.getQuery())
.build();
ExecutionResult executionResult = graphQL.execute(executionInput);
where body is a GraphQLQuery and graphQL is the bean of the code before.
Thanks for your help and best regards.
Ok got the answer in the GraphQL-Java chat.
I used this tutorial https://www.graphql-java.com/tutorials/getting-started-with-spring-boot/ to build the graphQl API and using this I build the runtime wiring by myself and there I can set the visibility.
I now implemented it this way:
GraphqlFieldVisibility blockedFields = BlockedFields.newBlock()
.addPattern("ApiField.secretfield")
.addPattern(".*\\.secretAdminMutation")
.build();
private RuntimeWiring buildWiring() {
return RuntimeWiring.newRuntimeWiring().fieldVisibility(blockedFields)....
and it works great!
I want to introduce a scalar type to the graphql scheme and provide resolver for it.
I believe what I need to do is to provide the corresponding resolver for the new type as described here
I use spring boot starter to implement the server.
com.graphql-java:graphql-spring-boot-starter:5.0.2
There is schema.graphqls:
type Query {
pets(last: Int): [Pet]
}
type Pet {
id: ID
name: String
}
and QueryResolver:
#Component
public class Query implements GraphQLQueryResolver {
#Autowired
PetStore petStore;
public Pet pet(long id) {
return petStore.pet(id);
}
public List<Pet> pets(Integer last) {
return petStore.pets().stream()
.limit(last != null ? last : Integer.MAX_VALUE)
.collect(Collectors.toList());
}
}
I believe that there is a way to update a scheme like this:
type Pet {
id: ID
name: String
dateOfBirth: LocalDateTime
}
scalar LocalDateTime
and provide resolver to define how new field value should be processed, like this:
#Component
public class DateResolver extends GraphQLScalarType {
// serialize/deserialize logic
}
Unfochinatly I get following exception:
com.coxautodev.graphql.tools.SchemaClassScannerError: Expected a user-defined GraphQL scalar type with name 'LocalDateTime' but found none!
After some research, I found resolvers for the java types in the graphql-java-9.2.jar!/graphql/Scalars.class and inspired of it.
Not sure what your setup looks like but for me this is how I quickly resolved that exception:
I extended the GraphQlHttpServlet and there is where I load my graphql schema (overriding the getConfiguration() method etc to start the servlet... sorry I can't go into much detail but I'm sure looking at the docs will make what I just said make sense). In getConfiguration() I have:
return GraphQLConfiguration.with(createSchema())
.with(customGraphQLContextBuilder)
.build();
and in createSchema I load the graphql schema and I PARSE it:
.
.
return SchemaParser.newParser()
.schemaString(schemaString)
.scalars(CustomDateTimeScalar.getDateTimeScalar())
.resolvers(myQueryResolverXYZ)
.build()
.makeExecutableSchema();
.
.
Just in case this sheds light on someone else that runs into this.
What I've known are:
If I define a custom method in the Repository interface, it will show in the URL like http://localhost/{repository}/search/{myMethod}. For example, I can define a recent method in the Order Repository interface (I can do this by using #Query simple and clean) and I can get the most recent object via http://localhost/order/search/recent.
If I use #RepositoryRestController to make a custom controller class, I have to implement HATEOAS using Resource and Resources myself and add a link. But I will get the right URL I want like http://localhost/{myPath}. For example, if I want to get the most recent objects of Order, I have to write codes below:
#RepositoryRestController
public class RecentOrdersController {
private OrderRepository orderRepo;
#Autowired
public RecentOrdersController(OrderRepository orderRepo) {
this.orderRepo = orderRepo;
}
#GetMapping(path = "/orders/recent", produces = "application/hal+json")
public ResponseEntity<Resources<OrderResource>> recentOrders() {
PageRequest page = PageRequest.of(
0, 12, Sort.by("createdAt").descending());
List<Order> orders = orderRepo.findAll(page).getContent();
List<OrderResource> orderResources =
new OrderResourceAssembler().toResources(orders);
Resources<OrderResource> recentResources =
new Resources<OrderResource>(tacoResources);
recentResources.add(
linkTo(methodOn(RecentOrdersController.class).recentOrders())
.withRel("recents"));
return new ResponseEntity<>(recentResources, HttpStatus.OK);
}
}
and then:
#Bean
public ResourceProcessor<PagedResources<Resource<Order>>>
orderProcessor(EntityLinks links) {
return new ResourceProcessor<PagedResources<Resource<Order>>>() {
#Override
public PagedResources<Resource<Order>> process(
PagedResources<Resource<Order>> resource) {
resource.add(
links.linkFor(Order.class)
.slash("recent")
.withRel("recents"));
return resource;
}
};
}
I have to write the OrderResource and OrderResourceAssembler myself and which make this even worse is that there may many other domains like Person in the Order domain, I have to wirte the xxxResource and xxxResourceAssembler myself too.
My question is how can I combine those?
For example, if I define a domain class named Order and enable the spring data REST, how can I get the most recent orders via URL like http://localhost/recent with fully HATEOAS support at minimal effort?
This is the same questions than :
Jackson JSON library: how to instantiate a class that contains abstract fields
Nevertheless its solution is not possible since my abstract class is in another project than the concrete one.
Is there a way then ?
EDIT
My architecture is as follows:
public class UserDTO {
...
private LanguageDTO lang;
}
I send that object user :
restTemplate.postForObject(this.getHttpCore().trim() + "admin/user/save/1/" + idUser, userEntity, UserDTO.class);
Then I am supposed to receive it in the function :
#RequestMapping(value = "/save/{admin}/{idUser}", method = RequestMethod.POST)
public String saveUserById(#RequestBody final UserEntity user, #PathVariable Integer idUser, #PathVariable boolean admin)
with UserEntity defined as :
public class UserEntity extends AbstractUserEntity {
...
}
public abstract class AbstractUserEntity {
...
private AbstractLanguageEntity lang;
}
I would like to know how I can specify that lang should be instantiate as LanguageEntity whereas abstract classes are in another project.
This could work assuming you can configure how the object get serialized. See the example here. Look under "1.1. Global default typing" to set the defaults to include extra information in your JSON string, basically the concrete Java type that must be used when deserializing.
Since it seems you need to do this for your Spring servlet, you would have to pass a Spring message converter as mentioned here
Then inside your custom objectMapper, you can do the necessary configuration:
public class JSONMapper extends ObjectMapper {
public JSONMapper() {
this.enableDefaultTyping();
}
}
You could probably also make it work with Mix-ins, which allow you to add annotations to classes already defined. You can see and example here. This will also need to be configured inside the objectMapper.
If you need the same functionality on your client side (REST template), you can pass the object mapper as shown here.
The easiest way to solve that issue is to add getters et setters in UserEntity but specifying a concrete class :
public LanguageEntity getLang() {
return (LanguageEntity) lang;
}
public void setLang(LanguageEntity language){
this.lang = language
}
If all that you want to achieve is to note that LanguageEntity is the implementation of AbstractLanguageEntity, you can register this mapping via module:
SimpleModule myModule = new SimpleModule())
.addAbstractTypeMapping(AbstractLanguageEntity.class,
LanguageEntity.class);
ObjectMapper mapper = new ObjectMapper()
.registerMdoule(myModule);