0

I have a room service which returns detail for rooms when requesting http://localhost:8082/room/search/byRoomChar

    @GetMapping(path = "/search/byRoomChar")
    public @ResponseBody List<Room> byCapacity(@RequestParam(required = false) Integer capacity,
                                               @RequestParam(required = false) Boolean isUnderMaintenance,
                                               @RequestParam(required = false) String equipment) {
        return roomRepository.findByRoomChar(capacity, isUnderMaintenance, equipment);
    }

Now I want to request this @GetMapping from the booking service since this is the application gateway that users are going to interact with using the http://localhost:8081/booking/search/byRoomChar.

    @GetMapping(path = "/search/byRoomChar")
    public @ResponseBody List<Room> byCapacity(@RequestParam(required = false) Integer capacity,
                                               @RequestParam(required = false) Boolean isUnderMaintenance,
                                               @RequestParam(required = false) String equipment) {
        ResponseEntity<Room[]> roomsResponse = restTemplate.getForEntity("http://localhost:8082/room/search/byRoomChar?capacity=" + capacity + "&isUnderMaintenance=" +
            isUnderMaintenance + "&equipment=" + equipment, Room[].class);
        return Arrays.asList(roomsResponse.getBody());
    }

Room entity code:

package nl.tudelft.sem.template.entities;

import javax.persistence.Column;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "Room")
public class Room {
    @EmbeddedId
    private RoomId id;

    @Column(name = "capacity")
    private int capacity;

    @Column(name = "numberOfPeople")
    private int numberOfPeople;

    @Column(name = "isUnderMaintenance", nullable = false)
    private boolean isUnderMaintenance;

    @Column(name = "equipment")
    private String equipment;

    public Room() {
    }

    public Room(long roomNumber, long buildingNumber, int capacity,
                int numberOfPeople, boolean isUnderMaintenance, String equipment) {
        RoomId id = new RoomId(roomNumber, buildingNumber);
        this.id = id;
        this.capacity = capacity;
        this.numberOfPeople = numberOfPeople;
        this.isUnderMaintenance = isUnderMaintenance;
        this.equipment = equipment;
    }

    public RoomId getId() {
        return id;
    }

    public void setId(RoomId id) {
        this.id = id;
    }

    public int getCapacity() {
        return capacity;
    }

    public void setCapacity(int capacity) {
        this.capacity = capacity;
    }

    public int getNumberOfPeople() {
        return numberOfPeople;
    }

    public void setNumberOfPeople(int numberOfPeople) {
        this.numberOfPeople = numberOfPeople;
    }

    public boolean getIsUnderMaintenance() {
        return isUnderMaintenance;
    }

    public void setUnderMaintenance(boolean underMaintenance) {
        isUnderMaintenance = underMaintenance;
    }

    public String getEquipment() {
        return equipment;
    }

    public void setEquipment(String equipment) {
        this.equipment = equipment;
    }
}

Room repository code:

package nl.tudelft.sem.template.repositories;

import java.util.List;
import nl.tudelft.sem.template.entities.Room;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

@Repository
public interface RoomRepository extends JpaRepository<Room, Integer> {
    @Query("SELECT r FROM Room r WHERE (:number is null or r.id.number = :number)"
        + "and r.id.buildingNumber = :buildingNumber")
    List<Room> findByRoomNum(@Param("number") Long number,
                             @Param("buildingNumber") Long buildingNumber);

    @Query("SELECT r FROM Room r WHERE (:capacity is null or r.capacity = :capacity) and"
        + "(:isUnderMaintenance is null or r.isUnderMaintenance = :isUnderMaintenance) and"
        + "(:equipment is null or r.equipment = :equipment)")
    List<Room> findByRoomChar(@Param("capacity") Integer capacity,
                              @Param("isUnderMaintenance") Boolean isUnderMaintenance,
                              @Param("equipment") String equipment);

}

However, this does not work because when omitting parameters when calling the getmapping from the booking service all parameter values are turned into null because of the required=false. And this are converted into Strings inside the hard coded url.

2021-12-04 17:13:03.883  WARN 16920 --- [nio-8082-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'java.lang.Boolean'; nested exception is java.lang.IllegalArgumentException: Invalid boolean value [null]]

How can I make a get http request with optional parameters from within the code?

