How to get Custom object in java spring? - spring

I'm using java spring for my server.
My question is how can I get custom object through the controller.
Example for what I mean:
I know I can do that by doing two functions:
#RequestMapping(
path = arrayOf("getObject", "getObject/"),
method = arrayOf(RequestMethod.GET))
open fun getRecord1(#RequestBody data: CustomObjectOption1): ResponseEntity<*> {
return ResponseEntity<Any>(data.name,HttpStatus.OK)
}
#RequestMapping(
path = arrayOf("getObject", "getObject/"),
method = arrayOf(RequestMethod.GET))
open fun getRecord2(#RequestBody data: CustomObjectOption2): ResponseEntity<*> {
return ResponseEntity<Any>(data.number,HttpStatus.OK)
}
but I want to do it by only one endpoint:
#RequestMapping(
path = arrayOf("getObject", "getObject/"),
method = arrayOf(RequestMethod.GET))
open fun getRecord(#RequestBody data: CustomObjectOption): ResponseEntity<*> {
if(data instance option1)
return ResponseEntity<Any>(data.name,HttpStatus.OK)
else
return ResponseEntity(data.number,HttpStatus.OK)
else
}
such that the object can be like this:
option 1:
public class CustomObject {
private String name;
private Long id;
}
or option 2:
public class CustomObject {
private List<Integer> number;
private List<Long> count;
}
Is that possible to do that in java spring?
The only solution I was thinking is to use inheritance but I would like to know if there's different way...
Thank you for the help

