Cannot return String from #RestController method - spring

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>

Related

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.

How to test SpringMvc controller that returns pageable result with MockMvc?

Using spring-boot 2.2.4.
I have a SpringMvc Controller that returns pageable objects:
#RestController
#RequestMapping("/call-data")
public class CallDataController {
#GetMapping
public Page<CallDataDto> findAll(Pageable page) {
...
Trying to test it with MockMvc:
ObjectMapper mapper = new ObjectMapper();
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/call-data")).andReturn();
Page<CallDataDto> myDtos = mapper.readValue(mvcResult.getResponse().getContentAsString(), TypeUtils.pageTypeRef());
...
public class TypeUtils {
public static <T> TypeReference<RestResponsePage<T>> pageTypeRef() {
return new TypeReference<>() {
};
}
But instead of page with dto objects I get a page with LinkedHashMaps.
So how to get the page with dto objects?
Similar question: ObjectMapper using TypeReference not working when passed type in generic method
You can solve the problem by replacing the type parameter(T) with CallDataDto.
public class TypeUtils {
public static TypeReference<RestResponsePage<CallDataDto>> pageTypeRef() {
return new TypeReference<>() {
};
}
Type parameters(e.g. <T>) don't exist at runtime so you have to replace them with some concrete values so that Jackson can obtain full generics type information.

Spring + MongoDB tag #Query with $group not working

NOTE: Go down in order to see the edited message.
I'm trying to imitate this query:
db.sentiments.aggregate([
{"$group" : {_id:{theme_id:"$theme",sentiment_id:"$sentiment"}, count:{$sum:1}}},
{"$sort":{"_id.theme_id":1}} ])
This is the code that I had generated in order to imitate it:
#RepositoryRestResource(collectionResourceRel = "sentiments", path = "sentiments")
public interface SentimentsRepository extends MongoRepository<Sentiments, String> {
Long countByTheme(#Param("theme") String theme);
#Query(value ="[\n" +
" {\"$group\" : {_id:{theme_id:\"$theme\",sentiment_id:\"$sentiment\"}, count:{$sum:1}}},\n" +
"\t{\"$sort\":{\"_id.theme_id\":1}}\n" +
"]",count = true)
List<Object> comptarSentiments();
}
Well this code is returning me this error:
"exception": "org.springframework.data.mongodb.UncategorizedMongoDbException",
"message": "Can't canonicalize query: BadValue unknown operator: $group; nested exception is com.mongodb.MongoException: Can't canonicalize query: BadValue unknown operator: $group",
Actually I'm a begginer in what refers to the use of Spring so I'm very lost, does any one know what should I do?
Thanks and sorry for my bad english, not my native language.
[EDIT]----------------------------------------
Just as the comment wrote by Shawn Clark It's not possible to do it this way, in order to achieve that you will need to create a customRepository.
What's the difference between Spring Data's MongoTemplate and MongoRepository?
I have been trying to do it this way but something doesn't seem to be correct, here is my new code:
#RepositoryRestResource(collectionResourceRel = "sentiments", path = "sentiments")
public interface SentimentsRepository extends CrudRepository<Sentiments, String>, CustomSentimentsRepository {
//Other methods...
}
public interface CustomSentimentsRepository {
List<CountResult> yourCustomMethod();
class CountResult{
String theme;
String sentiment;
int total;
}
}
public class SentimentsRepositoryImpl implements CustomSentimentsRepository {
private final MongoOperations operations;
#Autowired
public SentimentsRepositoryImpl(MongoOperations operations) {
Assert.notNull(operations, "MongoOperations must not be null!");
this.operations = operations;
}
#Override
public List<CountResult> yourCustomMethod(){
Aggregation agg = Aggregation.newAggregation(
Aggregation.group("theme","sentiment").count().as("total"),
Aggregation.project("theme","sentiment").and("total").previousOperation(),
Aggregation.sort(Sort.Direction.DESC, "theme")
);
//Convert the aggregation result into a List
AggregationResults<CountResult> groupResults
= operations.aggregate(agg,"sentiments", CountResult.class);
//List<CountResult> result = groupResults.getMappedResults();
return groupResults.getMappedResults();
}
}
I'm not even able to debbug this code and I'm always getting a 404.
Based on the information I have found you can't do that complex of a #Query on a MongoRepository method. In this case you would want to create a class and implement your comptarSentiments() method using the mongoTemplate to query the data store with your aggregate function. Then create a controller class that exposes a REST endpoint and have it call the repository.
Once you get to doing complex queries in Mongo you lose the ease of #RepositoryRestResource and have to go back to wiring the REST endpoint to the repository yourself.
Spring Data REST : custom query for MongoDB repository
Implementing custom methods of Spring Data repository and exposing them through REST
I finally managed to solve the problem, seems like it was related with the controller and the type of the atribute "total" from the innerClass CountResult, it needs to be a String (this is very important, otherwise the Aggregation.project will fail). Here goes the final code:
public interface CustomSentimentsRepository {
List<CountResult> myCountGroupByThemeAndSentiment();
class CountResult{
public String theme;
public String sentiment;
public String total;
}
}
public class SentimentsRepositoryImpl implements CustomSentimentsRepository {
private final MongoTemplate mongoTemplate;
#Autowired
public SentimentsRepositoryImpl(MongoTemplate mongoTemplate) {
this.mongoTemplate = mongoTemplate;
}
#Override
public List<CountResult> myCountGroupByThemeAndSentiment(){
Aggregation agg = Aggregation.newAggregation(
Aggregation.group("theme","sentiment").count().as("total"),
Aggregation.project("theme","sentiment").andInclude("total"),
Aggregation.sort(Sort.Direction.ASC,"theme","sentiment")
);
AggregationResults<CountResult> groupResults
= mongoTemplate.aggregate(agg,"sentiments", CountResult.class);
return groupResults.getMappedResults();
}
}
#RepositoryRestResource(collectionResourceRel = "sentiments", path = "sentiments")
public interface SentimentsRepository extends CrudRepository<Sentiments, String>, CustomSentimentsRepository {
//Other methods
}
#RestController
#RequestMapping(value = "sentiments/search")
public class ChartsController {
#Autowired
private SentimentsRepository sentimentsRepository;
#RequestMapping(value = "myCountGroupByThemeAndSentiment", method = RequestMethod.GET)
public ResponseEntity<?> yourCustomMethod() {
List<?> count=sentimentsRepository.myCountGroupByThemeAndSentiment();
return new ResponseEntity(count, HttpStatus.OK);
}
}
You can use #Aggrgation available in spring data mongodb 2.2.X versions:
#Aggregation(pipeline = {"{ '$group': { '_id' : '$lastname', names : { $addToSet : '$?0' } } }", "{ '$sort' : { 'lastname' : -1 } }"}) List<PersonAggregate> groupByLastnameAnd(String property);

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?

Using custom JSON serialization for Spring WebSocket #MessageMapping/#SubscribeMapping

This works fine
In my Spring-based application, I have set up a HTTP-based REST endpoint. This endpoint "speaks" JSON:
#Controller
public class HttpRestController implements RestController {
#Override
#RequestMapping(value = "/users/{user}", method = RequestMethod.GET)
#ResponseBody
public getUser(#PathVariable User user) {
User jsonFriendlyUser = new JacksonAnnotatedUser(user);
return jsonFriendlyUser;
}
}
As these JSON payloads have to follow unusual naming conventions, I used annotations such as #JsonRootName and #JsonProperty to customize the serialized property names:
#JsonRootName("uussaaar")
public class JacksonAnnotatedUser implements User {
//...
public int getId() {
return id;
}
#JsonProperty("naammee")
public String getName() {
return name;
}
#JsonSerialize(using = FriendsJsonSerializer.class )
public Set<User> getFriends() {
return friends;
}
#JsonIgnore
public String getUnimportantProperty() {
return unimportantProperty;
}
}
With this custom JSON metadata, querying /users/123 via HTTP returns the following JSON payload:
{"uussaaar":
{
"id":123,
"naammee":"Charlie",
"friends": [456, 789]
}
}
The following doesn't work as expected
Now I am playing around with Spring's WebSocket support: I want to create a STOMP-based REST endpoint. Therefore i created a StompRestController like this:
#Controller
public class StompRestController implements RestController {
#Override
#SubscribeMapping("/users/{user}")
public getUser(#DestinationVariable User user) { // assuming this conversion works
User jsonFriendlyUser = new JacksonAnnotatedUser(user);
return jsonFriendlyUser;
}
I would have expected for #SubscribeMapping/#MessageMapping to follow the same JSON serialization behavior as #RequestMapping. But this is not the case. Instead, when querying this WebSocket/STOMP endpoint, #SubscribeMapping/#MessageMapping-annotated methods will result in sending a STOMP message to clients with a payload/body corresponding to the "normal" Jackson serialization rules, e.g.
{
"id":123,
"name":"Charlie"
"friends":[{argh recursion}, ...],
"unimportantProperty":"This property shall not be part of JSON serialization"
}
Therefore:
How can I have #SubscribeMapping/#MessageMapping-annotated methods obey custom #JsonXXX annotations for returned values?
Is there another way aside #JsonXXXfor doing such returned value serialization?

Resources