Allowed values for RequestParam in get url - java

Is it possible to set possible values for #RequestParam(value = "id") String id)
?
The ideal is: I give some List<String> allowedValues and it automatically validate it. (This list will be loaded from database).

use an enum instead of a String and define your enum values
#RequestMapping(value = "yourRestEndPoint", method = RequestMethod.GET)
public anyReturnType methodName(#RequestParam(defaultValue = "ONE") IdValue id) {
....
}
and have a separate class defined e.g
public enum IdValue {
ONE, TWO, THREE
}

It is possible but not some magical way but by simple Validation checks.
Eg:
#RequestMapping(value = "yourRestEndPoint", method = RequestMethod.GET)
public anyReturnType methodName(#RequestParam(value = "id") String id){
List<String> allowedValues = getAllowedValuesFromDB();
if(allowedValues.contains(id)){//check if incoming ID belongs to Allowed Values
//Do your action....
}
}
private List<String> getAllowedValuesFromDB(){
List<String> allowedValues = new ArrayList<>();
//fetch list from DB
//set fetched values to above List
return allowedValues;
}
Second Way
If you want to do it like we do Bean Validation, using #Valid, which is for Bean not just a single parameter and also required Validator to be configured, then check out this & this answer.
Hope this helps.

Related

How do i differentiate between two endpoints, each with one PathVariable?

I'm working on a Spring Boot application. I have the following REST endpoint(s):
#GetMapping(value = { "/person/{name}", "/person/{age}" })
public PersonData getPersonData(#PathVariable(required = false) String name,
#PathVariable(required = false) Integer age) {
}
This endpoint can be called with either a name variable or an age variable, however it looks like it can't differentiate between them. If I was to call '.../person/20', it would not call "/person/{age}", but it always calls "/person/{name}".
I know I could make something like:
#GetMapping(value = { "/person/name/{name}", "/person/age/{age}" })
However are there any other way to solve it without adding anything to the path?
A path variable is something like a primary key usually.
Like:
/person/{id}
What you try is to search for data and this should be done with query parameters.
Example:
#GetMapping(value = { "/person/{name}", "/person/{age}" })
public PersonData getPersonData(#RequestParam String name,
#RequestParam Integer age) {
}
Then you can call it
/person?age=40
/person?name=Peter
age and name are logically not the same thing; the chosen best answer correctly suggests to keep them as distinguished parameters.
However you can check if the value is numeric and treat it like an age,
#GetMapping("/person/{value}")
public String getPerson(#PathVariable String value) {
if (value.matches("[0-9]|[0-9][0-9]"))
return "Age";
else
return "Name";
}
but this is ambiguous and error prone (e.g. how'll you distinguish when adding other numerical params like shoeSize or numberOfPartners?).
In your case I would make 2 different endpoints to be more clear, each one requiring it's own query parameter to be served.
#GetMapping(value = "/person")
public PersonData getPersonDataByName(#RequestParam(required = true) String name) {
....
}
#GetMapping(value = "/person")
public PersonData getPersonDataByAge(#RequestParam(required = true) Integer age) {
....
}
required = true can be omitted from the annotation as this is the default value, I used it just to point that each endpoint will be fulfilled only for that specific query parameter

Make Sort ignore field in model

I've encountered a problem:
I'm accepting the sortBy string in the controller and creating Sort object with Sort.by(sortBy).
The problem is that I can't seem to find a way to block fields from being sorted.
For example, I have:
#Column(nullable = false)
private String encryptedPassword;
I would like to block being able to sort by a password.
My controller method:
#GetMapping(produces = { MediaTypes.HAL_JSON_VALUE, MediaType.APPLICATION_XML_VALUE }, consumes = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
public PagedModel<EntityModel<UserDetailsResponse>> getUsers(#RequestParam(required = false, defaultValue = "0") Integer page,
#RequestParam Integer size,
#RequestParam(required = false, defaultValue = "createDate") String sortBy,
#RequestParam(required = false) String order) {
Sort.Direction sortDirection = Sort.Direction.fromOptionalString(order)
.orElse(Sort.Direction.DESC);
PageRequest pageRequest = PageRequest.of(page, size, Sort.by(sortDirection, sortBy));
Page<UserDTO> users = userService.getUsers(pageRequest);
Page<UserDetailsResponse> userDetailsResponses = users.stream()
.map((userDTO) -> modelMapper.map(userDTO, UserDetailsResponse.class))
.map(this::addRelations)
.collect(Collectors.collectingAndThen(Collectors.toList(), PageImpl::new));
return pagedResourcesAssembler.toModel(userDetailsResponses);
}
Of course, I could create a blacklist with field names, but I'm looking for a non-hardcoding way.
Is there some annotation or other way I could use to achieve that?
Since there doesn't appear to be any annotation for that, I wrote my own.
If someone stumbles upon this problem here's the code:
Annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface SortBlacklisted {
}
SortBlackListUtil:
#Component
public class SortBlackListUtil {
/**
* Looks for fields annotated with #SortBlacklisted in the specified class
* and creates blacklist form their names.
*
* #param classToLookIn Class to search in for #SortBlacklisted annotation.
* #param <T> Class object type.
* #return List of blacklisted fields names for the specified class.
*/
public <T> ArrayList<String> getBlackListedFields(Class<T> classToLookIn) {
ArrayList<String> blackListedFieldsNames = new ArrayList<>();
for (Field field : classToLookIn.getDeclaredFields()) {
SortBlacklisted sortBlacklisted = field.getAnnotation(SortBlacklisted.class);
if (sortBlacklisted != null) blackListedFieldsNames.add(field.getName());
}
return blackListedFieldsNames;
}
}
Now simply place that an annotation on the desired field
#SortBlacklisted
private String encryptedPassword;
Use that method to get a list of blacklisted fields names:
sortBlackListUtil.getBlackListedFields(MyClass.class)

How to return only an one instance in java?

I am coming to an issue where I am getting three objects instead of one object itself. (You can see my schema and the output result I am getting below). All I need to do is return a list of the Object Mapper that can transforms the collection in an array. In my controller this is the cause "public #ResponseBody List" please help me to have it print like my schema. Thank you for the help..!
Controller:
#RestController
public class AutoCompleteController {
private AutoCompleteService autoCompleteService;
private EntityManager em;
public AutoCompleteController(AutoCompleteService autoCompleteService, EntityManager em){
this.autoCompleteService = autoCompleteService;
this.em = em;
}
#RequestMapping(value = "jobs", method = RequestMethod.GET)
public #ResponseBody List<AutoComplete> getSalary(#RequestParam(value = "autocomplete") String autocompleteValue) {
return autoCompleteService.retrieveSalary(autocompleteValue);
}
public void getAllSalaries(HttpServletResponse res) {
Stream<AutoComplete> stream = autoCompleteService.retrieveAllSalaries();
DataStreamUtility.streamObjects(stream, res, em);
}
#RequestMapping(value = "/jobs")
public #ResponseBody List < AutoComplete > getSalary(#RequestParam(value = "autocomplete", defaultValue = "1400") String autocompleteValue) {
return autoCompleteService.retrieveSalary(autocompleteValue);
}
public void getAllSalaries(HttpServletResponse res) {
Stream < AutoComplete > stream = autoCompleteService.retrieveAllSalaries();
DataStreamUtility.streamObjects(stream, res, em);
}
}
Instead of list return single object
public #ResponseBody AutoComplete getSalary();
If I've understood your intention correctly, the problem here is that you're retrieving a list of entities where you only desired a single entity.
autocompleteRepository.findByJobClassCdStartsWith(jobClassCd);
In a nutshell: The "magic" findByXXX methods return either a list or a single entity; you should use findOneByXXXX instead to clarify.
Check this answer for further info, I think it's pretty well explained:
Difference between findBy and findOneBy in Spring data JPA
In one of your comments you say my rateType it just only prints "annual" ... the other hourly and weekly rate types does not return. By any chance have you created 3 objects with the same id but different rateTypes? It sounds to me like you need to make the rates an array each.
public String[] rateAnnual;
public String[] rateBiweekly;
public String[] Hourly;

