Enunciate with Swagger - cannot generate documentation resource models when using generics - java

I'm facing similar issue than the one described in that thread :
Can Enunciate generate docs for an API that handles generic types?
I am using enunciate 1.28 with spring and swagger modules enabled.
So considering an abstract resource class like :
public abstract class AbstractResource<T> {
#Autowired
private SomeService<T> service;
#Path("/{id}")
#GET
public T get(#PathParam("id") String id) {
return service.get(id);
}
#POST
public Response post(T entity) {
return service.post(entity);
}
}
and one concrete implementation :
#Path("/authors")
public class AuthorResource extends AbstractResource<Author> { }
Enunciate docs are not generated with the proper "Author" Data Model for both GET and POST methods.
For GET I have :
Response Body element: (custom)`
and POST :
Request Body element: entity`
For Swagger the Author model is not showing as the JSON model for GET as "responseClass" and POST for the body "dataType". Instead I get string for both.
However the Author model is listed in the AuthorResource.json generated in swagger/ui directory. The responseClass and dataType fields are just missing the link to the model.
Manually replacing :
"responseClass" : "string"` by `"responseClass" : "ns0_Author" (GET)
"dataType" : "string"` by `"dataType" : "ns0_Author" (POST)
does the trick.
Note : I confirm that on my side Author is annotated with #XmlRootElement and the Author class is included in my <api-import pattern="com.my.package.**"/> which is located in a jar file on the classpath.
Any ideas on how to tweak the Enunciate/Swagger documentation generation in that case ?
Thanks

Smells like a bug. Tracking it here.

Related

Mapping multiple graphQL schema files to separate resolvers - Spring Boot

I'm finding it really difficult to separate queries from one schema file. I want to have something like this:
car.graphqls
type Query {
car(id: ID!): Car
}
type Car {
id: ID!,
name: String!
}
house.graphqls
type Query {
house(id: ID!): House
}
type House {
id: ID!,
owner: String,
street: String
}
I searched a lot but I can't find a way to write two java classes and implement getHouse() in one of them and getCar() in other.
#Component
public class CarQuery implements GraphQLQueryResolver {
#Autowired
private CarService carService;
public List<Car> getCar(final int id) {
return this.carService.getCar(id);
}
}
public class HouseQuery implements GraphQLQueryResolver {
#Autowired
private HouseService houseService;
public List<House> getHouse(final int id) {
return this.houseService.getHouse(id);
}
}
I found out that the graphql-java-tools package which I'm using will search through the project and finds all schema files (that end with .graphqls), but the code which I showed above gives me this error:
Caused by: com.coxautodev.graphql.tools.FieldResolverError: No method found with any of the following signatures (with or without one of [interface graphql.schema.DataFetchingEnvironment] as the last argument), in priority order:
com.example.polls.resolvers.CarQuery.house(~count)
com.example.polls.resolvers.CarQuery.getHouse(~count)
I also found some advises that I need to have only one Root Query in schema files, and to extend all other Query types in schema files. I tried to write to house.graphqls something like this, but failed:
extend Type Query {
house(id: ID!): House
}
Is there a way to tell graphql and java what schema file I want to be mapped to which java resolver file?
Thanks AllirionX. Your answer was helpful.
I would just like to summarize final solution to all who are looking for answer how to create multiple schema files with separate query types in each of them and map those query types to different Java Components using GraphQLQueryResolver.
My Spring Boot project structure
I have two schema files A.graphqls and B.graphqls.
A.graphqls
---------------
type Person {
id: ID!,
name: String
}
type Query {
getPerson(id: Int):Person
}
type Mutation {
createPerson(name: String):Int
}
B.graphqls
---------------
type Book {
id: ID!,
title: String,
owner: Person
}
extend type Query {
getBooks(count: Int):[Book]
}
extend type Mutation {
deleteBook(id: Int):Int
}
schema {
query: Query,
mutation: Mutation
}
I will explain what I learned about rules we need to follow about this topic (I don't guarantee that this is all necessary, but that is how I managed to get it work how I wanted it to work).
The key here is to only have one schema definition. It doesn't matter in which file (A.graphqls or B.graphqls or C.graphqls...) - In example, I added it to B.graphqls file at the bottom.
Also, you can have only one "type Query" definition in ONE file. In all other schema files you will need to extend that type with "extend type Query" (yeah, I know, it makes sense now...). In which schema file you do that main definition for Query that is not relevant. Everything in this paragraph applies to mutations also.
You can use type defined in one .graphqls file in other .graphqls file. It will get recognized. So, in this example, you can use Person type reference in B.graphqls.
Java resolvers:
import com.coxautodev.graphql.tools.GraphQLQueryResolver;
import graphql.demo.model.Person;
import graphql.demo.service.PersonService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
#Component
public class AQuery implements GraphQLQueryResolver {
#Autowired
private PersonService personService;
public Person getPerson(final int id) {
return this.personService.getPerson(id);
}
}
And second one...
#Component
public class BQuery implements GraphQLQueryResolver {
#Autowired
private BookService bookService;
public List<Book> getBooks(final int count) {
return this.bookService.getBooks(count);
}
}
Names of this classes are not important. We could also have only one class that implements GraphQLQueryResolver and we could implement all query methods from both A.graphqls and B.graphqls files (getBooks() and getPerson() methods). As long as we implement all methods, it's not important in which resolver class we implemented it graphql-java will find it.
Same applies to mutations using GraphQLMutationResolver.
I have full working example (MySQL, Spring Boot, React with Apollo client) on my github, so you can check it out. There is also mysql script for generating database used in project. There is plenty of tables, but there are just for testing purposes, what is important is file structure and files I explained above. If you are not interested in client app, you can test it using graphiql, of course.
https://github.com/dusko-dime/spring-react-graphql
Hope this can be helpful to someone and thanks for helping me once again :)
Multiple schema files
Graphql-java-tools will find all the .graphqls files to build a schema instance. Multiple schema files work out of the box.
Multiple resolvers
Each resolver component must implement GraphQLQueryResolver or GraphQLMutationResolver and be scannable by spring boot. Make sure it has a #Component annotation and is in one of the spring #ComponentScan basePackages.
The error
No method found with any of the following signature means that graphql-tools was not able to find a resolver component with a method matching the signatures. In this case, the HouseQuery resolver is missing the #Component annotation.
Also there can be issue with your Resolver name
Please ensure that you specify the same name of your Resolver method as you have defined in your GraphQL Schema

