Reflection API + Annotations: how to send class-inheritor as parameter? - java

I have annotation & want to send class name there as parameter:
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.METHOD})
#Documented
public #interface PostApiRequest {
Class<?> value();
}
I attach annotation to method of parent class:
#PostApiRequest(value = ...)
#Override
public ResponseEntity<D> save(#RequestBody D dto) {
Application don't know, which inheritor shall call this method, than I want to send inheritor there to work with its properties later. I should see any like this:
#PostApiRequest(value = this.class) //send inheritor
#Override
public ResponseEntity<D> save(#RequestBody D dto) {
but it not works.
Please, give advice, how to do it?

Related

Custom annotation isn't validating method parameter

I created a custom annotation
#Documented
#Constraint(validatedBy = CheckGranularityValidator.class)
#Target( { ElementType.PARAMETER} )
#Retention(RetentionPolicy.RUNTIME)
public #interface CheckGranularity {
String message() default "Duration has to be a multiple of granularity";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
With a validator like so
public class CheckGranularityValidator implements ConstraintValidator<CheckGranularity, AssetCostsRequest> {
#Override
public void initialize(final CheckGranularity constraintAnnotation) {
}
#Override
public boolean isValid(final AssetCostsRequest value, final ConstraintValidatorContext context) {
return value.getRange().getDuration() % value.getGranularity() == 0;
}
}
I tried using it in my RestController
#RestController
public class CalcApiController extends CalcApi {
#Override
public ResponseEntity<String> calcProfitability(#Valid #CheckGranularity #RequestBody final AssetCostsRequest assetCostsRequest) {
return ResponseEntity.ok("Works");
}
I tried using this annotation by writing a test:
#Test
public void calcTest() {
final AssetCostsRequest request = new AssetCostsRequest()
.setRange(new TimeRange(100L, 200L))
.setGranularity(26L);
given()
.contentType(ContentType.JSON)
.body(request)
.when()
.post("/calc")
.then()
.statusCode(HttpStatus.SC_BAD_REQUEST);
}
Relevant part of AssetCostsRequest:
public class AssetCostsRequest {
#JsonProperty
#NotNull
private TimeRange range;
#JsonProperty
#NotNull
private Long granularity = 30L;
...getters & setters
}
Test method returns with 200. When I try to set a breakpoint in isValid method, it isn't hit when I run the test. I tried changing order of annotations, getting rid of #Valid, changing #Target in CheckGranularity class, nothing helped. I'm using RestAssured for testing.
How do I make it, so my annotation is properly validating a parameter?
Change CheckGranularity's target to ElementType.TYPE and add #CheckGranularity directly on AssetCostsRequest. Also remove #CheckGranularity from endpoint definition.
How it works. By adding #Valid on endpoint's parameter you tell spring to validate it. Adding validation like #CheckGranularity won't work on the same level as Valid. It has to be added somewhere inside parameters class.

Is there a declarative way to validate request parameters depending from one of request fields?

I'm looking for something like JSR-303 Validation Groups (bean validation, when you mark method argument with #Validated(GroupName.class) in controller and specify group in request class fields where needed), but it should decide how to validate at runtime depending on one of request fields.
For example, if we have controller class like this
#Controller
public class MyController {
#Autowired
private MyService myService;
#ResponseBody
#RequestMapping(value = "/path", method = RequestMethod.PUT)
public ResponseVo storeDetail(/*maybe some annotation here*/ DetailRequestVo requestVo) {
return myService.storeDetail(requestVo);
}
class DetailRequestVo {
String type;
Long weight;
Long radius;
}
}
And we want validation depending on type field value: if type = "wheel" then radius and weight fields should be presented, if type = "engine" then only weight field should be presented.
Does Spring (as of 3.2.17) provide API to implement these rules in more declarative approach? org.springframework.validation.Validator looks like not an option here, because its method boolean supports(Class<?> clazz) decides based on class info, not instance info.
Thanks in advance
Not sure about Spring but you can do that with plain JSR-303 using a custom Validator for the class itself... like
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.TYPE })
#Constraint(validatedBy = TypeValidator.class)
#Documented
public #interface ValidType {
...
}
public class TypeValidator implements ConstraintValidator<ValidType , Object> {
public boolean isValid(final Object target, final ConstraintValidatorContext context) {
DetailRequestVo request = (DetailRequestVo) target;
// do your checks here
}
and used like
#ValidType
class DetailRequestVo {
String type;
Long weight;
Long radius;
}
Since the custom Validator has access to the whole DetailRequestVo-Object you can do your check of field A depending on field B etc.

Limiting the values of Query Params JAX-RS with CXF as implementation

I have a use case where I need to limit the values that can be passed as the query param.
#Path("/foo")
public interface Foo {
#GET
#Path("/details/id/{id}")
void getFooDetails(#PathParam("id") String id, #QueryParam("sort") String sortDirection);
}
public class FooImpl {
public void getFooDetails(String id, String sortDir) {
//Implementation
}
}
In the above example, I want to restrict the value of query param sort that can be passed via the API to ASC, DESC.
Is there any existing CXF annotation which I can use to restrict the values on a parameter? I haven't found any and so I tried the following solution.
My Approach:
#Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
#Inherited
public #interface ValueSet {
String[] allowedValues();
}
The modified interface looks like this.
#Path("/foo")
public interface Foo {
#GET
#PathParam("/details/id/{id}")
void getFooDetails(#PathParam("id") String id, #QueryParam("sort") #ValueSet(allowedValues = {"ASC", "DESC"}) String sortDirection);
}
I wrote a CXF Interceptor which intercepts the API invocation. I used reflection to get a handle on FooImpl.getFooDetails params. But the problem I faced is that the interceptor looks at FooImpl.getFooDetails method and doesn't find the annotations #QueryParam on the method params since #QueryParam is on the base method and the annotation is not inherited.
Interceptor implementation:
#Provider
public class ParamValidationInterceptor extends AbstractPhaseInterceptor<Message> {
public ParamValidationInterceptor() {
super(Phase.PRE_INVOKE);
super.addBefore(someInterceptor);
}
#Override
public void handleMessage(Message message) throws Fault {
UriInfo uriInfo = new UriInfoImpl(message);
MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
Method methodToInvoke = (Method) message.get("org.apache.cxf.resource.method");
Parameter[] parameters = methodToInvoke.getParameters();
for (Parameter parameter : parameters) {
if (parameter.isAnnotationPresent(ValueSet.class)) {
ValueSet valueSet = parameter.getAnnotation(ValueSet.class);
QueryParam queryParam = parameter.getAnnotation(QueryParam.class);
Object invokedVal = queryParams.get(queryParam.value());
String[] allowedValues = valueSet.allowedValues();
if (!Arrays.asList(allowedValues).contains(invokedVal)) {
throw new CustomException();
}
}
}
}
}
Can anyone suggest a way forward? It would be great if anyone can suggest an alternative approach.
P.S: I am using CXF as an implementation for JAX-RS and spring is used as a container.
Update:
Like #Cássio Mazzochi Molin and #Andy McCright suggested, I will go with #Pattern annotation. But I am curious to know why the JAX-RS annotations are not inherited from the interface although the spec says they will be inherited.
Annotation inheritance
According to the section §3.6 Annotation Inheritance of the JAX-RS specification, it is recommended to always repeat annotations instead of relying on annotation inheritance.
Refer to this answer for the complete quote.
#QueryParam can be applied to different targets
Bear in mind that the #QueryParam annotation can be applied to:
Resource method parameters
Resource class fields
Resource class bean properties
Hence a manual validation can be tricky.
Use Bean Validation
For validation purposes, you should consider Bean Validation. Consider a #Pattern annotation with the allowed values:
#Pattern(regexp = "ASC|DESC")
And just annotate your resource method parameter:
#GET
#Path("foo")
public Response getFoos(#QueryParam("sort")
#Pattern(regexp = "ASC|DESC") String sortDirection) {
...
}
If you prefer case insensitive values, use:
#Pattern(regexp = "ASC|DESC", flags = Pattern.Flag.CASE_INSENSITIVE)
If the given value is invalid, a ConstraintViolationException will be thrown. To handle such exception and return a customized response, you can use an ExceptionMapper:
#Provider
public class ConstraintViolationExceptionMapper
implements ExceptionMapper<ConstraintViolationException> {
#Override
public Response toResponse(ConstraintViolationException exception) {
...
}
}
Perhaps it is just a typo, but CXF may not be recognizing the getFooDetails method (on the interface) because it is annotated with #PathParam instead of #Path.
Instead of using your ValueSet approach, I used BeanValidation, but the following code works for me.
Foo.java
#Path("/foo")
public interface Foo {
#GET
#Path("/details/id/{id}")
Response getFooDetails(
#PathParam("id") #Pattern(regexp="[0-9]*") String id,
#QueryParam("sort") #Pattern(regexp = "ASC|DESC") String sortDirection);
}
FooImpl.java
public class FooImpl implements Foo {
#Override
public Response getFooDetails(String id, String sortDirection) {
Integer idInt = Integer.parseInt(id);
if ("ASC".equals(sortDirection) || sortDirection == null) {
...
} else if ("DESC".equals(sortDirection)) {
...
}
return ...;
}
I've got this working on WebSphere Liberty 17.0.0.2 which is based on CXF 3.1.11.
Hope this helps,
Andy

Spring aliasFor for Annotations with Target(PARAMETER)

I am trying to use the Meta-annotation of spring using the aliasFor annotation to create a custom annotation for the springs RequestParam
Simply 'extend/replace'
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface RequestParam {
#AliasFor("name")
String value() default "";
----
}
with my annotation
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
#Inherited
public #interface QueryParam {
#AliasFor(annotation = RequestParam.class, attribute = "name")
String name() default "";
#AliasFor(annotation = RequestParam.class, attribute = "required")
boolean required() default false;
#AliasFor(annotation = RequestParam.class, attribute = "defaultValue")
String defaultValue() default ValueConstants.DEFAULT_NONE;
}
This way it throws the Exception
org.springframework.core.annotation.AnnotationConfigurationException: #AliasFor declaration on attribute [name] in annotation [package.QueryParam] declares an alias for attribute [name] in meta-annotation [org.springframework.web.bind.annotation.RequestParam] which is not meta-present.
Problem is that without the RequestParam annotated on the QueryParam this doesn't work. And it is not possible to put the RequestParam as it PARAMETER targeted.
#RequestParam <--This is not possible.
public #interface QueryParam
So is there another way to achieve this ?
Basically what you want to achieve is not possible now, at least for the Spring v 4.3.3 There are main two problems, the first one is the fact that annotations like #RequestParam are declared with #Target(ElementType.PARAMETER) which make it impossible to be used as part of meta annotations. Furthermore, Spring MVC looks up annotations on method parameters using org.springframework.core.MethodParameter.getParameterAnnotations() which does not support meta-annotations or composed annotations. But if you really need some customizations there you can use HandlerMethodArgumentResolver instead of meta annotations.
So you code will look something like
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface QueryParam {
String name() default "";
boolean required() default false;
String defaultValue() default ValueConstants.DEFAULT_NONE;
}
Then using the HandlerMethodArgumentResolver add the custom logic which you need.
public class QueryParamResolver implements HandlerMethodArgumentResolver {
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterAnnotation(QueryParam.class) != null;
}
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
QueryParam attr = parameter.getParameterAnnotation(QueryParam.class);
// here you can use any logic which you need
return webRequest.getParameter(attr.value());
}
}
Then we need to register our HandlerMethodArgumentResolver
#Configuration
#EnableWebMvc
public class Config extends WebMvcConfigurerAdapter {
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new QueryParamResolver());
}
}
And last lets use our custom annotation
#GetMapping("/test")
public String test(#QueryParam("foo") String foo){
// something here
}
i had same problem and I entered the wrong package for GetAmpping. I must import «import org.springframework.web.bind.annotation.GetMapping» .

