Is it possible to split request params in Spring controllers? - java

I have a request like:
example.com/search?sort=myfield1,-myfield2,myfield3
I would like to split those params to bind a List<String> sort in my controller or List<SortParam> where SortParam is the class with fields like: name (String) and ask (boolean).
So the final controller would look like this:
#RequestMapping(value = "/search", method = RequestMethod.GET)
public ResponseEntity<MyResponse> search(#RequestParam List<String> sort) {
//...
}
or
#RequestMapping(value = "/search", method = RequestMethod.GET)
public ResponseEntity<MyResponse> search(#RequestParam List<SortParam> sort) {
//...
}
Is there a way to make it?
UPDATE:
The standard way of passing parameters does not satisfy my requirements. I.e. I cannot use sort=myfield1&sort=-myfield2&sort=myfield3. I have to use comma separated names.
Also, I do understand that I can accept #RequestParam String sort in my controller and then split the string inside the controller like sort.split(",") but it also doesn't solve the above problem.

Its just a simple Type Covertion task. Spring defines an SPI (Service Provider Interface) to implement type conversion logic. For your specific problem you can define your Type Conversion logic by implementing Converter interface.
#Component
public class StringToListConverter implements Converter<String, List<String>> {
#Override
public List<String> convert(String source) {
return Arrays.asList(source.split(","));
}
}
You can also convert your request parameter to List<SortPram> according your logic (but I am not sure about your that logic from your question). This is it! Now Spring get known that how to bind your request paramter to a list.
#RequestMapping(value = "/search", method = RequestMethod.GET)
public ResponseEntity<MyResponse> participants(#RequestParam("sort") List<String> sort) {
// .. do your logic
}
There are many more ways to define you custom data binder. Check this
A Custom Data Binder in Spring MVC article.
Spring documentation about Validation, Data Binding, and Type Conversion

Yes, you can certainly do that, you're almost there.
#RequestMapping(value = "/search", method = RequestMethod.GET)
public ResponseEntity<MyResponse> participants(#RequestParam("sort") List<String> sort) {
//...
}
You should now be able to call your service like this (if search is located at your root, otherwise adapt according to your situation):
curl "localhost:8080/search?sort=sortField1&sort=sortField2&sort=sortField3"
Hope this helps!
EDIT
Sorry, I have read your comments and what you need is clear to me now. I have created a workaround for you that is almost what you want I think.
So first a SortParams class:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class SortParams {
private List<SortParam> sortParamList;
public SortParams(String commaSeparatedString) {
sortParamList = Arrays.stream(commaSeparatedString.split(","))
.map(p -> SortParam.valueOf(p))
.collect(Collectors.toList());
}
public List<SortParam> getSortParamList() {
return this.sortParamList;
}
public enum SortParam {
FOO, BAR, FOOBAR;
}
}
Then your controller could look like this:
#RequestMapping(value = "/search", method = RequestMethod.GET)
public ResponseEntity<List<SortParams.SortParam>> search(#RequestParam("sort") SortParams sort) {
return ResponseEntity.ok(sort.getSortParamList());
}
Now your SortParams object has a list of SortParam:
curl "localhost:8080/search?sort=FOO,BAR"
["FOO","BAR"]
Would something like this fit what you're looking for?

It could be helpfull if Kotlin
private const val DELIMITER: Char = ':'
private val DEFAULT_DIRECTION: Sort.Direction = Sort.Direction.ASC
private fun parseFrom(source: String): Sort.Order = if (source.contains(DELIMITER)) {
Sort.Order(Sort.Direction.fromString(source.substringAfter(DELIMITER)), source.substringBefore(DELIMITER))
} else Sort.Order(DEFAULT_DIRECTION, source)
// if few sort paremeters
#Component
class RequestSortConverterArray : Converter<Array<String>, Sort> {
override fun convert(source: Array<String>): Sort? = if (source.isEmpty()) Sort.unsorted()
else source.map { parseFrom(it) }.let { Sort.by(it) }
}
// if single sort paremeter
#Component
class RequestSortConverterString : Converter<String, Sort> {
override fun convert(source: String): Sort? = if (source.isEmpty()) Sort.unsorted()
else Sort.by(parseFrom(source))
}
...
#GetMapping
fun search(
#RequestParam(required = false, defaultValue = "0") page: Int,
#RequestParam(required = false, defaultValue = "20") size: Int,
#RequestParam(required = false, defaultValue = "myfield1:asc") sort: Sort
) {
val pageable = PageRequest.of(page, size, sort)
// make a search by repository
}

Related

How can I get api with FeignClient

