RequestParam value converter - java

We are using Swagger to generate our API interfaces and model classes. However, Swagger generates the request parameters of our endpoints in a camelCase style instead of snake_case as it specified in our APIs.
For example, the code generated is:
#RequestMapping(value = "/search/test",method = RequestMethod.GET)
public ResponseEntity<Location> getLocation(#RequestParam(value = "locationId",required = true) locationID)
when it should be:
#RequestMapping(value = "/search/test",method = RequestMethod.GET)
public ResponseEntity<Location> getLocation(#RequestParam(value = "location_id",required = true) locationID)
Is there any way to match programmatically (maybe using a HttpConverter) a request containing the param "location_id" with the method containing "locationId" without throwing a PathNotFound exception?
All our params are even Integer or String.

Related

How to get headers of MultipartFile?

Using spring MVC I receive multipart files in the controller this way
#RestController
public class FilesController {
#PostMapping(path = ("/files"), consumes = {"multipart/form-data", "multipart/mixed"})
public Reference createFile(
#RequestPart(value = "description") FileDescription fileDesc,
#RequestPart(value = "attachments", required = false) List<MultipartFile> attachments) {
Some parts of the multipart request may contain headers like "Content-ID", "Content-Location" and so on. But spring interface MultipartFile doesn't provide a method to get any header I want, only getContentType as I see. How I can get all provided headers?
Important point is that in request I could have multipart/mixed as a part of multipart/form-data. So every part of the message has its own map of headers. If I use #RequestHeader, I can see main headers of the request, but there are no headers of a specific part of multipart.
There might be another way, but the one I know of is to ask for a MultipartHttpServletRequest in your method signature.
#PostMapping(path = ("/files"), consumes = {"multipart/form-data", "multipart/mixed"})
public Reference createFile(MultipartHttpServletRequest multipartRequest)
You can ask for other arguments if need be.
This object allows you to access details of the multipart in a finer-grained way. For example, you can access each part's header using getMultipartHeaders(String paramOrFileName). You also have methods to access the files content this way, so you would not typically need to keep you #RequestPart inside the method signature.
We can also use javax.servlet.http.Part instead of MultipartFile. Interface Part has getHeader method.
#RequestPart(value = "attachments", required = false) List<Part> attachments
You can get all request headers by using this
#RequestHeader Map<String,String> headers After that, you can search for the header you are looking for.
You can use #RequestHeader annotation to retrieve all the headers from the request, like this:
#RestController
public class FilesController {
#PostMapping(path = ("/files"), consumes = {"multipart/form-data", "multipart/mixed"})
public Reference createFile(
#RequestHeader Map<String, String> headersMap
) {
// Use headersMap here
}
or if you want a single header's value, then you can specify the name of the header in #RequestHeader annotation, like this:
#RestController
public class FilesController {
#PostMapping(path = ("/files"), consumes = {"multipart/form-data", "multipart/mixed"})
public Reference createFile(
#RequestHeader("Content-ID") String contentId,
#RequestHeader("Content-Location") String contentLocation,
) {
// Use contentId, contentLocation here
}

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.

Zero length part of URL in Spring controller RequestMapping PathVariable breaks resolution

I'm trying to make an app's REST API more RESTful and it feels like I'm not using the Spring RequestMappings in the way intended.
I have a single GET end point for doing reads:
#RequestMapping(value = "thing/{thingName}",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public String getThing(
#PathVariable(value = "thingName", required = false)
String thingName,
#RequestParam(value = "findByComponent", required = false)
String findByComponentQuery,
#AuthenticationPrincipal User user) {
...
To be more restful, I want this endpoint to do both:
GET /thing/{thingName} returns a single thing having that name
GET /thing or /thing/ with query params returns lists of things
So in my controller, I can test whether {thingName} is null or zero-length and if so, check the query params for known query names.
However calling this with http://localhost:8080/thing/?findByComponent=component123 returns a 404 from Spring with this logging:
12:45:18.485 PageNotFound : No mapping found for HTTP request with URI [/thing/] in DispatcherServlet with name 'dispatcher' : WARN : XNIO-1 task-3 : org.springframework.web.servlet.DispatcherServlet
Spring does not allow path variables ({thingName}) to be mapped to an empty String. In practice, this means that the URL /thing/?findByComponent=component123 does not map to thing/{thingName} with an empty {thingName}, but rather, it expects there to be some mapping for thing. Since there is no endpoint that maps to the path thing (without the path variable), a 404 error is returned.
To solve this issue, you can break this single endpoint into two separate endpoints:
#RequestMapping(value = "thing/{thingName}",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public String getThing(
#PathVariable(value = "thingName") String thingName,
#AuthenticationPrincipal User user) {
// ...
}
#RequestMapping(value = "thing",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public String getThings(,
#RequestParam(value = "findByComponent", required = false) String findByComponentQuery,
#AuthenticationPrincipal User user) {
// ...
}
For more information, see With Spring 3.0, can I make an optional path variable?.
The required=false flag allows for two types of requests:
/thing
/thing/<some_value>
Strictly speaking, including a trailing slash at the end of the URL (i.e. /thing/) means that a decision was made to include a value for the path variable, but none was provided. In the context of REST APIs, /thing and /thing/ are two different endpoints, where the latter means that a value after the trailing slash was expected.
A workaround for not having to create three separate request mappings (one for each case above) is to set the #RequestMapping value for the controller to the base path and then have a "" and "/{thingName} request mapping for the two endpoints:
#RestController
#RequestMapping("thing")
public class ThingController {
#RequestMapping(value = "/{thingName}",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public String getThing(
#PathVariable(value = "thingName") String thingName) {
return "foo";
}
#RequestMapping(value = "",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public String getThings(
#RequestParam(value = "findByComponent", required = false) String findByComponentQuery) {
return "bar";
}
}
In this case, the following mappings will occur:
/thing: getThings
/thing/: getThings
/thing/foo: getThing
An example of this workaround, including test cases can be found here.

swagger-codegen API converts types to string

Here is my interface for my swagger app:
#SwaggerDefinition(
...
)
#Api(
produces = "application/json",
protocols = "http",
tags = {"Service"}
)
#Path("/v1/{userId}/Services")
public interface ServiceApiV1 {
#GET
#Produces(MediaType.APPLICATION_JSON)
#AsyncTimed
#ApiOperation(
value = "Retrieves a service instance",
tags = "Service"
)
#Path("/{serviceId}")
void fetchService(#Suspended AsyncResponse asyncResponse,
#ApiParam #PathParam("userId") UserId userId,
#ApiParam #PathParam("serviceId") ServiceId serviceId
);
#GET
#Produces(MediaType.APPLICATION_JSON)
#AsyncTimed
#ApiOperation(
value = "Retrieves a list of services",
tags = "Service"
)
#Path("/")
void fetchServices(#Suspended AsyncResponse asyncResponse,
#ApiParam #PathParam("userId") UserId userId
);
}
I tried to use #ApiParam(type = "UserId") but it still uses string (I also tried to use the full path to UserId, like com.myservice.UserId)
As you can see, I have a specific type for UserId and one for ServiceId. When I run swagger-codegen the API generated, however, convert these arguments to type string
Is it possible to have Swagger generate the API clients, but keep the type of PathParams as I have defined them here?
(And yes, the swagger.json has these parameters of type string which makes sense why the codegen is then generating these as a string. So I guess the correct question is how to make Swagger generate the parameters are the more advance types as they are)
UPDATE So using full path does generate the right type for my swagger.json. However, the generated API still uses a String :(

How to use swagger with query params deserialising to class?

I have spring rest controller and i want it documented via swagger. It looks like this:
#ApiOperation(value = "Returns comments list")
#RequestMapping(method = RequestMethod.GET)
public CollectionResponse<CommentDTO> getComments(CommentFilterDTO filterDTO) {
Page<CommentEntity> requisitionComments = commentService.getComments(filterDTO);
return Convert.convert(requisitionComments, COMMENT_ENTITY_2_COMMENT_DTO);
}
CommentsFilterDTO is
public class CommentFilterDTO {
private Long requisitionId;//get, set
private CommentType commentType;//get, set
// etc
}
This controller takes query string with pageable and filterDTO params like this:
my/comments?requisitionId=1&commentType=COMMENT
Now i'm trying to document it via swagger and i want all possible query parameters documented as well, but if i leave it like this i see only
I can document query params via #ApiImplicitParams like
#ApiImplicitParams({
#ApiImplicitParam(name = "requisitionId", value = "Requisition id", required = true, dataType = "long", paramType = "query"),
...
})
Is there any way to tell swagger what all CommentFilterDTO fields can be used as query parameters?

Categories