Jersey vs RestEasy - JAX-RS XML collection root element name change

I have the following Jax-RS end-point:
#XmlRootElement(name = "foobar")
public class Foobar {}
#GET
#Produces(MediaType.APPLICATION_XML)
public Object getFoobars() {
return new GenericEntity<List<FooBar>>(service.getFooBars());
}
Using Jersey 1.x, it used to return:
<foobars>
<foobar>...</foobar>
<foobar>...</foobar>
</foobars>
Now that I use RestEasy, it returns:
<collection>
<foobar>...</foobar>
<foobar>...</foobar>
</collection>
How can I control the root name of a returned GenericEntity<List<X>> in Jax-RS (using Rest-Easy)?
Please note that I also return Json format and I need to keep the API backward-compatible (for exemple the root element is an array in Json and should stay the same)
Found the solution myself after digging a bit in the RestEasy source code. You just have to add the #Wrapped(element="___") annotation to the method:
import org.jboss.resteasy.annotations.providers.jaxb.Wrapped;
#GET
#Produces(MediaType.APPLICATION_XML)
#Wrapped(element = "foobars")
public Object getFoobars() {
return new GenericEntity<List<FooBar>>(service.getFooBars());
}
Works correctly for XML and properly ignored for JSON.

Springfox / Swagger does not resolve polymorphic field

