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.
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.
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);
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?
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?