How to consume Spring HATEOAS REST resources containing links to another resources? - java

I have /studentCourses endpoint on the server (built with Spring Data REST) which returns the following content:
{
"_embedded" : {
"studentCourses" : [
{
"uid" : "5f23abe9-b24e-4e76-86b0-d539950a0a41",
"registrationDate" : "7/23/2016",
"_links" : {
"self" : {
"href" : "http://localhost:8080/studentCourses/5f23abe9-b24e-4e76-86b0-d539950a0a41"
},
"studentCourse" : {
"href" : "http://localhost:8080/studentCourses/5f23abe9-b24e-4e76-86b0-d539950a0a41"
},
"course" : {
"href" : "http://localhost:8080/studentCourses/5f23abe9-b24e-4e76-86b0-d539950a0a41/course"
},
"student" : {
"href" : "http://localhost:8080/studentCourses/5f23abe9-b24e-4e76-86b0-d539950a0a41/student"
}
}
},
{
...
},
...
]
},
"_links" : {
"self" : {
"href" : "http://localhost:8080/studentCourses"
},
"profile" : {
"href" : "http://localhost:8080/profile/studentCourses"
}
},
"page" : {
...
}
}
And the following client code:
class StudentCourseDTO {
String uuid;
String registrationDate;
StudentDTO student; // contains uuid, firstName, lastName, etc.
CourseDTO course; // contains uuid, name, etc.
// getters, setters
}
RestTemplate restTemplate() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new Jackson2HalModule());
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
MappingJackson2HttpMessageConverter messageConverter =
new MappingJackson2HttpMessageConverter();
messageConverter.setObjectMapper(objectMapper);
messageConverter.setSupportedMediaTypes(Arrays.asList(MediaTypes.HAL_JSON));
return new RestTemplate(Arrays.asList(messageConverter));
}
...
Collection<StudentCourseDTO> studentCourses = restTemplate().exchange(
"http://localhost:8080/studentCourses",
HttpMethod.GET, null,
new ParameterizedTypeReference<PagedResources<StudentCourseDTO>>() {})
.getBody().getContent();
The problem is that StudentCourseDTO.student and StudentCourseDTO.course are always null, but StudentCourseDTO.uuid and StudentCourseDTO.registrationDate are retrieved correctly from the server.
Anyone has an idea what I have missed?
I think there must be someway to tell RestTemplate to automatically follow the links in the returned content like student and course in the example above, but I haven't found a way to do this.

Just because there are links that does not mean they are automatically followed.
I would change the StudentCourseDTO to:
class StudentCourseDTO {
String uuid;
String registrationDate;
}
And then you would deserialize the response to a
PagedResources<Resource<StudentCourseDTO>> studentCourses = restTemplate().exchange(
"http://localhost:8080/studentCourses",
HttpMethod.GET, null,
new ParameterizedTypeReference<PagedResources<Resource<StudentCourseDTO>>>() {})
.getBody().getContent();
For each Resource<StudentCourseDTO> you can then follow the links for studentand course, e.g. by using the RestTemplate to retrieve the resources.
Of course this gives you two additional calls per response item - but the only way to avoid this is to change the service to embed this information in the list resource.

Related

Spring Data REST - Unrecognized field "_embedded" by consuming list of entities, Java HATEOAS

I am trying to consume a list of entities from the following REST HAL response:
{
"_embedded" : {
"posts" : [ {
"branch" : 1,
"article" : "aaaaaaa",
"featuredImage" : "aaaaaaa",
"authorId" : 1,
"datePublished" : "2020-05-05T09:11:13.336+0000",
"commentsEnabled" : true,
"enabled" : false,
"views" : 0,
"snippetTitle" : null,
"snippetDescription" : null,
"comments" : null,
"_links" : {
"self" : {
"href" : "http://localhost:8081/posts/1"
},
"post" : {
"href" : "http://localhost:8081/posts/1"
},
"categories" : {
"href" : "http://localhost:8081/posts/1/categories"
}
}
}, {
"branch" : 1,
"article" : "aaaaaaa",
"featuredImage" : "aaaaaaa",
"authorId" : 1,
"datePublished" : "2020-05-05T10:45:15.999+0000",
"commentsEnabled" : true,
"enabled" : false,
"views" : 0,
"snippetTitle" : null,
"snippetDescription" : null,
"comments" : null,
"_links" : {
"self" : {
"href" : "http://localhost:8081/posts/3"
},
"post" : {
"href" : "http://localhost:8081/posts/3"
},
"categories" : {
"href" : "http://localhost:8081/posts/3/categories"
}
}
} ]
},
"_links" : {
"self" : {
"href" : "http://localhost:8081/posts/search/byAuthorId?authorId=1&page=0&size=10"
}
},
"page" : {
"size" : 10,
"totalElements" : 3,
"totalPages" : 1,
"number" : 0
}
}
I would like to map these entities to this class:
#Setter
#Getter
#AllArgsConstructor
public class Post {
private int id;
private int branch;
private String article;
private Date datePublished;
private String featuredImage;
private Boolean commentsEnabled;
private Boolean enabled;
private int views;
private String snippetTitle;
private String snippetDescription;
}
However, I keep getting the error:
Unrecognized field "_embedded" (class
org.springframework.hateoas.PagedModel), not marked as ignorable (3
known properties: "links", "page", "content"])
With this code:
ObjectMapper mapper = new ObjectMapper();
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
messageConverter.setSupportedMediaTypes(MediaType.parseMediaTypes("application/hal+json"));
messageConverter.setObjectMapper(mapper);
ResponseEntity<PagedModel<Post>> responseEntity =
new RestTemplate(Arrays.asList(messageConverter)).exchange(uri, HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference<PagedModel<Post>>() {});
The versions are:
Jackson-databind version: 2.11.0
Spring-hateoas version: 1.0.5.RELEASE
Any help would be appreciated!
Response structure seems like PagedResources<T> type.
Use org.springframework.hateoas.PagedResources in ParameterizedTypeReference
ResponseEntity<PagedResources<Post>> responseEntity =
new RestTemplate(Arrays.asList(messageConverter)).exchange(uri, HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference<PagedResources<Post>>() {});