Just as you have written, you can do it just like that:
#RequestMapping(...)
public void method(#RequestBody YourCustomClass body)
YourCustomClass can be either option 1 or option 2.
And that's all :)

Related

Dependency injection with mockito example

I am very new with Mockito and I don't get the following example (classes were provided, only test to write) and how to solve it.
What I try to do is use a test double for the supplier so that we can control the returned greeting in the test and assert that the GreetingService does not modify the greeting message in any way. Then assert that the returned greeting string is equal to "Hello Andy.".
public class Greeting {
private final String template;
public Greeting(String template) {
this.template = template;
}
public String forName(String world) {
return String.format(template, world);
}
}
#Component
public class GreetingService {
private final Supplier<Greeting> greetingSupplier;
public GreetingService(Supplier<Greeting> greetingSupplier) {
this.greetingSupplier = greetingSupplier;
}
public String greet(String name) {
return greetingSupplier.get().forName(name);
}
}
#Component
public class RandomGreetingSupplier implements Supplier<Greeting> {
private final List<Greeting> greetings = Arrays.asList(
new Greeting("Hello %s."),
new Greeting("Hi %s!"),
);
private final Random random = new Random();
#Override
public Greeting get() {
return greetings.get(random.nextInt(greetings.size()));
}
}
#SpringBootTest
public class GreetingServiceTest {
#Autowired
GreetingService greetingService;
#MockBean
Supplier<Greeting> greetingSupplier;
#Test
void getGreetingForPerson() {
String name = "Andy";
// that test cannot know which greeting will be returned by the supplier
// WHY IS IT NULLPOINTEREXCEPTION AFTER INITIALIZING #MockBean
//String greeting = greetingService.greet(name);
//assertThat(greeting).contains(name);
// WROTE SUCH TEST HERE -> NullPointerException WHY?
Mockito.when(greetingSupplier.get().forName(name)).thenReturn("Hello %s.");
assertThat(greetingSupplier.equals("Hello Andy."));
// THIS IS WORKING & TEST PASSED BUT I GUESS ITS WRONG?
Mockito.when(greetingSupplier.get()).thenReturn(new Greeting("Hello %s."));
assertThat(greetingSupplier.equals("Hello Andy."));
}
}
Mockito.when(greetingSupplier.get().forName(name)).thenReturn("Hello %s.");
You can't chain calls like that, you need to produce intermediate results, like
Supplier<Greeting> supplier = mock(Supplier.class);
Mockito.when(supplier).forName().thenReturn("Hello %s.");
Mockito.when(greetingSupplier.get()).thenReturn(supplier);
For dependency injection, you need to create the subject under test with the mocked Supplier. You can do that in a #Before method for example.
Your mocking is wrong.
Mockito.when(greetingSupplier.get().forName(name)).thenReturn("Hello %s.");
You mocked Supplier<Greeting> and the default behavior is to return null. So when you call greetingSupplier.get() in your first line it returns null. You directly chain forName which nou basicall is null.forName which leads to an error.
Your second part is actually (kind of) correct.
Mockito.when(greetingSupplier.get()).thenReturn(new Greeting("Hello %s."));
You now properly return a response from greetingSupplier.get(). Instead of chaining the call.
However I would argue that your excercise is wrong. Why? When using a Supplier<?> in Spring it actually is a lazy beanFactory.getBean call. You can lazily inject dependencies this way. You should have a mock for Greeting which returns a hardcoded String which you can check.

Does Springboot #RequestParam support List<Object> params in get request

Springboot #RequestParam annotation can pass basic list parameters, just like:
#GetMapping("param")
public String requestParamDemo(#RequestParam("list")List<Long> list) {
System.out.println(list.toString());
return list.toString();
}
and in postman, GET request localhost:8998/param?list=1,3,100 is works, "1,3,100" can be converted to List, but how or if Springboot #RequestParam support custom Generics such as below:
#GetMapping("objlist")
public String paramWithObjList(#RequestParam("objList")List<AaParam> objList) {
System.out.println("objList = " + objList);
return objList.toString();
}
import lombok.Data;
#Data
public class AaParam {
private int id;
private String name;
}
postman request: GET url: localhost:8998/objlist?objlist=[{id: 1, name: "aa"},{id: 2, name: "bb"}]
I tested in local and it didn't work.
Want to know if #RequestParam can do that or any alternative way to implement it.
Thanks!
Hope this suggestion holds good for your requirement.
I would suggest going with #RequestBody code will look like this
#PostMapping(path = "/objlist", consumes = "application/json", produces = "application/json")
public String paramWithObjList(#RequestBody List<AaParam> objList) {
System.out.println("objList = " + objList);
return objList.toString();
}
Note: please add some ObjectPaser in your actual logic (for example Jackson )
Postman request would be like this

How to design a REST service to response with different levels of information?

I would like to have a single service that can respond with different levels of information:
Level 1:
{
"field_1": "value_1",
"field_2": "value_2"
}
Level 2:
{
"field_1": "value_1",
"field_2": "value_2",
"field_3": "value_3"
}
Level 3:
{
"field_1": "value_1",
"field_2": "value_2",
"field_3": "value_3",
"field_4": "value_4"
}
My first approach is using a parameter in the request such like this:
#RestController
public <ResponseObject> getInfo(..., #RequestParam levelInfo) {
service.getInfo(..., levelInfo);
}
#Service
public <ResponseObject> getInfo(..., levelInfo) {
if (levelInfo == 1)
return setupResponseLevel1();
if (levelInfo == 2)
return setupResponseLevel1();
if (levelInfo == 3)
return setupResponseLevel1();
}
private <ResponseObject> setupResponseLevel1() {
responseObject.setField_1(repository.getField1());
responseObject.setField_2(repository.getField2());
return responseObject;
}
private <ResponseObject> setupResponseLevel2() {
responseObject = this.setupResponseLevel1();
responseObject.setField_3(repository.getField3());
return responseObject;
}
private <ResponseObject> setupResponseLevel3() {
responseObject = this.setupResponseLevel2();
responseObject.setField_4(repository.getField4());
return responseObject;
}
#JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
public class ResponseObject {
private String field_1;
private String field_2;
private String field_3;
private String field_4;
// Getters & setters...
}
My API will be very large and I need to find a pattern that I can reuse in many services.
Do you know any cleaner way to do it?
EDIT: I'm sorry, I did not explain with the properly precision.
I like the ideas of the answers but I have added more code to the #Service so that you understand that the problem is not only the presentation of the response (JSON) but also the saving of the cost of obtaining the information (queries to BBDD).
You could use #JsonView annotation for that. A simple example would look like this
public class Views {
public static class LevelOne {
}
public static class LevelTwo extends LevelOne {
}
}
public class ResponseObject {
#JsonView(Views.LevelOne.class)
private String field_1;
#JsonView(Views.LevelOne.class)
private String field_2;
#JsonView(Views.LevelTwo.class)
private String field_3;
}
and method for serializing
public String toJson(Class<?> view) {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.writerWithView(view).writeValueAsString(this);
}
if the toJson method is called with Views.LevelOne.class as an argument, only the field_1 and field_2 will be serialized. If it is called with Views.LevelTwo.class, all three fields would be serialized.
You can choose which view to use based on parameter as you suggested.
What about a URL scheme along the lines of:
https://your.api/info/2
then
#RequestMapping("/info/{levelInfo}")
public <ResponseObject> getInfo(..., #PathVariable String levelInfo) {
where 2 is the level. As the level is part of your domain, i.e. it has relevance to your clients, you could capture that in the database with a level column for each field, 1,2,3,4 etc then use the repository to:
repository.findByLevelLessThanEqual(levelInfo)
which could return a package of information containing all the required fields. So if you ask for level 1, you only get level 1 fields. If you ask for level 4, you get all fields up to and including level 4
Spring JPA LessThanEqual documentation

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 #RequestParam a list of objects

I want to create a page where a person sees a list of users and there are check boxes next to each of them that the person can click to have them deleted.
In my MVC that consumes a REST API, I want to send a List of User objects to the REST API.
Can the #RequestParam annotation support that?
For example:
#RequestMapping(method = RequestMethod.DELETE, value = "/delete")
public #ResponseBody Integer delete(
#RequestParam("users") List<Users> list) {
Integer deleteCount = 0;
for (User u : list) {
if (u != null) {
repo.delete(u);
++deleteCount;
}
}
return deleteCount;
}
In the MVC client, the url would be:
List list = new ArrayList<User>();
....
String url = "http://restapi/delete?users=" + list;
Request parameters are a Multimap of String to String. You cannot pass a complex object as request param.
But if you just pass the username that should work - see how to capture multiple parameters using #RequestParam using spring mvc?
#RequestParam("users") List<String> list
But I think it would be better to just use the request body to pass information.
Spring mvc can support List<Object>, Set<Object> and Map<Object> param, but without #RequestParam.
Take List<Object> as example, if your object is User.java, and it like this:
public class User {
private String name;
private int age;
// getter and setter
}
And you want pass a param of List<User>, you can use url like this
http://127.0.0.1:8080/list?users[0].name=Alice&users[0].age=26&users[1].name=Bob&users[1].age=16
Remember to encode the url, the url after encoded is like this:
http://127.0.0.1:8080/list?users%5B0%5D.name=Alice&users%5B0%5D.age=26&users%5B1%5D.name=Bob&users%5B1%5D.age=16
Example of List<Object>, Set<Object> and Map<Object> is displayed in my github.
Just a reminder, any List of custom objects might require custom converters to be registered, like:
#Bean
public Converter<String, CustomObject> stringToCustomObjectConverter() {
return new Converter<>() {
#Override
public CustomObject convert(String str) {
return new ObjectMapper().readValue(str, CustomObject.class);
}
};
}
#Bean
public Converter<String, List<CustomObject>> stringToListCustomObjectConverter() {
return new Converter<>() {
#Override
public List<CustomObject> convert(String str) {
return new ObjectMapper().readValue(str, new TypeReference<>() {
});
}
};
}
So you can cover custom cases like:
/api/some-api?custom={"name":"Bla 1","age":20}
/api/some-api?custom={"name":"Bla 1","age":20}&custom={"name":"Bla 2","age":30}
/api/some-api?custom=[{"name":"Bla 1","age":20},{"name":"Bla 2","age":30}]
where: #RequestParam("custom") List customObjects

Resources