I used Lombok, Open Feign and Spring Web
I have currencyClient interface:
#FeignClient(value = "getcurrency", url = "https://openexchangerates.org")
public interface currencyClient {
#RequestMapping(value = "/api/historical/2012-07-10.json/{smt}", method = RequestMethod.GET)
public List<Object> getCurrency(#PathVariable String smt);
}
And Controller:
#RestController
#RequiredArgsConstructor
public class StatusController {
private String appId1 = "appId";
private final currencyClient currencyClient;
#GetMapping("/getAllCurrency")
public List<Object> getCurrency(){
return currencyClient.getCurrency(appId1);
}
}
And "http://localhost:1212/getAllCurrency" is not working cause the link is converted into "**https://openexchangerates.org/api/historical/2012-07-10.json/appId**" I understand that &/= are reversed and I also think that my indication of List is not correct. That's what I tried so how can I get info from "**https://openexchangerates.org/api/historical/2012-07-10.json?app_id**" as "http://localhost:1212/getAllCurrency"?
According to the https://docs.openexchangerates.org docs, the app_id should be a request parameter (see #RequestParam), not a path variable. You could do something like this:
CurrencyClient interface:
#FeignClient(value = "getcurrency", url = "https://openexchangerates.org")
public interface CurrencyClient {
#RequestMapping(value = "/api/historical/2012-07-10.json", method = RequestMethod.GET)
Map<String, Object> getCurrency(#RequestParam("app_id") String appId);
}
StatusController:
#RestController
public class StatusController {
private final CurrencyClient currencyClient;
public MyController(CurrencyClient currencyClient) {
this.currencyClient = currencyClient;
}
#GetMapping("/getAllCurrency")
public Map<String, Object> getCurrency() {
String appId1 = "*****";
return currencyClient.getCurrency(appId1);
}
}
Some additional things to note here:
Please don't post yout API key to StackOverflow, or anywhere else publicly. Other people might abuse it. Since you already posted it, you should request a new API key and get rid of this one (close it if possible).

Java: How to add mapper for streamed items

I currently have the following controller method (it's a bit simplified to only show the relevant part):
#PostMapping(...)
...
public ResponseEntity<List<PresignedUrlsResponse>> getPresignedUrlBatch(#Valid #RequestBody PresignedUrlsRequest urlsRequest) {
List<PresignedUrlsResponse> presignedUrlResponses = urlsRequest.getRequests().stream().map(request -> {
// TODO: put this in it's own mapping
String url = this.mediaService.getPresignedUrl(request.getObjectId(), request.getBucket());
PresignedUrlsResponse response = new PresignedUrlsResponse();
response.setId(request.getId());
response.setUrl(url);
return response;
}).collect(Collectors.toList());
return ResponseEntity.ok().body(presignedUrlResponses);
}
As mentioned in the TODO, I want to simplify this controller method and add a mapper. I'm only used to mapping requests from a db call for example (in which I will get a List of entities) but not when the service method has to be called for a list of items.
Is there a best practice for this?
MapStruct supports mapping Stream to Collection and Stream to Stream.
However, in your use case you start with List and not with Stream.
You can move your entire logic in a mapper.
e.g.
#Mapper(componentModel = "spring", uses = {
PresignedUrlMappingService.class
})
public interface PresignedUrlsMapper {
List<PresignedUrlsResponse> map(List<PresignedUrlsRequest> requests);
#Mapping(target = "url", source = "request", qualifiedByName = "presignedUrl")
PresignedUrlsResponse map(PresignedUrlsRequest request);
}
Your PresignedUrlMappingService can look like:
#Service
public class PresignedUrlMappingService {
protected final MediaService mediaService;
public PresignedUrlMappingService(MediaService mediaService) {
this.mediaService = mediaService;
}
#Named("presignedUrl")
public String presignedUrl(PresignedUrlsRequest request) {
return this.mediaService.getPresignedUrl(request.getObjectId(), request.getBucket())
}
}
and finally your controller method will look like:
#PostMapping(...)
...
public ResponseEntity<List<PresignedUrlsResponse>> getPresignedUrlBatch(#Valid #RequestBody PresignedUrlsRequest urlsRequest) {
return ResponseEntity.ok().body(presignedUrlsMapper.map(urlsRequest.getRequests());
}

How to create route that search by value?

I have a recipe app with several routes (get, getById, post, delete, etc).
So far it hasn't been difficult to implement them, since, for example, a get route would be like this:
Controller:
#RequestMapping(value = "/", method = RequestMethod.GET)
public List<Recipe> getAllRecipe() {
return recipesRepository.findAll();
}
Or by id:
Repository:
public interface RecipesRepository extends MongoRepository<Recipe, String> {
Recipe findBy_id(String _id);
}
Controller:
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
public Recipe getRecipeById(#PathVariable("id") String id) {
return recipesRepository.findBy_id(id);
}
I want to make a search screen in my app, so when you write something (like "straw") I get all results that contain the word "straw" in my database (like strawberry ice cream, strawberry milkshake...) in the name field (my model has a field called name).
My knowledge in Java isnt by any means good, so I'm lost here, so far I have a route that gets all results using "name", but I need it to search anything that contains that value, not only if you put the exact value (like if I put "Stew", it gives me "Stew" result if it exists, but I need to get every result that have the word "Stew" in their name). If it helps, this is the code for that:
Repository:
public interface RecipesRepository extends MongoRepository<Recipe, String> {
Recipe findBy_id(String _id);
Recipe findByName(String name);
}
Controller:
#RequestMapping(value = "/recipe/{name}", method = RequestMethod.GET)
public Recipe getRecipeByName(#PathVariable("name") String name) {
return recipesRepository.findByName(name);
}
About the case sensitive problem, you can add keywords, like this:
public interface RecipesRepository extends MongoRepository<Recipe, String> {
{
public List<Recipe> findByNameLikeIgnoreCase(String name);
}
I think you can use smth like this one :
public interface RecipesRepository extends MongoRepository<Recipe, String> {
{
public List<Recipe> findByNameLike(String name);
}

How to do post request on a method returns integer

in the below example, I am trying to know how in he below code, i am trying to perform POST request given the method initVar ().
I tried to do the post request as follows:
http://localhost:8080/root/initVar/id/2->error
http://localhost:8080/root/initVar/id?2->error
but it does not work when i return Int data type form the method.
please let me know how to fix this error.
please note that this method return Int
code:
#RestController
#RequestMapping("root")
class Controller1 {
val sayHiLazy by lazy {
"hi from get"
}
#GetMapping(value = "/sayHi")
#ResponseBody
fun sayHi() : String {
return sayHiLazy
}
#PostMapping("/initVar/id")
#ResponseBody
fun initVar(#PathVariable id : Int) : Int {
return 11
}
}to use spring annotation with koltin in intellij. i used spring annotation with java projects in intellij. but for koltin, it does not work.
in the below example, HelloController is underlined with red and i recived the following werror:
classes annotated with #Configurations cannto be implicily subclassed and must be final
please let me know how to fi
Controller1
#SpringBootConfiguration
#RequestMapping("/app")
public class HelloController {
#RequestMapping(value = "/hello")
fun SayHello(): String {
return "success"
}
}
If You want to use #PathVariable, You have to do something like this:
#RequestMapping(value="/stuff/{id}")
#ResponseBody
public String doStuff(#PathVariable("id") int id){
return "id="+id;
}
You usually write POST method without variables at the end. For such scenarios You should use PUT - https://restfulapi.net/rest-put-vs-post/
I'm not sure if You can return plain integer as a response, but You could optionally wrap your integer into String - String.valuOf(id);
Regards

Spring Rest Controller, Path Variables on an overriden method's arguement

I have a controller annotated with #RestController and it implements an interface:
public interface ContratEndpoint {
String ROOT = "/api/contrats";
String GET_CONTRAT = "";
String GET_CONTRAT_PER_PK = "/{idContrat}";
#RequestMapping(value = GET_CONTRAT)
Contrat getContrat(#RequestParam(value = "contratId")Long contratId);
#RequestMapping(value = GET_CONTRAT_PER_ID)
ExtContrat getContratById(#PathVariable("idContrat") Long idContrat);
}
The controller:
#RestController
#RequestMapping(value = ContratEndpoint.ROOT)
public class ContratController implements ContratEndpoint {
//Injecting Services....
#Resource
private Mapper mapper;
#Override
public Contrat getContrat(Long contratId) {
return mapper.map(contratService.get(contratId),Contrat.class);
}
#Override
public ExtContrat getContratById(#PathVariable("idContrat") Long idContrat){
Preconditions.checkArgument(idContrat !=null);
return mapper.map(contratService.get(idContrat),ExtContrat.class);
}
.The above Code works just fine.
. But For the first inherited method , I didn't have to annotate arguments with #RequestParam and it worked just fine.
As for the second method I tried at first :
#Override
public ExtContrat getContratById(Long idContrat){
Preconditions.checkArgument(idContrat !=null);
return mapper.map(contratService.get(idContrat),ExtContrat.class);
}
. I expected the same behaviour Like the first Method, But i was wrong and the code ended up firing an IllegalArgumentException because of the check in ligne Preconditions.checkArgument(idContrat!=null).
My question is what is so specific about #PathVariable that i've missed ?
Or is it just something is wrong with my approach?
Thanks.
There is difference between Request param and path variable,seee below post that you can confirm with your uri the cause for the exception :
#PathVariable is to obtain some placeholder from the uri (Spring call it an URI Template) — see Spring Reference Chapter 16.3.2.2 URI Template Patterns
#RequestParam is to obtain an parameter — see Spring Reference Chapter 16.3.3.3 Binding request parameters to method parameters with #RequestParam
Assume this Url http://localhost:8080/SomeApp/user/1234/invoices?date=12-05-2013 (to get the invoices for user 1234 for today)
#RequestMapping(value="/user/{userId}/invoices", method = RequestMethod.GET)
public List<Invoice> listUsersInvoices(
#PathVariable("userId") int user,
#RequestParam(value = "date", required = false) Date dateOrNull) {
...
}

Categories

Resources