Jersey project Swagger-UI doesn't send #HeaderParam while #PathParam is sent

Java Jersey project using following Swagger Core:
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-jaxrs2</artifactId>
<version>2.0.2</version>
</dependency>
the document link goes to "openapi.json". Swagger-UI dist ver 3.20.5 is downloaded from here. Java code is like this:
#Path("/auth")
public class TestConttroller {
#GET
#Path("/{id}")
#Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public Response testGet(
#DefaultValue("") #HeaderParam("Authorization") String a,
#DefaultValue("") #PathParam("id") String id)
{
return Response.ok().build();
}
When sending request from Postman everything works. But when from following Swagger-UI, the header-param string "a" is an empty string while the path-param string is good. The part in openapi.json is here:
"paths" : {
"/auth/{id}" : {
"get" : {
"operationId" : "testGet",
"parameters" : [ {
"name" : "Authorization",
"in" : "header",
"schema" : {
"type" : "string",
"default" : ""
}
}, {
"name" : "id",
"in" : "path",
"required" : true,
"schema" : {
"type" : "string",
"default" : ""
}
} ],
"responses" : {
"default" : {
"description" : "default response",
"content" : {
"application/json" : { },
"application/xml" : { }
}
}
}
}
}
Check with WireShark found the header is not in request at all. Should the problem in Swagger-UI?
#HeaderParam("Authorization") is like a keyword of HTTP? when I used other name like "Auth" then it works.

Swagger annotations describe default response of an operation

I want to define "default" response of an operation with #ApiResponse annotation.
I'm using swagger annotations 1.5.x and want to generate something like this (look at default response):
"get" : {
...
"responses" : {
"200" : {
"description" : "successful operation",
"schema" : {
"type" : "array",
"items" : {
"$ref" : "#/definitions/Address"
}
}
},
"default" : {
"description" : "unsuccessful operation",
"schema" : {
"$ref" : "#/definitions/ErrorResponse"
}
}
},
...
}
But I don't know how to do that, because #ApiResponse(code = ...) annotation expects only numbers not Strings.
My Java code:
...
#ApiResponses(value = {
#ApiResponse(code = 200, message = "successful operation", response = Address.class, responseContainer = "List"),
#ApiResponse(code = "default", message = "unsuccessful operation", response = ErrorMessage.class),
...})
public Response getAllAddresses() throws SQLException {
...
}
So is there any way to specify "default" ApiResponse in Swagger annotations 1.5.x?

swagger core 2.0 disable security for endpoint