Multiple Sort Optional query - Spring REST Controller configuration with Pagination

I want to build a spring controller that can handle multiple, optional sort queries. According to spring.io spec these queries should be formatted thus
&sort=name,desc&sort=code,desc
As discussed by EduardoFernandes
I know this can be done with one instance of sort with value to be sorted and the direction give separately as per Gregg but that doesn't match the Spring spec or handle multiple sort values.
I'm not sure how to turn multiple queries in the spring spec format into a Sort that I can pass to my PageRequest and then on to my repository. Also I would like the ability to make these optional and if possible, it would be great if I could use #Anotation based config if defaults are necessary to achieve this as per Rasheed Amir (#SortDefault)
Here is the basics of what I'm working with..
Domain
#Entity
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class Subject {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String code;
...
Repository
public interface SubjectRepository extends JpaRepository<Subject, Long> {
}
Service
#Override
public Page<SubjectDTO> listSubjectsPageble(PageRequest pageableRequest) {
return subjectRepository.findAll(pageableRequest)
.map(subjectMapper::subjectToSubjectDTO);
}
Controller
#GetMapping
#ResponseStatus(HttpStatus.OK)
#PreAuthorize("hasRole('LECTURER')")
public Page<SubjectDTO> getSubjects(
#RequestParam("page") int page,
#RequestParam("size") int size,
#RequestParam("sort") String sort
) {
return subjectService.listSubjectsPageble(PageRequest.of(page, size, new Sort(sort)));
}
So here in the controller I don't know how to deal with\populate the Sort from the RequestParam at all, according to Ralph I should be able to use something like the below to get multiple values from one param, but I don't know how to then pass that to a Sort.
I know a Sort can take more than one parameter but only one sort direction. And then of coarse I would like to make them optional.
#RequestParam MultiValueMap<String, String> params
Please help, I'm still quite a noob :)
Thanks
EDIT
I solved some of my issues thanks to a post by Dullimeister But the approach feels a little messy and still doesn't handle multiple sort parameters. Does anyone know of better approach or is this the way to go?
#GetMapping
#ResponseStatus(HttpStatus.OK)
#PreAuthorize("hasRole('LECTURER')")
public Page<SubjectDTO> getSubjects(
#RequestParam(value = "page", defaultValue = "0", required = false) int page,
#RequestParam(value = "size", defaultValue = "10", required = false) int size,
#RequestParam(value = "sort", defaultValue = "name,ASC", required = false) String sortBy
) {
String[] sort = sortBy.split(",");
String evalSort = sort[0];
String sortDirection = sort[1];
Sort.Direction evalDirection = replaceOrderStringThroughDirection(sortDirection);
Sort sortOrderIgnoreCase = Sort.by(new Sort.Order(evalDirection,evalSort).ignoreCase());
return subjectService.listSubjectsPageble(PageRequest.of(page, size, sortOrderIgnoreCase));
}
private Sort.Direction replaceOrderStringThroughDirection(String sortDirection) {
if (sortDirection.equalsIgnoreCase("DESC")){
return Sort.Direction.DESC;
} else {
return Sort.Direction.ASC;
}
}
Final Solution
Thanks everyone, this is what I ended up with. Not sure if its the perfect way but it works :) I had to replace the comma with a semi-colon in the end as the FormattingConversionService was automatically parsing a single sort param to a string instead of an Sting[]
#GetMapping
#ResponseStatus(HttpStatus.OK)
#PreAuthorize("hasRole('LECTURER')")
public Page<SubjectDTO> getSubjects(
#RequestParam(value = "page", defaultValue = "0", required = false) int page,
#RequestParam(value = "size", defaultValue = "10", required = false) int size,
#RequestParam(value = "sort", defaultValue = "name;ASC", required = false) String[] sortBy
Sort allSorts = Sort.by(
Arrays.stream(sortBy)
.map(sort -> sort.split(";", 2))
.map(array ->
new Sort.Order(replaceOrderStringThroughDirection(array[1]),array[0]).ignoreCase()
).collect(Collectors.toList())
);
return subjectService.listSubjectsPageble(PageRequest.of(page, size, allSorts));
private Sort.Direction replaceOrderStringThroughDirection(String sortDirection) {
if (sortDirection.equalsIgnoreCase("DESC")){
return Sort.Direction.DESC;
} else {
return Sort.Direction.ASC;
}
Why don't you use Pageable in your controller ?
Pageable can handle many sort queries, each of them will be stored in orders list.
Moreover, any of pageable parameters aren't required. When you don't pass them in url, pageable will contains default values (page = 0, size = 20). You can change default values by using #PageableDefault annotation.
GET .../test?sort=name,desc&sort=code,desc

Multiple route with the same pattern url

I m actually creating a simple application and I need to have routing pattern identical in multiple case :
/*
* Returns a list of all the root directories accepting query string on name
*/
#RequestMapping(value = "/directories", method = RequestMethod.GET)
public List<DirectoryEntity> find() {
return directoryService.findAll();
}
/*
* Returns a list of all the root directories accepting query string on name
* #param name Name of the ressources to search. Query string at format : *name*
*/
#RequestMapping(value = "/directories", method = RequestMethod.GET)
public List<DirectoryEntity> findByCriteria(#RequestParam(value = "name", required = true) String name) {
return directoryService.findByName(name);
}
In fact, I dont want to manage criteria request in the same function as findAll one. Is there anyway to handle this case without be forced to manage everything inside the same function ?
Thanks for advance
Try changing the second method #RequestMapping annotation adding params:
#RequestMapping(value = "/directories", method = RequestMethod.GET, params = "name")
public List<DirectoryEntity> findByCriteria(#RequestParam(value = "name", required = true) String name) {
return directoryService.findByName(name);
}
See also the Spring Documentation for more details.
I'm not quite sure what you are asking, but assuming the decision as to which method to call is based on request parameters (it must be since they're both the same URL and HTTP method), then something like this might help:
#RequestMapping(method=RequestMethod.GET, params={"name"})
public #ResponseBody List<DirectoryEntity> findByCriteria(#RequestParam(value = "name", required = true) String name) {
//do your stuff
}
The inclusion of the params attribute in the #RequestMapping annotation removes the ambiguity in which method to call.
I've also added #ResponseBody to the return type, just in case you want Spring to return the list in the HTTP response.

Categories

Resources