Query parameters with postman and Spring - java

Can someone explain to me why given this controller:
#RestController
#RequestMapping("/gamification")
public class GamificationController {
private static final Logger logger = LoggerFactory.getLogger(GamificationController.class);
#Autowired
private GameServiceImpl gameService;
#GetMapping("/retrieve-stats/?user={userId}")
ResponseEntity<GameStats> getUserStats(#RequestParam("userId") String userId){
logger.debug("UserId is {}", userId);
return ResponseEntity.ok(gameService.retrieveStatsForUser(Long.parseLong(userId)));
}
}
and this pm request
I get a 404 NOT FOUND?
and If I add brackets to the query parameter, I get a 400 BAD REQUEST

You need to remove the /?user={userId} from #GetMapping as well as from the 1st Postman screenshot request and Construct it like the below one.
#GetMapping("/retrieve-stats")
ResponseEntity<GameStats> getUserStats(#RequestParam("userId") String userId){
logger.debug("UserId is {}", userId);
return ResponseEntity.ok(gameService.retrieveStatsForUser(Long.parseLong(userId)));
}
PS: And you don't have to cast it Long.parseLong, you can declare the parameter with the type #RequestParam("userId") Long userId, Spring is smart enough to autobox the variable type based on its type declartion.

Related

How would you handle a REST API with only optional query params with Spring Boot?

