How can I have two return types in one controller returning Collection of Class A or Object of Class B - spring

I am trying to follow Rest Principles, so I have two rest controllers
with a base URL localhost:8088/trucks.
First, I have to get all trucks:
#GetMapping(value = "/trucks")
public final Collection<TruckDto> getAllTrucks() {
LOGGER.debug("test: getAllTrucks()");
Collection<Truck> trucks = truckService.getAllTrucks();
return mappingService.map(trucks, TruckDto.class);
}
Note, my issue also has to do with the fact that I have different classes I am returning for truckById and truckByTruckCode.
And I have 'get truck by ID' service:
#GetMapping(value = "/trucks/{truckId:[0-9]+}")
#ResponseStatus(HttpStatus.FOUND)
#ResponseBody
public final TruckDto getTruckId(#PathVariable(value = "truckId") final Integer truckId) {
LOGGER.debug("test: truckId({})", truckId);
Truck truck = truckService.getTruckById(truckId);
return mappingService.map(truck, TruckDto.class);
}
Now I have a get by truckCode that works but it doesnt follow the rest principle , which is something like , there should be only one base url and all others build from it , here it is
#ResponseStatus(HttpStatus.OK)
#ResponseBody
#GetMapping(value = "/trucks/{truckCode:[0-9]*[a-zA-Z][a-zA-Z0-9]*}")
public final TruckWithAvgPetrolDto getTruckByTruckCode (#PathVariable(value = "truckCode")
final String truckCode) {
LOGGER.debug("getTruckByTruckCode()");
TruckWithAvgDto truck = truckService.getTruckByTruckCode(truckCode);
return mappingService.map(truck, TruckWithAvgPetrolDto.class);
}
Now it works but I think it should be an optional param there for it should be in one method. So maybe I can have some sort of optional return type because I have two different services, methods to return in both situations.
Because get by id just gets truck detail, but get by trukCode performs a left join and gets truck average consumption of petrol from an order table so I have two different DTOs for get by id and get by truckCode.
My questions are how can I get something like one method say getBY(param) if I put a number it should get by id and return TruckDto but if I put a code like BY788 it should get by code and return a TruckWithAvgPetrolDto. Is that's possible?

This is a solution that works but i will need verification that it is ok to do such in rest or it is bad practice
#GetMapping("/trucks/{value}")
public ResponseEntity<?> getTruckByIdOrCode(#PathVariable(value = "value" )String value) {
if (value.matches("[0-9]*[a-zA-Z][a-zA-Z0-9]*")) {
TruckWithAvgDto list = truckService.getTruckByTruckCode(value);
return new ResponseEntity<TruckWithAvgDto>(list, HttpStatus.FOUND);
}else {
Truck truck = truckService.getTruckById(Integer.parseInt(value));
return new ResponseEntity<Truck>(truck,HttpStatus.FOUND);
}
}

Related

How to iterate from the JPA query in spring boot based on the virtual table such as (the count of students for courses tutorials taken)