I am using Swagger Core 2.0 to generate openAPI 3.0 definition files and
I am having trouble to disable "security" for a particular endpoint.
I have my securitySchemes and root security element defined:
{
"openapi" : "3.0.1",
"security" : [ {
"JWT" : [ ]
} ],
"paths" : {
"/auth" : {
"post" : {
"summary" : "authenticate user",
"operationId" : "authenticate",
"requestBody" : {
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/AuthenticationRequest"
}
}
}
},
"responses" : {
"200" : {
"description" : "when user is successfully authenticated",
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/AuthenticateUserOutput"
}
}
}
},
"401" : {
"description" : "when email/password not valid or user is blocked/inactive"
}
}
}
},
},
"components" : {
"schemas" : {
"AuthenticateUserOutput" : {
"type" : "object",
"properties" : {
"token" : {
"type" : "string"
},
"lastLoginAt" : {
"type" : "string",
"format" : "date-time"
},
"lastProjectId" : {
"type" : "string"
}
}
},
...,
"AuthenticationRequest" : {
"required" : [ "email", "password" ],
"type" : "object",
"properties" : {
"email" : {
"type" : "string"
},
"password" : {
"type" : "string"
}
}
}
},
"securitySchemes" : {
"JWT" : {
"type" : "http",
"scheme" : "bearer",
"bearerFormat" : "JWT"
}
}
}
}
According to OPEN API 3 spec https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#securityRequirementObject i shall be able to override global "security requirement" for an individual operation. I would like to "disable" JWT security for a few operations and according to https://github.com/OAI/OpenAPI-Specification/blob/3.0.1/versions/3.0.1.md#securityRequirementObject it can be done:
To remove a top-level security declaration, an empty array can be used.
Unfortunately I am struggling to define "empty security array" on Operation level using annotations...
I tried to specify
security = {}
or
security = #SecurityRequirement(name ="")
but no security element within operation is generated at all....
Any idea ?
Below is my java code (i use for swagger dropwizard integration) that allows one to have SecurityScheme and root level security defined
Info info = new Info()
.title("someTitle")
.description("some description")
.version("1.0")
SecurityScheme jwtSecurity = new SecurityScheme()
.type(SecurityScheme.Type.HTTP)
.name("Authorization")
.in(SecurityScheme.In.HEADER)
.scheme("bearer")
.bearerFormat("JWT");
String securitySchemaName = "JWT";
OpenAPI oas = new OpenAPI()
.info(info)
.components(new Components().addSecuritySchemes(securitySchemaName, jwtSecurity))
.addSecurityItem(new SecurityRequirement().addList(securitySchemaName));
SwaggerConfiguration oasConfig = new SwaggerConfiguration()
.openAPI(oas)
.prettyPrint(true)
.resourcePackages(Stream.of("my.resources.package")
.collect(Collectors.toSet()));
environment.jersey().register(new OpenApiResource()
.openApiConfiguration(oasConfig));
Then on a few dedicated endpoints i would like to disable security, so i am trying with:
#POST
#Operation(
summary = "authenticate user",
responses = {
#ApiResponse(responseCode = "200", description = "when user is successfully authenticated",
content = #Content(schema = #Schema(implementation = AuthenticateUserOutput.class))),
#ApiResponse(responseCode = "401", description = "when email/password not valid or user is blocked/inactive"),
}
,security = what to put here ?
)
if you want to do it in yml swagger hub style you can put
security: []
in that endpoint after request body, So swagger considers it as no auth for that particular path or endpoint.
According to a comment over on the OpenAPI-Specifiction GitHub project. It should be possible.
Did you try this?
security: [
{}
]
I had the same problem, on a Java SpringBoot webapp (dependency org.springdoc:springdoc-openapi-ui:1.5.2). As per this answer, I solved it adding an empty #SecurityRequirements annotation on the operation. For example:
#POST
#SecurityRequirements
#Operation(
summary = "authenticate user",
responses = {
#ApiResponse(responseCode = "200", description = "when user is successfully authenticated",
content = #Content(schema = #Schema(implementation = AuthenticateUserOutput.class))),
#ApiResponse(responseCode = "401", description = "when email/password not valid or user is blocked/inactive"),
} )
)

Jackson JSON deserialization

I am trying to deserialize the following Json payload using Jackson:
[{
"owner" : "345MWyh7w4hY98W6",
"url" : "http://www.google.com",
"items" : [{
"createdAt" : 1342099411415,
"amount" : 1,
"note" : "item 1",
"product" : "car"
}, {
"createdAt" : 1342100231111,
"amount" : 4,
"note" : "item 2",
"product" : "wheels"
}],
"createdAt" : 1342096943777,
"title" : "Car order",
"description" : "car order",
"id" : "98hw85Y945e6U358"
}]
I am using the following code to deserialize:
ObjectMapper mapper = new ObjectMapper().configure(
DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
List<Order> result = null;
try {
result = mapper.readValue(jsonString,new TypeReference<List<Order>>() { });
} catch (IOException e) {
e.printStackTrace();
}
However I get the following error:
org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor aroundWriteTo
SEVERE: MessageBodyWriter not found for media type=application/json, type=class org.glassfish.jersey.client.InboundJaxrsResponse, genericTyp
e=class org.glassfish.jersey.client.InboundJaxrsResponse.
Feb 06, 2015 8:14:45 PM org.glassfish.jersey.filter.LoggingFilter log
The class Orders is generated by RAML -> JAX-RS maven plug-in and this does not have a zero argument constructor. Is there any way of doing this de-serialization other than modifying the generated class to add a zero-args constructor ?
Was able to fix with a work-around. I created a wrapper DTO over the Order as follows:
#XmlRootElement
public class OrderDTO {
private List<Order> orderList;
public List<Order> getOrderList() {
return orderList;
}
public void setOrderList(List<Order> orderList) {
this.orderList = orderList;
}
public void addOrder(Order order){
orderList.add(order);
}
public OrderDTO() {
super();
orderList = new ArrayList<Order>();
}
}
here I was able to add the zero-args constructor. Now it works :).

Categories

Resources