I want to build a simple endpoint that returns an Order object where I can search for this order by a single query parameter or a combination of several query parameters altogether. All of these query parameters are optional and the reason is that different people will access these orders based on the different Ids.
So for example:
/order/items?itemId={itemId}&orderId={orderId}&deliveryId={deliveryId}&packetId={packetId}
#GetMapping(path = "/order/items", produces = "application/json")
public Order getOrders(#RequestParam Optional<String> itemId,
#RequestParam Optional<String> orderId,
#RequestParam Optional<String> deliveryId,
#RequestParam Optional<String> packetId) { }
I could of course also skip the Java Optional and use #RequestParam(required = false), but the question here is rather how do I escape the if-else or .isPresent() nightmare of checking whether the query params are null? Or is there an elegant way, depending on the constellation of params, to pass further to my service and Spring Data JPA repository.
To minimize the amount of parameters in your method, you could define your query parameters as fields of a class:
#Data
public class SearchOrderCriteria {
private String itemId;
private String orderId;
private String deliveryId;
private String packetId;
}
Then receive an instance of such class in your controller method:
#GetMapping(path = "/order/items", produces = "application/json")
public ResponseEntity<OrderInfo> getOrder(SearchOrderCriteria searchCriteria) {
OrderInfo order = orderService.findOrder(searchCriteria)
return ResponseEntity.ok(order);
}
And, in your service, to avoid a bunch of if-else, you could use query by example:
public OrderInfo findOrder(SearchOrderCriteria searchCriteria) {
OrderInfo order = new OrderInfo();
order.setItemId(searchCriteria.getItemId());
order.setOrderId(searchCriteria.getOrderId());
order.setDeliveryId(searchCriteria.getDeliveryId());
order.setPacketId(searchCriteria.getPacketId());
Example<OrderInfo> example = Example.of(order);
return orderRepository.findOne(example);
}
My small suggestion is avoid to use too general API, for example you can split your mapping into several endpoints eg:/order/delivery/itemId/{itemId} and /order/delivery/deliveryId/{deliveryId} and /order/delivery/packetId/{packetId} and handle which you need to call on client side.

Spring restful API, is there a method being used like router to get other method's end points or URL?

#RequestMapping("/accounts")
public class controller {
#GetMapping("/get/{id}")
public final ResponseEntity<?> getHandler(){
}
#PostMapping(value = "/create")
public final ResponseEntity<?> createHandler(){
/*
trying to use some spring library methods to get the url string of
'/accounts/get/{id}' instead of manually hard coding it
*/
}
}
This is the mock code, now I am in createHandler, after finishing creating something, then I want to return a header including an URL string, but I don't want to manually concat this URL string ('/accounts/get/{id}') which is the end point of method getHandler(), so I am wondering if there is a method to use to achieve that? I know request.getRequestURI(), but that is only for the URI in the current context.
More explanation: if there is some library or framework with the implementation of route:
Routes.Accounts.get(1234)
which return the URL for the accounts get
/api/accounts/1234
The idea is, that you don't need to specify get or create (verbs are a big no-no in REST).
Imagine this:
#RequestMapping("/accounts")
public class controller {
#GetMapping("/{id}")
public final ResponseEntity<?> getHandler(#PathVariable("id") String id) {
//just to illustrate
return complicatedHandlerCalculation(id).asResponse();
}
#PostMapping
public final ResponseEntity<?> createHandler() {
//return a 204 Response, containing the URI from getHandler, with {id} resolved to the id from your database (or wherever).
}
}
This would be accessible like HTTP-GET: /api/accounts/1 and HTTP-POST: /api/accounts, the latter would return an URI for /api/accounts/2 (what can be gotten with HTTP-GET or updated/modified with HTTP-PUT)
To resolve this URI, you could use reflection and evaluate the annotations on the corresponding class/methods like Jersey does.
A Spring equivalent could be:
// Controller requestMapping
String controllerMapping = this.getClass().getAnnotation(RequestMapping.class).value()[0];
and
//Method requestMapping
String methodMapping = new Object(){}.getClass().getEnclosingMethod().getAnnotation(GetMapping.class).value()[0];
taken from How do i get the requestmapping value in the controller?

How to validate redundant query parameters with RestEasy?

I have service(WildFly 10.1) which looks like that :
#GET
#Path("/retrieve")
public Response getModels(#BeanParam ModelQueryParams queryParams) {
return getModels();
}
With ModelQueryParams:
public class ModelQueryParams{
#QueryParam("offset")
private Long offset;
#QueryParam("limit")
private Long limit;
}
So the user can call endpoint like:
/retrieve?offset=100&limit=4
But how can I validate case when user pass into the query wrong parameter?
/retrieve?offset=100&limit=4&WRONG_PARAMETER=55
Is there the way to validate it somehow?
if you don't have any field or method parameters annotated with #QueryParam, then those extra parameters are not your problem and it's best to deal with only parameters you expect for your resource.
If you still need access to all query parameters, then inject UriInfo with #Context and call it's getQueryParameters() to get a MultivaluedMap of request parameters

MissingServletRequestParameterException with PostMapping in Spring MVC

I'm getting this error when I try to create an instance of an Object and store it into a database:
org.springframework.web.bind.MissingServletRequestParameterException
The code for the method:
#PostMapping(path="accounts/add", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
public
#ResponseBody
String addAccount(#RequestParam String accountNumber, #RequestParam String accountName ) {
Account account = new Account();
account.setAccountName(accountName);
account.setAccountNumber(accountNumber);
accountRepository.save(account);
return "Saved";
}
When I use PostMan and try to enter the JSON in, it gives me this message. What am I doing wrong?
Since your POSTing (or PUTing?) JSON as the content body, you cannot use #RequestParam to deconstruct it. That is used for query or form parameters. You need to specify a single method parameter with #RequestBody, which is a class that looks something like the following.
public class Account {
public String accountNumber;
public String accountName;
// setters and getters removed for brevity
}
See this answer for more info: #RequestBody and #ResponseBody annotations in Spring

Java Spring - how to handle missing required request parameters

Consider the following mapping:
#RequestMapping(value = "/superDuperPage", method = RequestMethod.GET)
public String superDuperPage(#RequestParam(value = "someParameter", required = true) String parameter)
{
return "somePage";
}
I want to handle the missing parameter case by not adding in required = false. By default, 400 error is returned, but I want to return, let's say, a different page. How can I achieve this?
If a required #RequestParam is not present in the request, Spring will throw a MissingServletRequestParameterException exception. You can define an #ExceptionHandler in the same controller or in a #ControllerAdvice to handle that exception:
#ExceptionHandler(MissingServletRequestParameterException.class)
public void handleMissingParams(MissingServletRequestParameterException ex) {
String name = ex.getParameterName();
System.out.println(name + " parameter is missing");
// Actual exception handling
}
I want to return let's say a different page. How to I achieve this?
As the Spring documentation states:
Much like standard controller methods annotated with a #RequestMapping
annotation, the method arguments and return values of
#ExceptionHandler methods can be flexible. For example, the
HttpServletRequest can be accessed in Servlet environments and the
PortletRequest in Portlet environments. The return type can be a
String, which is interpreted as a view name, a ModelAndView object, a
ResponseEntity, or you can also add the #ResponseBody to have the
method return value converted with message converters and written to
the response stream.
An alternative
If you use the #ControllerAdvice on your class and if it extends the Spring base class ResponseEntityExceptionHandler. A pre-defined function has been created on the base class for this purpose. You have to override it in your handler.
#Override
protected ResponseEntity<Object> handleMissingServletRequestParameter(MissingServletRequestParameterException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
String name = ex.getParameterName();
logger.error(name + " parameter is missing");
return super.handleMissingServletRequestParameter(ex, headers, status, request);
}
This base class is very useful, especially if you want to process the validation errors that the framework creates.
You can do this with Spring 4.1 onwards and Java 8 by leveraging the Optional type. In your example that would mean your #RequestParam String will have now type of Optional<String>.
Take a look at this article for an example showcasing this feature.
Maybe not that relevant, but I came across to a similar need: change the 5xx error to 4xx error for authentication header missing.
The controller is as follows:
#RequestMapping("list")
public ResponseEntity<Object> queryXXX(#RequestHeader(value = "Authorization") String token) {
...
}
When you cURL it without the authorization header you get a 5xx error:
curl --head -X GET "http://localhost:8081/list?xxx=yyy" -H "accept: */*"
HTTP/1.1 500
...
To change it to 401 you can
#ExceptionHandler(org.springframework.web.bind.MissingRequestHeaderException.class)
#ResponseBody
public ResponseEntity<Object> authMissing(org.springframework.web.bind.MissingRequestHeaderException ex) {
log.error(ex.getMessage(), ex);
return IResponse.builder().code(401).message(ex.getMessage()).data(null).build();
}
#Data
public class IResponse<T> implements Serializable {
private Integer code;
private String message = "";
private T data;
...
}
You can verify it by an automation test:
#Test
void testQueryEventListWithoutAuthentication() throws Exception {
val request = get("/list?enrollEndTime=1619176774&enrollStartTime=1619176774&eventEndTime=1619176774&eventStartTime=1619176774");
mockMvc.perform(request).andExpect(status().is4xxClientError());
}

Categories