I need to retrieve each student with number of courses taken, for example :
count
student
5
John
8
Jenny
And I need the following JSON response from my rest end-point:
[
{
"name_student": John,
"count_course": 5,
},
{
"name_student": Jenny,
"count_course": 8,
},
]
I have made the query to fetch the required data inside the Repository:
#Query(value ="Select count(*), s.student_name from student s INNER JOIN courses c on c.student_id= s.student_id group by s.student_name", nativeQuery = true )
List<StudentDTO> getIncidentCountByOfficerKPI ()
My DTO:
#Data
#NoArgsConstructor
public class StudentDTO {
Integer count;
String StudentName;
}
Service:
public List<StudentDTO> getStudentCount() {
return p2StudentRepository.getIncidentCountByOfficerKPI ();
}
Controller:
#GetMapping("/Student-count")
public ResponseEntity<StudentDTO> getAllStudentCount() {
List<StudentDTO> selectedCount = p2StudentService.getStudentCount();
List<StudentDTO> result = new ArrayList<>();
selectedCount.forEach(i -> {
StudentDTOitem = new StudentDTO();
item.setUserName(i.getStudentName());
item.setCount(i.getCount());
result.add(item);
});
return ResponseEntity.ok(result);
}
when I test with postman, it returns to nothing like below
[]
How can I fix that?
The return type of your rest end-point is incorrect, it should be ResponseEntity<List<StudentDTO>> (since you need to place multiple StudentDTO in the response).
And to provide ResponseEntity with a response body, you can use body() method of the ResponseEntity.BodyBuilder (that's a builder-object produced by the static method ResponseEntity.ok()), or it can be done by using of the parameterized constructors of the ResponseEntity.
And since you're actually not transforming the data returned by getStudentCount() you can attach this list as the response body directly.
#GetMapping("/Student-count")
public ResponseEntity<List<StudentDTO>> getAllStudentCount() { // <- change the return type
List<StudentDTO> selectedCount = p2StudentService.getStudentCount();
return ResponseEntity.ok()
.body(selectedCount); // <- add the response body
}

How to filter object lists and create another filtered lists from it

I receive a List of MediaDTO and this Object has two attributes:
String sizeType and String URL.
In 'sizeType' comes the image´s size: small, medium, large, and thumbnail.
So I have to filter the sizeType of these objects and create 4 new lists based on them.
This is how I get the List<MediaDTO> mediaDTO:
medias=[MediaDTO(sizeType=THUMBNAIL, liveloUrl=https://s3.sao01.cloud-object-storage.appdomain.cloud/catalog-media-storage/id-source/productId/skuseller2/thumbnail/celular-iphone-11-azul.png), MediaDTO(sizeType=SMALL, liveloUrl=https://s3.sao01.cloud-object-storage.appdomain.cloud/catalog-media-storage/id-source/productId/skuseller2/small/celular-iphone-11-azul.png), MediaDTO(sizeType=SMALL, liveloUrl=https://s3.sao01.cloud-object-storage.appdomain.cloud/catalog-media-storage/id-source/productId/skuseller2/medium/celular-iphone-11-azul.png), MediaDTO(sizeType=LARGE, liveloUrl=https://s3.sao01.cloud-object-storage.appdomain.cloud/catalog-media-storage/id-source/productId/skuseller2/large/celular-iphone-11-azul.png), MediaDTO(sizeType=THUMBNAIL, liveloUrl=https://s3.sao01.cloud-object-storage.appdomain.cloud/catalog-media-storage/id-source/productId/skuseller2/thumbnail/celular-iphone-11-vermelho.png), MediaDTO(sizeType=SMALL, liveloUrl=https://s3.sao01.cloud-object-storage.appdomain.cloud/catalog-media-storage/id-source/productId/skuseller2/small/celular-iphone-11-vermelho.png), MediaDTO(sizeType=MEDIUM, liveloUrl=https://s3.sao01.cloud-object-storage.appdomain.cloud/catalog-media-storage/id-source/productId/skuseller2/medium/celular-iphone-11-vermelho.png), MediaDTO(sizeType=LARGE, liveloUrl=https://s3.sao01.cloud-object-storage.appdomain.cloud/catalog-media-storage/id-source/productId/skuseller2/large/celular-iphone-11-vermelho.png)]
I achieved filtering for one of the sizes. This works!
However, I could not figure out how can I filter over the 4 sizes and create 4 new lists of it.
If I fix some error another appears ... so I´m really stuck.
And by the way I´ve been searching for a solution on the internet and in the forum for a couple of days but did´nt find something that fits.
If someone might help, I´d really be grateful.
I was thinking about using a 'forEach' to filter but even like that, I could filter just one size.
Thanks in advance.
**This is what I got till now: **
public class ProcessProductDTO {
String processId;
OperationProcess operation;
String categoryId;
ProductDTO productDTO;
}
public class ProductDTO {
String id;
Boolean active;
String displayName;
String longDescription;
List<MediaDTO> medias;
List<AttributeDTO> attributes;
}
public class MediaDTO {
String sizeType;
String liveloUrl;
}
public Properties toOccProductPropertiesDTO(ProcessProductDTO processProductDTO) throws JsonProcessingException {
String pSpecs = convertAttributes(processProductDTO.getProductDTO().getAttributes());
//List<String> medias = convertMedias(processProductDTO.getProductDTO().getMedias());
return Properties.builder()
.id(processProductDTO.getProductDTO().getId()) .active(processProductDTO.getProductDTO().getActive())
.listPrices(new HashMap())
.p_specs(pSpecs)
//.medias(medias)
.displayName(processProductDTO.getProductDTO()
.getDisplayName())
.longDescription(processProductDTO.getProductDTO().getLongDescription())
.build(); }
private String convertAttributes(List<AttributeDTO> attributes) throws JsonProcessingException {
Map<String, String> attribs = attributes.stream()
.collect(Collectors.toMap(AttributeDTO::getName, AttributeDTO::getValue));
return objectMapper.writeValueAsString(attribs);
}
private List<MediaDTO> convertMedias(ProcessProductDTO processProduct, List<MediaDTO> mediaDTO){
List<MediaDTO> filteredList = processProduct.getProductDTO().getMedias();
Set<String> filterSet = mediaDTO.stream().map(MediaDTO::getSizeType).collect(Collectors.toSet());
return filteredList.stream().filter(url -> filterSet.contains("SMALL")).collect(Collectors.toList());
}
UPDATE
I got the following result:
private Properties toOccProductPropertiesDTO(ProcessProductDTO processProductDTO) throws JsonProcessingException {
String pSpecs = convertAttributes(processProductDTO.getProductDTO().getAttributes());
MediaOccDTO medias = convertMedias(processProductDTO.getProductDTO().getMedias());
return Properties.builder()
.id(processProductDTO.getProductDTO().getId())
.active(processProductDTO.getProductDTO().getActive())
.listPrices(new HashMap())
.p_specs(pSpecs)
.medias(medias)
.displayName(processProductDTO.getProductDTO().getDisplayName())
.longDescription(processProductDTO.getProductDTO().getLongDescription())
.build();
}
private MediaOccDTO convertMedias(List<MediaDTO> mediaDTOs){
String smallImageUrls = generateOccUrl(mediaDTOs, ImageSizeType.SMALL);
String mediumImageUrls = generateOccUrl(mediaDTOs, ImageSizeType.MEDIUM);
String largeImageUrls = generateOccUrl(mediaDTOs, ImageSizeType.LARGE);
String thumbImageUrls = generateOccUrl(mediaDTOs, ImageSizeType.THUMB);
return MediaOccDTO.builder()
.p_smallImageUrls(smallImageUrls)
.p_mediumImageUrls(mediumImageUrls)
.p_largeImageUrls(largeImageUrls)
.p_thumbImageUrls(thumbImageUrls)
.build();
}
private String generateOccUrl(List<MediaDTO> mediaDTOs, ImageSizeType imageSizeType){
return mediaDTOs.stream()
.filter(m -> m.getSizeType().equals(imageSizeType))
.map(MediaDTO::getLiveloUrl)
.reduce(",", String::concat);
}
The problem is:
the comparison: m.getSizeType().equals(imageSizeType)
is always false, so the list gets created empty...
Though the question is laborious, I could think of the requirement being, you need to create 4 new lists based on sizeType.
Stream collector, can collect the results to a single data structure. It can be a list, set, Map etc.
Since you need 4 lists based on sizeType, you will need to pass through the stream 4 times to create 4 lists.
Another Alternate will be to create a Map<SizeType, List<MediaDTO>>
This can be achieved through,
mediaDTO.stream().collect(Collectors.toMap(i -> i.getSizeType(), i->i)
I think the toMap doesn't collect the values in a list. We need groupingBy instead.
mediaDTO.stream()
.collect(Collectors.groupingBy(MediaDTO::getSizeType));

if/else doese not work in my POST Method of SpringBoot rest api

I use Spring Boot rest api with MongoDB.
In the POST Method, if there is not scoreID and there is not a player with specific date in my collection, because at the same time a player cannot play different games and bring score, then if the specific player and gamecode exist, create a score.
In fact, in the POST Method I used Nested IF-ELSE conditions.
But, in the Postman when I execute POST Request with this data:
{
"scoreid":"s11",
"score":1000,
"player":"sahari",
"gamecode":"g12",
"date":"2020-01-01"
}
always, I recieve an error, in the Postman, 400 Bad Request!, which i defined in the last line of my IF-ELSE statements.
I do not know, what is my mistake and why my program doese not execute IF conditions correct.
The POST Method:
//Create Score
#PostMapping
public ResponseEntity<?> createScore(#RequestBody #JsonView(Views.class) #Valid Score score) {
String p = srepo.findByPlayerName(score.getPlayer());
String g = srepo.findByGameCode(score.getGamecode());
String scoreid = srepo.findByScoreid(score.getScoreid());
Query query = new Query();
query.addCriteria(new Criteria().andOperator(Criteria.where("player").is(score.getPlayer()),
Criteria.where("date").is(score.getDate())));
if((scoreid != null)) {
return ResponseEntity.status(409).body("Conflict!"); }
else
if(mongoTemplate.exists(query, Score.class))
return ResponseEntity.status(409).body("There is not Possible at same time one player brings different Scores!");
else
if((p!= null)&&(g!= null))
{
history = new ArrayList<History>();
h = new History();
h.setScore(score.getScore());
h.setDate(score.getDate());
history.add(h);
hrepo.save(h);
score.setHistory(history);
srepo.insert(score);
return ResponseEntity.ok(score);
}
else
{
return ResponseEntity.status(400).body("Bad Request!");
}
}
The Score Repository:
#Repository
public interface ScoreRepository extends MongoRepository<Score, String>{
#Query("{'scoreid':?0}")
public String findByScoreid(String scoreid);
#Query("{'Player.nickname':?0}")
public String findByPlayerName(String player);
#Query("{'Games.code':?0}")
public String findByGameCode(String game);
}
The problem is not for my IF-ELSE statements.The problem is in the Score Repository
I must return a List instead of String for findByPlayerName and findByGameCode and for findByScoreid which is for checking duplicate in the POST Method I must return type of Score

Why my Pageable API is not working with Spring MVC?

Im implementing an API that is working correctly, but when i use pagitation this is returning nothing.
I have the next code for my repository:
public interface VinetaRepository extends JpaRepository<Vineta, Long> {
List<Vineta> findAllByOrderByCreationdateDesc();
List<Vineta> findByTitulo(String titulo);}
and, when i used the next RequestMapping everthings is ok:
#JsonView(VinetaView.class)
#RequestMapping("/api/vinetas2/")
public List<Vineta> getvinetas2(){
return this.vinetarepository.findAll();
}
but, when i use the pagination, my api is returnin anything:
#JsonView(VinetaView.class)
#RequestMapping(value = "/api/vinetaspage/", method= RequestMethod.GET)
public Page<Vineta> getvinetaspage(Pageable page ){
//This System.out print all the size of my repository
System.out.println(this.vinetarepository.findAll().size());
//This for print the title for my 20 first objects Vineta
for(Vineta v:this.vinetarepository.findAll(page)){
System.out.println(v.getTitulo());
}
// This print 20
System.out.println(this.vinetarepository.findAll(page).getSize());
//This print the page number = 0
System.out.println(page.getPageNumber());
//This print the page size = 20
System.out.println(page.getPageSize());
//Finally, this is not returning nothing
return vinetarepository.findAll(page);
}
With the System out, i can see that the size of my query is correct (20), but nothing is returning.
Both function are using the same Jsonview, so this problem is discarded.
An adapter class (like PageWrapper) would be needed to wrap the Spring Data Page interface as the page interface may lack of features.
public class PageWrapper {
}

Tranversing and filtering a Set comparing its objects' getters to an Array using Stream

I've got some working, inelegant code here:
The custom object is:
public class Person {
private int id;
public getId() { return this.id }
}
And I have a Class containing a Set<Person> allPersons containing all available subjects. I want to extract a new Set<Person> based upon one or more ID's of my choosing. I've written something which works using a nested enhanced for loop, but it strikes me as inefficient and will make a lot of unnecessary comparisons. I am getting used to working with Java 8, but can't quite figure out how to compare the Set against an Array. Here is my working, but verbose code:
public class MyProgram {
private Set<Person> allPersons; // contains 100 people with Ids 1-100
public Set<Person> getPersonById(int[] ids) {
Set<Person> personSet = new HashSet<>() //or any type of set
for (int i : ids) {
for (Person p : allPersons) {
if (p.getId() == i) {
personSet.add(p);
}
}
}
return personSet;
}
}
And to get my result, I'd call something along the lines of:
Set<Person> resultSet = getPersonById(int[] intArray = {2, 56, 66});
//resultSet would then contain 3 people with the corresponding ID
My question is how would i convert the getPersonById method to something using which streams allPersons and finds the ID match of any one of the ints in its parameter array? I thought of some filter operation, but since the parameter is an array, I can't get it to take just the one I want only.
The working answer to this is:
return allPersons.stream()
.filter(p -> (Arrays.stream(ids).anyMatch(i -> i == p.getId())) )
.collect(Collectors.toSet());
However, using the bottom half of #Flown's suggestion and if the program was designed to have a Map - it would also work (and work much more efficiently)
As you said, you can introduce a Stream::filter step using a Stream::anyMatch operation.
public Set<Person> getPersonById(int[] ids) {
Objects.requireNonNull(ids);
if (ids.length == 0) {
return Collections.emptySet();
}
return allPersons.stream()
.filter(p -> IntStream.of(ids).anyMatch(i -> i == p.getId()))
.collect(Collectors.toSet());
}
If the method is called more often, then it would be a good idea to map each Person to its id having a Map<Integer, Person>. The advantage is, that the lookup is much faster than iterating over the whole set of Person.Then your algorithm may look like this:
private Map<Integer, Person> idMapping;
public Set<Person> getPersonById(int[] ids) {
Objects.requireNonNull(ids);
return IntStream.of(ids)
.filter(idMapping::containsKey)
.mapToObj(idMapping::get)
.collect(Collectors.toSet());
}

Resources