Duplicate _links key in Projection REST responcse - java

to add my links in the Json frame of a projection I added the following class:
#Component
public class ResumeEntityProjectionResourceProcessor implements ResourceProcessor<Resource<ResumeEntity>> {
#Override
public Resource<ResumeEntity> process(Resource<ResumeEntity> resource) {
UriComponents uriComponents = ServletUriComponentsBuilder.fromCurrentContextPath()
.path("/api/entities/search/findEntities")
.buildAndExpand(Long.toString(resource.getContent().getId()));
resource.add(new Link(uriComponents.toUriString(), "findEntities"));
return resource;
}
The problem I had now as a return value was two keys "_links" one with the default links plus my links and the other with my links only.
{
"_embedded": {
"entities": [{
"id": 1696,
"reference": "aaaaaa",
"_links": {
"self": {
"href": "http://localhost:8080/myProject/api/entities/1696{?projection}",
"templated": true
},
"findByParametresValide": {
"href": "http://localhost:8080/myProject/api/entities/search/findEntities"
}
},
"_links": {
"findByParametresValide": {
"href": "http://localhost:8080/myProject/api/entities/search/findEntities"
}
}
}
]
}
}
How I did to keep only the first tag?
the expected result
{
"_embedded": {
"entities": [{
"id": 1696,
"reference": "aaaaaa",
"_links": {
"self": {
"href": "http://localhost:8080/myProject/api/entities/1696{?projection}",
"templated": true
},
"findByParametresValide": {
"href": "http://localhost:8080/myProject/api/entities/search/findEntities"
}
}
}
]
}
}
thanks

#Override
public Resource<ResumeEntity> process(Resource<ResumeEntity> resource) {
if (resource.getLinks().isEmpty()) {
return resource;
}
UriComponents uriComponents = ServletUriComponentsBuilder.fromCurrentContextPath()
.path("/api/entities/search/findEntities")
.buildAndExpand(Long.toString(resource.getContent().getId()));
resource.add(new Link(uriComponents.toUriString(), "findEntities"));
return resource;
}

Related

How to override org.springframework.hateoas.Resources serialize content as List to serialize the name

Jackson is serializing lists with no type erasure. When a GET method returns a Resources object,Is it possible to override to remove the type from Resources
{
"_embedded": {
"customObjList": [
{
"fld1": "a",
"_links": {
"self": {
"href": "http://..."
}
}
}
]
}
}
expected
{
"_embedded": {
"customObj": [
{
"fld1": "a",
"_links": {
"self": {
"href": "http://..."
}
}
}
]
}
}
Use #Relation on Resource class to change the default collection name
#Relation(collectionRelation = "customObj")
class CustomObjResource{
...
}

Springdoc random api-docs generation

I am looking to generate an api that take different content type.
The problem I am facing is that if I run several time my application I have different output documentation
#RestController
public class MyRestController {
#Operation(summary = "GetMyData", operationId = "gettt",
responses = #ApiResponse(responseCode = "204", content = #Content(mediaType = "application/vnd.something")))
#GetMapping(produces = "application/vnd.something")
public ResponseEntity<Void> getSomethingElse() {
return noContent().build();
}
#GetMapping(produces = TEXT_PLAIN_VALUE)
public String get() {
return "some text";
}
#GetMapping(produces = HAL_JSON_VALUE)
public EntityModel<JsonResponse> getHal() {
return EntityModel.of(new JsonResponse(),
linkTo(MyRestController.class).slash("somelink").withSelfRel()
);
}
#GetMapping(produces = APPLICATION_JSON_VALUE)
public JsonResponse getJson() {
return new JsonResponse();
}
}
It currently generate a wrong api-docs
"operationId": "gettt_1_1_1",
"responses": {
"200": {
"content": {
"application/hal+json": {
"schema": {
"$ref": "#/components/schemas/EntityModelJsonResponse"
}
},
"application/json": {
"schema": {
"$ref": "#/components/schemas/JsonResponse"
}
},
"text/plain": {
"schema": {
"type": "string"
}
}
},
"description": "OK"
},
"204": {
"content": {
"application/hal+json": {
"schema": {
"$ref": "#/components/schemas/EntityModelJsonResponse"
}
},
"application/vnd.something": {},
"text/plain": {
"schema": {
"type": "string"
}
}
},
"description": "No Content"
}
},
If I restart my server without changing the code the following response is generated
"operationId": "gettt_1",
"responses": {
"200": {
"content": {
"application/hal+json": {
"schema": {
"$ref": "#/components/schemas/EntityModelJsonResponse"
}
},
"application/json": {
"schema": {
"$ref": "#/components/schemas/JsonResponse"
}
},
"text/plain": {
"schema": {
"type": "string"
}
}
},
"description": "OK"
},
"204": {
"content": {
"application/vnd.something": {}
},
"description": "No Content"
}
},
I would expect that restarting my server will always generate the same documentation
Have you looked at the documentation?
https://springdoc.github.io/springdoc-openapi-demos/springdoc-properties.html#swagger-ui-properties
You can use the swagger-ui properties, without having to override the standard way of sorting (operationsSorter and tagsSorter).
For example:
springdoc.swagger-ui.operationsSorter=method
springdoc.swagger-ui.tagsSorter=alpha
If you want a an order on the server side, you can use OpenApiCustomiser, to sort the elements
This is a sample code that you can customize using Comparators, depending on the sorting logic you want:
Example, for alphabetical order sorting of schemas:
#Bean
public OpenApiCustomiser sortSchemasAlphabetically() {
return openApi -> {
Map<String, Schema> schemas = openApi.getComponents().getSchemas();
openApi.getComponents().setSchemas(new TreeMap<>(schemas));
};
}
Example for sorting tags, in alphabetical order:
#Bean
public OpenApiCustomiser sortTagsAlphabetically() {
return openApi -> openApi.setTags(openApi.getTags()
.stream()
.sorted(Comparator.comparing(tag -> StringUtils.stripAccents(tag.getName())))
.collect(Collectors.toList()));
}
You can have full control on the elements order, and you can sort them depending on your use case...
one other flag mentioned here:
springdoc:
writer-with-order-by-keys

Expecting 'STRING', got 'EOF'

I'm trying to create a config.gateway.json file for a Ubiquiti firewall and I need to upload the file to it. I go to the website jsonlint.com and try to run the following into it:
{
"LOAD_BALANCE": {
"description": "LOAD_BALANCE",
"rule": {
"2000": {
"action": "modify",
"modify": {
"lb-group": "wan2_failover"
},
"source": {
"address": "172.16.7.0/24"
},
"interfaces": {
"bridge": {
"br0": {
"aging": "300",
"bridged-conntrack": "disable",
"hello-time": "2",
"max-age": "20",
"priority": "32768",
"promiscuous": "disable",
"stp": "false"
}
},
"load-balance": {
"group": {
"wan2_failover": {
"flush-on-active": "disable",
"interface": {
"br0": {
"failover-only": "''"
},
"eth0": "''"
},
"lb-local": "enable",
"lb-local-metric-change": "enable"
},
but I get the error Expecting 'STRING', got 'EOF'
If one of the fine Java gurus could help me I'd appreciate it!
It looks like your JSON has some unbalanced curly brackets. Maybe this is what you're after:
{
"LOAD_BALANCE": {
"description": "LOAD_BALANCE",
"rule": {
"2000": {
"action": "modify",
"modify": {
"lb-group": "wan2_failover"
},
"source": {
"address": "172.16.7.0/24"
},
"interfaces": {
"bridge": {
"br0": {
"aging": "300",
"bridged-conntrack": "disable",
"hello-time": "2",
"max-age": "20",
"priority": "32768",
"promiscuous": "disable",
"stp": "false"
}
},
"load-balance": {
"group": {
"wan2_failover": {
"flush-on-active": "disable",
"interface": {
"br0": {
"failover-only": "''"
},
"eth0": "''"
},
"lb-local": "enable",
"lb-local-metric-change": "enable"
}
}
}
}
}
}
}
}

Problems consuming json+hal _embedded resources with spring RestTemplate

I have a simple use case where I would like to consume a resource collection that is represented with json+hal.
I use the spring RestTemplate and have configuired it to use the Jackson2HalModule.
When I debug my code I find that the Response object does contain accurate metadata (e.g. number of pages and resources) and response headers but there is no content or links. I have looked at many articles and guides on the internet over the last day, and I feel that my custom rest template should be working for my use case based on my findings.
If anybody can shed any light on this I would be eternally grateful.
My code for my service is as follows:
#Service
public class EventServiceImpl extends BaseService implements EventService {
private static final String knownEntity = "59d786d642572853721728f6";
private static String SERVICE_URL = "http://EVENTS-SERVER";
private static String EVENTS_PATH = "/events";
#Autowired
#LoadBalanced
protected RestTemplate restTemplate;
#Override
public ResponseEntity<PagedResources<Event>> fetchEventsList() {
// acceptable media type
List<MediaType> acceptableMediaTypes = Arrays.asList(HAL_JSON);
// header
HttpHeaders headers = new HttpHeaders();
headers.setAccept(acceptableMediaTypes);
HttpEntity<String> entity = new HttpEntity<String>(null, headers);
ResponseEntity<PagedResources<Event>> response = getRestTemplateWithHalMessageConverter()
.exchange(SERVICE_URL + EVENTS_PATH, HttpMethod.GET, entity, new ParameterizedTypeReference<PagedResources<Event>>(){});
return response;
}
public RestTemplate getRestTemplateWithHalMessageConverter() {
List<HttpMessageConverter<?>> existingConverters = restTemplate.getMessageConverters();
List<HttpMessageConverter<?>> newConverters = new ArrayList<>();
newConverters.add(getHalMessageConverter());
newConverters.addAll(existingConverters);
restTemplate.setMessageConverters(newConverters);
return restTemplate;
}
private HttpMessageConverter getHalMessageConverter() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new Jackson2HalModule());
MappingJackson2HttpMessageConverter halConverter = new TypeConstrainedMappingJackson2HttpMessageConverter(ResourceSupport.class);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
halConverter.setSupportedMediaTypes(Arrays.asList(HAL_JSON));
halConverter.setObjectMapper(objectMapper);
return halConverter;
}
And my simple model is:
public class Event {
private String name;
private String location;
private int capacity;
public String getName() {
return name;
}
public String getLocation() {
return location;
}
public int getCapacity() {
return capacity;
}
}
For completeness, here is a samle of the shape of the hal+json I am attempting to consume:
{
"_embedded": {
"events": [
{
"name": null,
"location": null,
"capacity": 0,
"currentState": "CANCELLED",
"_links": {
"self": {
"href": "http://192.168.1.6:2221/events/59d786d642572853721728f6"
},
"event": {
"href": "http://192.168.1.6:2221/events/59d786d642572853721728f6"
},
"reinstate": {
"href": "http://192.168.1.6:2221/events/59d786d642572853721728f6/reinstate"
},
"reschedule": {
"href": "http://192.168.1.6:2221/events/59d786d642572853721728f6/reschedule"
}
}
},
{
"name": null,
"location": null,
"capacity": 0,
"currentState": "ADVERTISED",
"_links": {
"self": {
"href": "http://192.168.1.6:2221/events/59d7f14342572812ceca7fc6"
},
"event": {
"href": "http://192.168.1.6:2221/events/59d7f14342572812ceca7fc6"
},
"cancel": {
"href": "http://192.168.1.6:2221/events/59d7f14342572812ceca7fc6/cancel"
},
"reschedule": {
"href": "http://192.168.1.6:2221/events/59d7f14342572812ceca7fc6/reschedule"
}
}
},
{
"name": null,
"location": null,
"capacity": 0,
"currentState": "ADVERTISED",
"_links": {
"self": {
"href": "http://192.168.1.6:2221/events/59d7f14742572812ceca7fc7"
},
"event": {
"href": "http://192.168.1.6:2221/events/59d7f14742572812ceca7fc7"
},
"cancel": {
"href": "http://192.168.1.6:2221/events/59d7f14742572812ceca7fc7/cancel"
},
"reschedule": {
"href": "http://192.168.1.6:2221/events/59d7f14742572812ceca7fc7/reschedule"
}
}
},
{
"name": null,
"location": null,
"capacity": 0,
"currentState": "ADVERTISED",
"_links": {
"self": {
"href": "http://192.168.1.6:2221/events/59d7f14c42572812ceca7fc8"
},
"event": {
"href": "http://192.168.1.6:2221/events/59d7f14c42572812ceca7fc8"
},
"cancel": {
"href": "http://192.168.1.6:2221/events/59d7f14c42572812ceca7fc8/cancel"
},
"reschedule": {
"href": "http://192.168.1.6:2221/events/59d7f14c42572812ceca7fc8/reschedule"
}
}
}
]
},
"_links": {
"self": {
"href": "http://192.168.1.6:2221/events{?page,size,sort}",
"templated": true
},
"profile": {
"href": "http://192.168.1.6:2221/profile/events"
}
},
"page": {
"size": 20,
"totalElements": 4,
"totalPages": 1,
"number": 0
}
}
EDIT: I can consume an individual Event with no problems.
I had a similar problem and I ended up using org.springframework.hateoas.Resources
For my example below, this object is located in org.springframework.hateoas:spring-hateoas:jar:0.25.2.RELEASE
which is being pulled in from org.springframework.boot:spring-boot-starter-data-rest:jar:2.1.7.RELEASE, so there's a good chance its already in your classpath assuming you declare the spring-boot-starter-data-rest dependency.
Here's a simple example (using your info):
RestTemplate restTemplate = new RestTemplate();
Resources<Event> resourceEvents = restTemplate.getForObject("http://192.168.1.6:2221/events", Resources.class);
List<Event> events = new ArrayList<>(resourceEvents.getContent());
There are probably some gotchas the make this not so straight forward, but hopefully this provides a start to solving your problem.

Empty collection returns single object in spring data rest instead of empty array

I am using spring data rest for my application.
I have disabled to hal json type by using the following:-
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.useHalAsDefaultJsonMediaType(false);
}
My response looks like the following for empty object:-
{
"links": [
{
"rel": "self",
"href": "http://localhost:8081/users"
},
{
"rel": "profile",
"href": "http://localhost:8081/profile/users"
},
{
"rel": "search",
"href": "http://localhost:8081/users/search"
}
],
"content": [
{
"relTargetType": "com.sample.User",
"collectionValue": true,
"value": [ ]
}
]
}
When I have data for user, It looks like the following:-
links": [
{
"rel": "self",
"href": "http://localhost:8081/users"
},
{
"rel": "profile",
"href": "http://localhost:8081/profile/users"
},
{
"rel": "search",
"href": "http://localhost:8081/users/search"
}
],
"content": [
{
"id": 2,
"key": "wire",
"name": "Wireless",
"content": [ ],
"links": [
{
"rel": "self",
"href": "http://localhost:8081/users/2"
},
{
"rel": "user",
"href": "http://localhost:8081/users/2"
}
]
}..
My concern is to have empty content collection like as below:-
{
"links": [
{
"rel": "self",
"href": "http://localhost:8081/users"
},
{
"rel": "profile",
"href": "http://localhost:8081/profile/users"
},
{
"rel": "search",
"href": "http://localhost:8081/users/search"
}
],
"content": []
}
How can I handle empty collection in spring data rest ? Kindly help me

Categories