I'm having a simple Spring Boot application with one REST endpoint to return a "Job" object, which contains a list of polymorphics, next to other stuff.
We go Code First approach and try to create the API models to fit our needs. But the generated Api Doc does not represent our model the in it's full complexity, as it does not resolve the list of polymorphics.
The Job object looks like
#Data // Lombok Getters and Setters
public final class Job {
private String foo;
private String bar;
private List<Condition> conditionList;
}
Condition is a parent object for a set of different conditions
public abstract class Condition {
}
Two example implementations of a Condition would be
#Data
public final class Internal extends Condition {
private String nodeId;
}
and
#Data
public final class Timed extends Condition {
private ZonedDateTime timestamp;
}
The REST controller is stupidly simple:
#RestController
#RequestMapping("/hello")
public class MyController {
#GetMapping
public ResponseEntity<Job> getJob() {
return new ResponseEntity<>(new Job(), HttpStatus.OK);
}
}
Now, when I open the Swagger UI and look at the generated definition, the element conditionList is an empty object {}
I tried to use the #JsonSubTypes and #ApiModel on the classed, but there was no difference in the output. I might not have used them correctly, or maybe Swagger is just not able to fulfill the job, or maybe I'm just blind or stupid.
How can I get Swagger to include the Subtypes into the generated api doc?
We "fixed" the problem by changing the structure. So it's more of a workaround.
Instead of using a List of polymorphics, we now use a "container" class, which contains each type as it's own type.
The Condition object became a "container" or "manager" class, instead of a List.
In the Job class, the field is now defined as:
private Condition condition;
The Condition class itself is now
public final class Condition{
private List<Internal> internalConditions;
// etc...
}
And, as example, the Internal lost it's parent type and is now just
public final class Internal{
// Logic...
}
The Swagger generated JSON now looks like this (excerpt):
"Job": {
"Condition": {
"Internal": {
}
"External": {
}
//etc...
}
}
Useful display of polymorphic responses in Swagger UI with Springfox 2.9.2 seems hard (impossible?). Workaround feels reasonable.
OpenAPI 3.0 appears to improve support for polymorphism. To achieve your original goal, I would either
Wait for Springfox to get Open API 3.0 support (issue 2022 in Springfox Github). Unfortunately, the issue has been open since Sept 2017 and there is no indication of Open API 3.0 support being added soon (in Aug 2019).
Change to Spring REST Docs, perhaps adding the restdocs-api-spec extension to generate Open API 3.0.
We have run into similar problems with polymorphism but have not yet attempted to implement a solution based on Spring REST Docs + restdocs-api-spec.

How to access json Post method values in netbeans using Java

I'm struggling with my restful webservice (Java & Netbeans 8.2)
My method looks like:
#POST
#Path("/usedPacking")
#Consumes(MediaType.APPLICATION_JSON)
public void setUsedPackage( ??? ) {
???
}
Actually I would like to receive a json-message as post-data like:
{"PackageID":"12345","Used":"false"}
My question is:
What do I have to replace the "???" with?
For GET-Methods it is:
#QueryParam("ID") String input
Which allows me to access the variable specified as ID by using input.
Everything I've found so far didn't quite address the problem I face..
For a JAXRS webservice you can create an annotated class that maps to your json e.g.
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class Package {
private String packageID;
private Boolean used;
// getters and setters here
}
then ??? will be your class
public void setUsedPackage(Package package)
When you post your json you'll need to specify the Content-type header as application/json
Here's a jaxrs tutorial I found that may help
http://www.logicbig.com/tutorials/java-ee-tutorial/jax-rs/post-example/

JSON deserialize only with at least 2 parameters

I'm implementing a RESTful service application for TomEE Plus 1.7.1 with Jettison as default json provider. I have several facade classes for my entitiy classes to provide CRUD functionalities for each of them. Service facades have been generated by netbeans.
This is the POST method:
#POST
public void create(Course entity) {
super.create(entity);
}
While using this method (to create a new instance in the database) I got following error:
No message body reader has been found for request class Object, ContentType : application/json.
After several hours of trying, I got it to work: I only had to add another parameter to the method, like that:
#POST
public void create(#Context Context uriInfo, Course entity) {
super.create(entity);
}
I don't understand why I had to add this Context parameter. I don't need the context variable, so actually I would like to remove it...
Does anybody know the reason?
Okay, I think I found the solution:
All my rest services have been implemented as facade classes. The abstract facade (super class of all services) has several methods like:
public void create(T entity) { getEntityManager().persist(entity); }
public void edit(T entity) {getEntityManager().merge(entity);}
These methods are used by the facade classes:
public void create(Course entity) {
super.create(entity);
}
public void edit(#PathParam("id") Integer id, Course entity) {
super.edit(entity);
}
(for better viewing I've removed the annotations here)
The difference between these two methods is, that the edit method has a second parameter "id" and so does not override the edit() method of the super class. But the create() method does only have a single parameter which causes override of the super class method "create()". I don't know why, but cxf is now creating two endpoints:
POST http://localhost:8080/webprog/api/course/ -> void create(Course)
POST http://localhost:8080/webprog/api/course/ -> void create(Object)
This is also the reason why I got it working with a secon parameter: The create() method is not getting overriden anymore.
So what i did now, is simply renaming the method in de super class, to not override them in the facade classes.
by the way: all services classes have been created by netbeans generator... maybe there is a bug in it
Here are some of the pointers
Make sure you have jettison jar in your classpath, CXF automatically registers jettison as json provider.
#Context Context is not mandatory, so if you want to access some context parameters you can add.
For Method create add Media Type #Consumes(MediaType.APPLICATION_JSON)
Finally Check why you are getting No message body reader has been found for request class Object Ideally you should have got No message body reader has been found for request class Course(There might be some issues with your class definations)

Categories

Resources