spring hateoas generates different responses for collection or pojo - spring

I have two classes
import org.springframework.hateoas.ResourceSupport;
public class A{}
public class B{}
public class AResource extends ResourceSupport {
private final A a;
}
public class BResource extends ResourceSupport {
private final B b;
}
#Controller
public class Controller {
#RequestMapping
#ResponseBody
public Set<AResource> giveMeColl() {
}
#RequestMapping
#ResponseBody
public BResource giveMeSingle() {
}
}
both responses add links object but for resource A is "links" and for resource B is "_link" and also structure changes
//RESPONSE FOR A
[
{
"content":{
//my fancy object
},
"links":[
{
"rel": "self",
"href": "http://localhost:8080/myid/22"
}
]
}
]
{
"content":{
//my fancy object
},
"_links":[
{
"self": "http://localhost:8080/myid/22/someelse/33"
}]
}
both resources are constructed with assemblers and both are adding the link from the ids
AResource aresource = new AResource(a);
resource.add(linkTo(methodOn(Controller.class).giveMeColl()).withSelfRel());
BResource bresource = new BResource(b);
resource.add(linkTo(methodOn(Controller.class).giveMeSingle()).withSelfRel());
Response headers for a is
"content-type": "application/json;charset=UTF-8"
and for b
"content-type": "application/hal+json;charset=UTF-8"
Could it be because returning an array is not really Restful? as Some post suggest
p.s. I have added and removed #EnableHypermediaSupport but doesn't seem to affect the problem.

"_links" follows the HAL specification. Spring HATEOAS includes a dedicated serializer for that, but it is only used for classes that extend ResourceSupport.
Returning a simple array is not exactly "unRESTful", but it doesn't meet the REST maturity level 3 (Hypermedia controls). In order to achieve that you can wrap the collection into a Resources instance, which extends ResourceSupport. You should get the same link serialization for both types then.

Related

Cannot return String from #RestController method

