I have 2 #RequestParam parameters in my Controller. I want to set the Required value of both the parameters based on a Condition. The condition could be like, if one of the parameter is passed, the other has to passed. So set the required of other as true and vice-versa. Otherwise set both false if none of the parameters are passed.
#RestController
public class TestController {
#GetMapping("/test")
public void test(#RequestParam(value = "a", required = (b !=null) String a,
#RequestParam(value = "b", required = (a !=null) ) String b,) {
{
}
}
The syntax of using the variable name inside #RequestParam() is wrong, but I wanted to explain what I want.
You can do it using one of the 2 following ways:
Using Spring AOP and create a surrounding aspect for that request
mapping
Using HandlerInterceptorAdapter to intercept the requests for a given URI
1. Using Spring AOP
Create an annotation like the following:
public #interface RequestParameterPairValidation {
}
Then you can annotate your request mapping method with it:
#GetMapping("/test")
#RequestParameterPairValidation
public void test(
#RequestParam(value = "a", required = false) String a,
#RequestParam(value = "b", required = false) String b) {
// API code goes here...
}
Create an aspect around the annotation. Something like:
#Aspect
#Component
public class RequestParameterPairValidationAspect {
#Around("#annotation(x.y.z.RequestParameterPairValidation) && execution(public * *(..))")
public Object time(final ProceedingJoinPoint joinPoint) throws Throwable {
Object[] requestMappingArgs = joinPoint.getArgs();
String a = (String) requestMappingArgs[0];
String b = (String) requestMappingArgs[1];
boolean requestIsValid = //... execute validation logic here
if (requestIsValid) {
return joinPoint.proceed();
} else {
throw new IllegalArgumentException("illegal request");
}
}
}
Note that it would be a good option to return 400 BAD REQUEST here since the request was not valid. Depends on the context, of course, but this is a general rule of thumb to start with.
2. Using HandlerInterceptorAdapter
Create a new interceptor mapping to your desired URI (in this case /test):
#Configuration
public class CustomInterceptor extends WebMvcConfigurerAdapter {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry
.addInterceptor(new CustomRequestParameterPairInterceptor())
.addPathPatterns("/test");
}
}
Define the logic for validation inside the custom interceptor:
public class CustomRequestParameterPairInterceptor extends HandlerInterceptorAdapter {
#Override
public void afterCompletion(HttpServletRequest req, HttpServletResponse res, Object obj, Exception exception) throws Exception {
}
#Override
public void postHandle(HttpServletRequest req, HttpServletResponse res, Object obj, ModelAndView modelAndView) throws Exception {
}
#Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) throws Exception {
// Run your validation logic here
}
}
I would say the 2nd option is the best one since you can directly control the answer of the request. In this case it might be a 400 BAD REQUEST, or anything else that makes more sense in your case.
You can use Optional here in an intelligent manner here like this:
#GetMapping("/test")
#RequestParameterPairValidation
public void test(#RequestParam("a") Optional<String> a,
#RequestParam("b") Optional<String> b){
String aVal = a.isPresent() ? a.get() : null;
String bVal = b.isPresent() ? b.get() : null;
//go for service call here based on your logic
}
I hope this works for your requirement.
You can use Java EE #Size Validation annotation with Spring (but you must have a Java EE validation API implementor on the classpath, i.e hibernate ). With hibernate, you can import this dependency using maven
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.10.Final</version>
</dependency>
Then the entire thing becomes:
#RestController
#Validated
public class TestController {
#GetMapping("/test")
public void test(#RequestParam(value = "a", required = true ) #Size(min=1) String a,
#RequestParam(value = "b", required = true) #Size(min=1) String b) {
{
}
}
In Java you can pass only constants as parameters of any annotation.
That's why it's impossible to do it this way.
However, you can validate all that kind of things in the method itself.
Related
I would like my API to return errorMessage when the request lacks of required parameters. For example let's say there is a method:
#GET
#Path("/{foo}")
public Response doSth(#PathParam("foo") String foo, #NotNull #QueryParam("bar") String bar, #NotNull #QueryParam("baz") String baz)
where #NotNull is from package javax.validation.constraints.
I wrote an exception mapper which looks like this:
#Provider
public class Mapper extends ExceptionMapper<ConstraintViolationException> {
#Override
public Response toResponse(ConstraintViolationException) {
Iterator<ConstraintViolation<?>> it= exception.getConstraintViolations().iterator();
StringBuilder sb = new StringBuilder();
while(it.hasNext()) {
ConstraintViolation<?> next = it.next();
sb.append(next.getPropertyPath().toString()).append(" is null");
}
// create errorMessage entity and return it with apropriate status
}
but next.getPropertyPath().toString() returns string in format method_name.arg_no, f.e. fooBar.arg1 is null
I'd like to receive output fooBar.baz is null or simply baz is null.
My solution was to include -parameters parameter for javac but to no avail.
Probably I could somehow achieve it with the use of filters:
public class Filter implements ContainerResponseFilter {
#Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) {
UriInfo uriInfo = requestContext.getUriInfo();
UriRoutingContext routingContext = (UriRoutingContext) uriInfo;
Throwable mappedThrowable = routingContext.getMappedThrowable();
if (mappedThrowable != null) {
Method resourceMethod = routingContext.getResourceMethod();
Parameter[] parameters = resourceMethod.getParameters();
// somehow transfer these parameters to exceptionMapper (?)
}
}
}
The only problem with the above idea is that ExeptionMapper is executed first, then the filter is executed. Also I have no idea how could I possibly transfer errorMessage between ExceptionMapper and Filter. Maybe there is another way?
You can inject ResourceInfo into the exception mapper to get the resource method.
#Provider
public class Mapper extends ExceptionMapper<ConstraintViolationException> {
#Context
private ResourceInfo resourceInfo;
#Override
public Response toResponse(ConstraintViolationException ex) {
Method resourceMethod = resourceInfo.getResourceMethod();
Parameter[] parameters = resourceMethod.getParameters();
}
}
I'm currently trying to modify, (via a Spring filter) some of the request variables being posted into a form.
Reason being, I would like to implement better phone number validation, and better control how telephone numbers are formatted. For that part of the puzzle, I intend to use Google's Lib phone number in my model so like so:
private PhoneNumber mobileNumber;
One getter, with no mention of the prefix at all, given that the filter will hopefully do the hard work for me.
I initially thought that perhaps I could use an attribute converter to do this i.e.
#Convert(converter = PhoneNumberConverter.class )
private PhoneNumber mobileNumber;
However, there is a problem with that, in that if the field is a composite type, the JPA doesn't support it: https://github.com/javaee/jpa-spec/issues/105 (compositie because PREFIX is needed as well as NUMBER) to build a lib phone object.
So. A filter (or Interceptor?) is what I'm left with. My question is, I'm new to the Spring framework and I'm not 100% sure whether just modifying the raw request will allow instantiation of the PhoneNumber object in the model - (I presume not), but any guidance on how Spring manages to do its magic tying up of request variables into an object (by mapping getters and setters) and how I would go about doing this manually in the filter would be helpful. Is there any way of access this Model object in the filter so I can set it directly?
public class PhonePrefixFilter extends OncePerRequestFilter
{
#Override
protected void doFilterInternal( HttpServletRequest request, HttpServletResponse response, FilterChain filterChain )
throws ServletException, IOException
{
String prefix = request.getParameter( "phonePrefix" );
if( StringUtils.isNotEmpty( prefix ) )
request.setAttribute( "mobileNumber", prefix + request.getAttribute( "mobileNumber" ) );
filterChain.doFilter( request, response );
}
}
AFAIK you cannot modify your request parameter directly. You need a HttpServletRequestWrapper to provide custom getter to your parameter:
public static class PhoneRequestWrapper extends HttpServletRequestWrapper {
public PhoneRequestWrapper(HttpServletRequest request) {
super(request);
}
#Override
public String getParameter(String name) {
if (!("mobileNumber").equals(name)) {
return super.getParameter(name);
}
String prefix = getParameter("phonePrefix");
String mobileNumber = getRequest().getParameter("mobileNumber");
if (StringUtils.isNotEmpty(prefix) && StringUtils.isNotEmpty(mobileNumber)) {
return prefix + getRequest().getParameter("mobileNumber");
} else {
return mobileNumber;
}
}
}
Then create your filter:
public static class PhoneNumberFilter implements Filter {
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws
ServletException,
IOException {
filterChain.doFilter(new PhoneRequestWrapper((HttpServletRequest) request), response);
}
#Override
public void destroy() {
}
}
And register it in your #Configuration class:
#Bean
public Filter filterRegistrationBean() {
return new PhoneNumberFilter();
}
From now on, your request.getParamter("mobileNumber") will have the value appended with phonePrefix
Since your question is not very clear, if you want to override the behaviour of #RequestParam to get your phone number string, you can use custom HandlerMethodArgumentResolver to resolve your parameter
I want to create an interceptor that writes a value to the #RequestBody by condition. But how can I intercept right before the #PostMapping is called by spring?
#RestController
public class PersonServlet {
#PostMapping("/person")
public void createPerson(#RequestBody Person p) {
//business logic
}
class Person {
String firstname, lastname;
boolean getQueryParamPresent = false;
}
}
Then I send the POST body:
{
"firstname": "John",
"lastname": "Doe"
}
To url: localhost:8080?_someparam=val
My goal is to detect if any query param is present, and then directly write to the Person object that has been generated from the POST body.
I know I could achieve this easily within the servlet method. BUT as this is just an example, I want to apply this logic globally to all requests. Thus, for not having to repeat the same code call on every POST request, I'd like to have some kind of interceptor to write directly to the generated object (reflection would be fine).
But: is that possible? What method is executed by spring right before the #PostMapping? Maybe one could hook up there?
in spring the messageConverters are responsible for (de-)serializing json strings into objects. In your case this should be the MappingJackson2HttpMessageConverter.
You could overwrite it with your own implementation and overwrite the read method like this:
#Service
public class MyMessageConverter extends MappingJackson2HttpMessageConverter
#Autowired Provider<HttpServletRequest> request;
#Override
public Object read(Type type, #Nullable Class<?> contextClass, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
Object result = super.read(type, contextClass, inputMessage);
if (result instanceof Person) {
HttpServletRequest req = request.get();
// Do custom stuff with the request variables here...
}
}
You can register than your own custom messageConverter by implementing your own WebMvcConfigurer and overwrite the configureMessageConverters method.
Couldn't try it here, but this should work!
In my controller I have a method such as bellow:
public QueryResult<TrsAccount> listExclude(String codeAccount, String searchFilter, String order, int pageNumber,
int pageSize){}
But before executing this method I have to chech if:
Assert.TRUE(codeAccount.matches("^[0-9]{1,20}$"));
Because this is very frequent in my application and it is not only this case, I want a general approach to check the argument format. The way I'm using now is the use of AOP, in which:
#Aspect
public class HijackBeforeMethod {
#Pointcut("within(#org.springframework.stereotype.Controller *)")
public void controllerBean() {
}
#Pointcut("execution(* *(..))")
public void methodPointcut() {
}
#Before(value = "controllerBean() && methodPointcut()", argNames = "joinPoint")
public void before(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Object[] args = joinPoint.getArgs();
String[] paramNames = signature.getParameterNames();
for (int count = 0; count < paramNames.length; count++) {
String tempParam = paramNames[count];
Object tempValue = args[count];
if (tempParam.toLowerCase().equalsIgnoreCase("codeAccount") && Assert.isNotNull(tempValue)
&& Assert.isNotEmpty((String) tempValue)) {
Assert.TRUE(((String) tempValue).matches("^[0-9]{1,20}$"));
}
}
}
}
As you can see, this is very rudimentary and error prone code snippet. Is there any better solutions??
Using AOP in Controllers is not really recommended. A better approach would be to use JSR 303 / JSR 349 Bean Validation, but that would probably require wrapping the string in a value object, which is then annotated accordingly.
If you insist on solving this with AOP, you'll probably need a ControllerAdvice
Just like #Sean Patrick Floyd said, using Bean Validation is more advisable.
Firstly, define a class which extends from org.springframework.validation.Validator like:
#Component
public class CodeAccountValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return String.class.equals(clazz);
}
#Override
public void validate(Object target, Errors errors) {
if (Assert.isNotNull(target) && Assert.isNotEmpty((String) target)) {
Assert.TRUE(((String) target).matches("^[0-9]{1,20}$"));
}
}
}
Then add #Validated annotation to your controller like:
public QueryResult<TrsAccount> listExclude(
#Validated(CodeAccountValidator.class)String codeAccount,
String searchFilter,
String order, int pageNumber,
int pageSize) {
... ...
}
Trying to solve this with AOP is something you shouldn't do. Instead use an object to bind your properties and validate that object.
public class QueryCriteria {
private String codeAccount;
private String searchFilter;
private int pageNumber;
private int pageSize;
private String order;
// Getters / Setters.
}
Then modify your controller method
public QueryResult<TrsAccount> listExclude(#Valid QueryCriteria criteria, BIndingResult result) { ... }
Then either use a Spring Validator which validates what you need .
public QueryCriteriaValidator implements Validator {
private final Pattern ACCOUNT_EXPR = Pattern.compile("^[0-9]{1,20}$");
public boolean supports(Class<?> clazz) {
return QueryCriteria.isAssignable(clazz);
}
public void validate(Object target, Errors errors) {
final QueryCriteria criteria = (QueryCriteria) target;
if (!ACCOUNT_EXPR.matcher(criteria.getCodeAccount()).matches()) {
errors.rejectValue("codeAccount", "invalid.format");
}
}
}
In an #InitBinder in your controller method register this validator
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.setValidator(new QueryCriteriaValidator());
}
When using JSR-303 you don't need this and you could simply annotate the codeAccount field with the #Pattern annotation.
#Pattern(regexp="^[0-9]{1,20}$")
private String codeAccount;
The validation works nicely together with Spring MVC and error reporting using I18N. So instead of trying to hack around it with exceptions work with the framework.
I suggest a read of the validation section and binding section of the Spring Reference guide.
I develop REST application based on Spring MVC.
I have following hierarhy of Controllers:
UserController extends ImageController
ImageController extends SuggestController // Controller that has GET-DELETE-POST for image
SuggestController extends CrudController// Controller that has some suggest methods
CrudController // CRUD operations
So I had following mappings in runtume:
/users (POST-GET-PUT-DELETE)
/users/suggest (GET, POST(Spring Pageable))
/users/image (POST-GET-DELETE)
It was ok before I realized, that one controller must be able to give me images of its stuff, but cannot implement "suggest" methods:
/stuff (POST-GET-PUT-DELETE)
/stuff/image (POST-GET-DELETE)
And another one does not have "image" functionality, but has "suggest":
/things (POST-GET-PUT-DELETE)
/things/suggest (GET, POST(Spring Pageable))
Java says : "Use composition in such cases":
StuffController {
#InlineThisController
ImageController imagedController;
#InlineThisController
CrudController crudController;
... //some additional methods
}
So how can I acheive this in Spring MVC without boilerplate for delegation? Annotations? XML?
Spring MVC will not allow you to override method annotated with #RequestMapping, or more exactly it does not allow you to annotate the overriding method with #RequestMapping and will use the mapping in base class.
But you can always define 2 methods in base class : one annotated with #RequestMapping that only delegates to second not annotated. Then you are then free to override the second method in subclasses. Example :
Abstract base class for a CRUD controller
public abstract class AbstractCRUDController<K extends Serializable, X>
// X is the data class, K is the class of the key (int or String)
#RequestMapping({"/{data}"})
public String show(#ModelAttribute("data") X data, HttpSession session, Model model,
#RequestParam(value = "pagesize", required = false, defaultValue = "-1") int pageSize,
WebRequest request,
#RequestParam(value = "all", defaultValue = "false") boolean all) {
return doShow(data, session, model, pageSize, request, all);
}
protected String doShow(X data, HttpSession session, Model model,
int pageSize, WebRequest request, boolean all) {
return this.getPrefix() + "/view";
}
#RequestMapping(value={"/{data}/edit"}, method=RequestMethod.GET)
public String edit(#ModelAttribute("data") X data, Model model) {
return doEdit(data, model);
}
protected String doEdit(#ModelAttribute("data") X data, Model model) {
model.addAttribute("title", editTitle);
return this.getPrefix() + "/edit";
}
#RequestMapping(value={"/{data}/edit"}, method=RequestMethod.POST)
public String update(#ModelAttribute("data") X data, BindingResult result, Model model) {
if (data == null) {
throw new NoSuchElementException();
}
if (save(data, result, model, SimpleSaveType.UPDATE, null) != null) {
return "redirect:" + savedUrl(data);
}
else {
model.addAttribute("title", editTitle);
return getPrefix() + "/edit";
}
}
public K save(X data, BindingResult result, Model model, SaveType saveType, K id) {
...
}
...
public abstract String getPrefix();
}
Concrete implementation for class ProcessQ
#Controller
#RequestMapping(ProcessController.PREFIX)
public class ProcessController extends AbstractCRUDController<Integer, ProcessQ> {
public static final String PREFIX = "/process";
#Override
public String getPrefix() {
return PREFIX;
}
#Override
protected String doShow(ProcessQ process, HttpSession session, Model model,
int pageSize, WebRequest request, boolean all) {
String viewName = super.doShow(process, session, model, pageSize, request, all);
// specialized processing for class ProcessQ
...
return viewName;
}
...
}
Example is taken from a real program, that's the reason why you can see elements for pagination, error processing and access to underlying request.
Here are possible approaches:
Use Lombok's #Delegate
Use Kotlin's delegation support
Use Java's default interface methods (looks uglier than 1 and 2)