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.
Related
In a service file I would simply use #Value and initialize the variable instially there. I have tried this approach in a model class but (I assume how things get autowired and that its a model class) this results in it always being null.
The need for this comes out that in different environments the default value is always different.
#Value("${type}")
private String type;
I would avoid trying to use Spring logic inside the models as they are not Spring beans themselves. Maybe use some form of a creational (pattern) bean in which the models are constructed, for example:
#Component
public class ModelFactory {
#Value("${some.value}")
private String someValue;
public SomeModel createNewInstance(Class<SomeModel> clazz) {
return new SomeModel(someValue);
}
}
public class SomeModel {
private String someValue;
public SomeModel(String someValue) {
this.someValue = someValue;
}
public String getSomeValue() {
return someValue;
}
}
#ExtendWith({SpringExtension.class})
#TestPropertySource(properties = "some.value=" + ModelFactoryTest.TEST_VALUE)
#Import(ModelFactory.class)
class ModelFactoryTest {
protected static final String TEST_VALUE = "testValue";
#Autowired
private ModelFactory modelFactory;
#Test
public void test() {
SomeModel someModel = modelFactory.createNewInstance(SomeModel.class);
Assertions.assertEquals(TEST_VALUE, someModel.getSomeValue());
}
}
I want to apply "stern" strategy on my rest api endpoint (https://stackoverflow.com/a/20597044/4828427). As parameter i am not using JsonObject but MyObject.
#RequestMapping(value = "/api/v1/authenticate", method = RequestMethod.POST)
public ResponseEntity<TokenDTO> createAuthenticationToken(#RequestBody JwtRequest authenticationRequest) {
So i let spring deserializate json to object and i am using object dirrectly.
Example:
authenticationRequest.getUsername();
I am looking for some Utils or annotations to detect if there are any unused json elements in request or not to handle it with proper warning handler.
You can use #JsonAnySetter like this:
1- you define a BaseRequestDTO class:
public abstract class BaseRequestDTO {
#JsonAnySetter
public Map<String, Object> additionalData=new HashMap<>();
}
the field additionalData will hold all json field not in your DTO
2- make your dto extend it, like:
class JwtRequest extends BaseRequestDTO{
public String username;
public string password;
}
3- write an aspect to apply your "stern" strategy:
#Aspect
#Component
public class ControllerArgsValidator {
#Pointcut("within(#org.springframework.web.bind.annotation.RestController *)")
public void restController() {
}
#Pointcut("within(#org.springframework.stereotype.Controller *)")
public void controller() {
}
#Around("controller() || restController()")
public Object validate(ProceedingJoinPoint point) throws Throwable {
Object[] args = point.getArgs();
for (Object arg : args) {
if (arg instanceof BaseRequestDTO) {
if((BaseRequestDTO) arg).additionalData.isEmpty())
//do what ever;
}
}
return point.proceed();
}
I have c converter which works:
public class StringToLongConverter implements Converter<String, Long> {
#Override
public Long convert(String source) {
Long myDecodedValue = ...
return myDecodedValue;
}
}
In web configuration I have:
#Override
public void addFormatters (FormatterRegistry registry) {
registry.addConverter(new StringToLongConverter());
}
Everything is good but it works for all controllers and I need it to be executed only for some controllers.
//I need this controller to get myvalue from converter
#RequestMapping(value = "{myvalue}", method = RequestMethod.POST)
public ResponseEntity myvalue1(#PathVariable Long myvalue) {
return new ResponseEntity<>(HttpStatus.OK);
}
//I need this controller to get myvalue without converter
#RequestMapping(value = "{myvalue}", method = RequestMethod.POST)
public ResponseEntity myvalue2(#PathVariable Long myvalue) {
return new ResponseEntity<>(HttpStatus.OK);
}
Can we specify which converters or parameters should be used with custom converter and which should not?
Normally speaking, a registered Converter is bound to an input source and an output destination. In your case <String, Long>. The default Spring converter you used will apply the conversion on each matching source-destination pair.
To gain more control over when to conditionally apply the conversion, a ConditionalGenericConverter can be used. The interface contains 3 methods:
boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType), to determine whether the conversion should be applied
Set<ConvertiblePair> getConvertibleTypes() to return a set of source-destination pairs the conversion can be applied to
Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) the method in which the actual conversion takes places.
I've set up a small Spring project to play around with the use of a ConditionalGenericConverter:
RequiresConversion.java:
// RequiresConversion is a custom annotation solely used in this example
// to annotate an attribute as "convertable"
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
public #interface RequiresConversion {
}
SomeConverter.java:
#Component
public class SomeConverter implements ConditionalGenericConverter {
#Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
// Verify whether the annotation is present
return targetType.getAnnotation(RequiresConversion.class) != null;
}
#Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(String.class, Long.class));
}
#Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
// Conversion logic here
// In this example it strips "value" from the source string
String sourceValue = ((String) source).replace("value", "");
return Long.valueOf(sourceValue);
}
}
SomeController.java:
#RestController
public class SomeController {
// The path variable used will be converted, resulting in the "value"-prefix
// being stripped in SomeConverter
// Notice the custom '#RequiresConversion' annotation
#GetMapping(value = "/test/{myvalue}")
public ResponseEntity myvalue(#RequiresConversion #PathVariable Long myvalue) {
return new ResponseEntity<>(HttpStatus.OK);
}
// As the #RequiresConversion annotation is not present,
// the conversion is not applied to the #PathVariable
#GetMapping(value = "/test2/{myvalue}")
public ResponseEntity myvalue2(#PathVariable Long myvalue) {
return new ResponseEntity<>(HttpStatus.OK);
}
}
The conversion will occur on http://localhost:8080/test/value123 , resulting in a 123 Long value. However, as the custom annotation #RequiresConversion is not present on the second mapping, the conversion on http://localhost:8080/test2/value123 will be skipped.
You could also inverse the annotation by renaming it to SkipConversion and verifying whether the annotation is absent in the matches() method.
Hope this helps!
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.
I am trying to get the annotation values. This is my scenario as follows:
This is the annotation I declared.
#Retention(RetentionPolicy.RUNTIME)
public #interface PluginMessage {
String name();
String version();
}
This is the class the uses the annotation for some values
#PluginMessage(name = "RandomName", version = "1")
public class Response{
private Date Time;
}
This is a generic interface which will be used in the next code snippet.
public interface ResponseListener<E> {
void onReceive(E response);
}
I Invoke this by calling the following code:
addListener(new ResponseListener<Response>() {
#Override
public void onReceive(Response response) {
System.out.println();
}
});
This is the implementation of the addListener method:
public <E> void addListener(ResponseListener<E> responseListener) {
Annotation[] annotations = responseListener.getClass().getAnnotations();
}
The annotations are always empty, any idea of what I am doing wrong? I am trying to get the value of them here.
You may get annotations here:
.addListener(new ResponseListener<Response>() {
public void onReceive(Response response) {
final Annotation[] annotations = response.getClass().getAnnotations();
for (Annotation annotation : annotations) {
System.out.println("annotation.toString() = " + annotation.toString());
}
}
});
Your .addListener implementation makes no sense. Instead of getting annotations from ResponseListener(which has no annotations) instance, you have to add listener to listeners pool. Then you have to call listener.onReceive(...) for each listener when you will receive the response. I believe something like that should be implemented there.