Yuji Reda
  • 127
  • 1
  • 5
  • 1
    I would be helpful to post the Room entity class and the RoomRepository interface as well in your question. – Georgios Syngouroglou Dec 04 '21 at 16:37
  • Thanks! Is your question about `roomRepository.findByRoomChar(capacity, isUnderMaintenance, equipment)` asking how to use variable arguments to a spring-data repository method? or is it about how to make the actual request from the client microservice, using variable arguments on this `ResponseEntity roomsResponse = restTemplate.getForEntity("http://localhost:8082/room/search/byRoomChar?capacity=" + capacity + "&isUnderMaintenance=" + isUnderMaintenance + "&equipment=" + equipment, Room[].class);`? – Georgios Syngouroglou Dec 04 '21 at 17:00
  • @GeorgiosSyngouroglou It was about how to make the actual request from the client microservice. Petr Aleksandrov answer seems appropriate but I get an "uri not absolute" exception :( – Yuji Reda Dec 04 '21 at 18:59
  • 1
    Looking to this so answer `https://stackoverflow.com/questions/14848877/uri-not-absolute-exception-getting-while-calling-restful-webservice`, you may try this `ResponseEntity roomsResponse = restTemplate.getForEntity(URLEncoder.encode(uri), Room[].class);` – Georgios Syngouroglou Dec 04 '21 at 21:47
  • @GeorgiosSyngouroglou now my searches return only empty lists. But it's fine! The assignment was for tomorrow and my messy solution below works. I didn't know url builders so I will study from the base since they seem quite useful. Thank you for the help Georgios! – Yuji Reda Dec 05 '21 at 08:12

4 Answers4

2

UriComponentsBuilder can help with URI construction. It correctly handles nullable query parameters.

String uri = UriComponentsBuilder.fromHttpUrl("http://localhost:8082/room/search/byRoomChar")
            .queryParam("capacity", capacity)
            .queryParam("isUnderMaintenance", isUnderMaintenance)
            .queryParam("equipment", equipment)
            .encode().toUriString();
ResponseEntity<Room[]> roomsResponse = restTemplate.getForEntity(uri, Room[].class);

Also, following answer can be helpful: https://stackoverflow.com/a/25434451/5990117

Petr Aleksandrov
  • 1,434
  • 9
  • 24
  • why could I possibly be getting a uri is not absolute exception? Isn't http://localhost:8082/room/search/byRoomChar already absolute? – Yuji Reda Dec 04 '21 at 18:40
2

If the parameters are not mandatory in your Room API but you still use them in your call to the Database either you have sensible defaults if they are actually not provided by the user. Something along the following lines (in this case you actually don't need to explicitly define required = false):

@GetMapping(path = "/search/byRoomChar")
public @ResponseBody List<Room> byCapacity(@RequestParam(defaultValue = "10") Integer capacity,
                                           @RequestParam(defaultValue = "false") Boolean isUnderMaintenance,
                                           @RequestParam(defaultValue = "default-equipment") String equipment) {
    return roomRepository.findByRoomChar(capacity, isUnderMaintenance, equipment);
}

Or you define a Repository method with no additional parameters, but this might be trickier since you basically need all the possibilities of null and non-null parameters.

João Dias
  • 16,277
  • 6
  • 33
  • 45
1

This is because the URI string being built when the parameters are null is like the follwing:

http://localhost:8082/room/search/byRoomChar?isUnderMaintenance=null

Since the "null" is being appended as a value of a parameter, the room server fails trying to deserialize it to a different type. For example in the error message you gave means that the "isUnderMaintenance" should be boolean but is "null" string.

To solve this problem, I recommend using the UriComponentBuilder.

@Test
fun constructUriWithQueryParameter() {
    val uri = UriComponentsBuilder.newInstance()
        .scheme("http")
        .host("localhost")
        .port(8082)
        .path("/room/search/byRoomChar")
        .query("capacity={capa}")
        .query("isUnderMaintenance={isUnderMaintenance}")
        .query("equipment={equip}")
        .buildAndExpand(null, null, null)
        .toUriString()
    assertEquals(
        "http://localhost:8082/room/search/byRoomChar 
             capacity=&isUnderMaintenance=&equipment=",
        uri
    )
}
KeepCoding
  • 13
  • 4
0

I tried Petr Aleksandrov answer. Looks very clean and it is most probably the way to go, but I was getting an "not absolute uri" exception.

Didn't have time to look for answers so I created my workaround code. Messy but it worked.

    @GetMapping(path = "/search/byRoomChar")
    public @ResponseBody List<Room> byCapacity(@RequestParam(required = false) Integer capacity,
                                               @RequestParam(required = false) Boolean isUnderMaintenance,
                                               @RequestParam(required = false) String equipment) {
        if(capacity == null && isUnderMaintenance == null && equipment == null) {
            ResponseEntity<Room[]> roomsResponse = restTemplate.getForEntity("http://localhost:8082/room/search/byRoomChar", Room[].class);
            return Arrays.asList(roomsResponse.getBody());
        }
        String url = "http://localhost:8082/room/search/byRoomChar?";
        if(capacity != null) {
            url += "capacity=" + capacity + "&";
        }
        if(isUnderMaintenance != null) {
            url += "isUnderMaintenance=" + isUnderMaintenance + "";
        }
        if(equipment != null) {
            url += "equipment=" + equipment;
        }
        ResponseEntity<Room[]> roomsResponse = restTemplate.getForEntity(url, Room[].class);
        return Arrays.asList(roomsResponse.getBody());

    }
Yuji Reda
  • 127
  • 1
  • 5