Custom String Method Validation JSR303 SpringMVC

I have a REST-Api where I want to validate the parameters on method Level. With #Valid and a custom object where validation annotations are inside, it works fine, but it fails, if I use a String and write the annotations directly into the method signiture.
My validator
#Documented
#Constraint(validatedBy = {})
#Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
#Pattern(regexp = "^[A-Za-z]{5}[0-9]{3,128}$")
public #interface CustomValidator{
public abstract String message() default "Custom Message"
public abstract Class<?>[] groups() default {};
public abstract Class<?>[] payload() default {};
}
My controller class
#RestController
public class MyController {
...
}
What works but is not so nice Code Style
#Data
public class FoobarForm{
#NotNull
#CustomValidator
String foobar;
}
#RequestMapping(value = "/foo/{bar}", method = RequestMethod.GET)
public Bank checkFoobar(#Valid #CustomValidator #PathVariable String foobar) {
What I want
#RequestMapping(value = "/foo/{bar}", method = RequestMethod.GET)
public Bank checkIban(#Validated #NotNull #CustomValidator #PathVariable String foobar) {
As you can see, I want to validate a String parameter on method Level with a custom Validator. But that does not work. If I put an invalid argument in the method, it passes and the validation is never performed.
Of course I can check the Regex in another way since it is a path variable, but I have the same scenario at other Methods and want to have a general Solution for this problem.
Info:
I currently use Spring Version: 4.0.7

Categories

Resources