I'm working with Spring Boot 2.4.5, MyBatis 3.5.6 and Java 8. When trying to return a String from a #RestController, an obscure error shows up in the returned HttpErrorResponse.
The method tries to obtain the value via MyBatis, i.e., a method in a DAO object that acts as a #Mapper.
My controller method:
#RestController
#RequestMapping("/api/myAPI")
public class MyController{
#Resource
private MyService service;
#GetMapping(value = "myString")
public String getBillingCompany() {
return this.service.getDAO().getMyString();
}
}
My DAO:
#Repository
#Mapper
public interface MyDAO{
String getMyString();
}
...and the MyBatis mapper:
<mapper namespace="com.package.MyDAO">
<select id="getMyString" resultType="String">
SELECT 'My desired result' FROM A_TABLE
</select>
...
</mapper>
The HttpErrorResponse:
HttpErrorResponse: {
"headers": {
"normalizedNames": {},
"lazyUpdate": null
},
"status": 200,
"statusText": "OK",
"url": "http://localhost:4200/api/myAPI/myString",
"ok": false,
"name": "HttpErrorResponse",
"message": "Http failure during parsing for http://localhost:4200/api/myAPI/myString",
"error": {
"error": { SyntaxError: Unexpected number in JSON at position 2
at JSON.parse (<anonymous>)
at XMLHttpRequest.onLoad (http://localhost:4200/vendor.js:18158:51)
at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (http://localhost:4200/polyfills.js:21266:35)
at Object.onInvokeTask (http://localhost:4200/vendor.js:74037:33)
at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (http://localhost:4200/polyfills.js:21265:40)
at Zone.push../node_modules/zone.js/dist/zone.js.Zone.runTask (http://localhost:4200/polyfills.js:21033:51)
at ZoneTask.push../node_modules/zone.js/dist/zone.js.ZoneTask.invokeTask [as invoke] (http://localhost:4200/polyfills.js:21348:38)
at invokeTask (http://localhost:4200/polyfills.js:22516:18)
at XMLHttpRequest.globalZoneAwareCallback (http://localhost:4200/polyfills.js:22553:25)
},
"text": "My desired result"
}
}
Nonetheless, if I ask the controller and the DAO methods to return an int, it all works flawlessly.
Due to this, I suspected that the issue has to do with non-primitive types "namespacing", so I've tried to set a typeAlias in the MyBatis configuration, to no avail:
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<typeAlias type="java.lang.String" alias="String"/>
</typeAliases>
</configuration>
Anyways, I'm under the impression that both MyBatis and Spring should be already smart enough to know what a String is. I've successfully returned collections of objects (Maps and Lists) and POJOs in the past.
Any ideas on what I'm lacking or not seeing? Thanks in advance.
Edit:
The only thing that has worked for me so far is similar to what #emeraldjava proposed. I've built a wrapper upon an existing one in a dependency, fetching the data in my Front:
#RestController
#RequestMapping("/api/myAPI")
public class MyController{
#Resource
private MyService service;
#GetMapping(value = "myString")
public Result<String> getBillingCompany() {
return new Result<>(this.service.getDAO().getMyString());
}
}
public class Result<T> extends ServiceResult {
public Result(T data) {
this.setData(data);
}
}
The already existing wrapper in a dependency:
public class ServiceResult {
private Object data;
...
public void setData(Object data) {
this.data = data;
}
public Object getData() {
return this.data;
}
}
I'd suggest you update your Controller method to a ResponseEntity which wraps the string.
#RestController
#RequestMapping("/api/myAPI")
public class MyController{
#Resource
private MyService service;
#GetMapping(value = "myString")
public ResponseEntity getBillingCompany() {
return new ResponseEntity<Object>(this.service.getDAO().getMyString(), HttpStatus.OK);
}
}
I think the problem is that the table you're querying has multiple rows, so the result of your query will be a List of string, containing "My desired result" on each element, in which the size of the list is equal to the number of rows in the table. In order to force a single result, just change the query to:
<select id="getMyString" resultType="String">
SELECT MAX('My desired result') FROM A_TABLE
</select>

How do I create a Spring Boot endpoint that can accept multiple different types as RequestBody?

I have 2 messages like this...
{
"type":"MessageString",
"message": {
"value":"..."
}
}
and
{
"type":"MessageObject",
"message": {
"value": { ... }
}
}
Then I have a Spring Boot endpoint like...
#Controller
#RequestMapping("...")
public class MyController {
#PostMapping(
consumes = MediaType.APPLICATION_JSON_VALUE
)
#ResponseBody
public String processMessage(
#RequestBody (?) wrapper
){
...
}
}
I would like to know what kind of classes I can use to accept both types? I had it working like this....
class MessageWrapper<T extends MessageType>{
private T message;
}
But then I still have to declare the type in the response body like...
#RequestBody MessageWrapper<Object> wrapper
or
#RequestBody MessageWrapper<String> wrapper
So this wouldn't work because I can't have 2 response bodies.
I am trying to keep this as simple as possible so I would like to avoid making the #RequestBody a string then parsing it inside the controller.

SPRING HATEOS LINKS (HTTPS vs HTTP)

I have a simple rest service which returns the response when a GET operation is performed along with the HATEOS links. When the service is deployed on the server and is accessed through a load balancer url (Https), the links section contains the url with "http" instead of "https". Any example solution/code for this particular scneario would help me.
For Example:
If a GET operation is performed by using the url(https://servicename.example.com) then I would expect the same url to be in the links section. But I receive something like below.
Response Body:
{
"fieldA": null,
"fieldB": null,
"links": [
{
"rel": "self",
"href": "http://servicename.example.com" // It has to be https
}
]
}
Below is the line of code that adds the links to the Rest Resource that will be exposed to the consumer.
resource.getLinks().add(linkTo(methodOn(ExampleController.class).methodInExampleController(arg a, arg b)).withSelfRel());
The resource that is being exposed to the consumer extends another class"ResourceSupport" where a field called links is declared which is of type List.
Example of Resource class:
imnport com.examplepackage.ResourceSupport
public SampleResource extends ResourceSupport{
private String fieldA,
private String fieldB,
//setters and getters
}
Example of the class where HATEOS Links are declared
import org.springframework.hateoas.Identifiable;
import org.springframework.hateoas.Link;
public ResourceSupport implements Identifiable<Link>{
#JsonInclude(Include.NON_EMPTY)
private final List<Link> links = new ArrayList();
#JsonProperty("links")
public List<Link> getLinks() {
return this.links;
}
}
SPRING HATEOS version: spring-hateos0.24.0.RELEASE

Spring MVC REST - standard request body wrapper

What is the best way to achieve a common REST request body unwrap effect with Spring MVC #RestController ?
In other words, assuming I have the following controller:
#RestController
public class MyController {
#RequestMapping(value = "/", method = POST)
public Object hello(#RequestBody MyDTO dto) {
...
}
}
I would like the actual post body to be:
{
"version": "1.0",
"payload": {
...
}
}
Which can be represented by the following class:
public class ApiRequestDTO<TPayload> {
private String version;
private TPayload payload;
...
// Getters and Setters...
...
}
So in this particular case the client would send an instance of ApiRequestDTO<MyDTO>.
I achieved the opposite (response body wrapper) using a #ControllerAdvice, but I notice that it won't work exactly for the request body. One possible approach I can see is to decorate all the relevant message converters. But I was wondering if there is a better approach?

Handle subclass of arraylist as an array

We have an Jersey REST-API annotated with Swagger annotations.
The swagger UI works with the exception of one class. This class extends ArrayList<T>. And I expect that this class is handled by swagger as an array. But this is not the case. It is handled like any other complex object and its public members are shown in swagger-ui.
Here a code summary of my class:
#JsonAutoDetect(getterVisibility = Visibility.NONE, fieldVisibility = Visibility.NONE, isGetterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE)
public class ArraySubClass extends ArrayList<ElementClass> implements IAnInterface {
// constructors...
public A getA() {
// ...
}
}
Swagger shows this class as model object:
"ArraySubClass": {
"id": "ArraySubClass",
"description": "",
"properties": {
"a": {
"$ref": "A"
},
"empty": {
"type": "boolean"
},
"size": {
"type": "integer",
"format": "int32"
}
}
}
What can I do, so that swagger does handle the class (extending from ArrayList<T>) as an array and that swagger just shows its elements and not other properties?
you can rather use the class ElementClass directly in swagger as this
#ApiOperation(value = "/yourEndPoint", notes = "comment for endpoint", response = ElementClass.class, responseContainer = "List")
So that you'll present the content of your class as a list in the return and not your ArraySubClass that may contains others datas / stuff you want to hide ! :)
For more info : see response container

Resources