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.
Related
I try to use the multipleOf-Property in my OpenApi spec but the generated java code doesn't contain any annotation or logic to validate the multipleOf. How could I use the multipleOf-Property to validate my JSON input? For the spec definition we use OpenApi 3.0.1
Here you can see the usage of multipleOf:
abc_field:
type: number
description: Description of ABC field
minimum: 0
maximum: 99999999999.99
multipleOf: 0.01
example: 200.57
Is there any solution to solve my validation problem? The validation api I use is javax.validation but there isn't any annotation for multipleOf.
The generated code for the abc_field looks like:
public BetraegeKennzahlen abcField(BigDecimal abcField) {
this.abcField = abcField;
return this;
}
#ApiModelProperty(
example = "200.57",
value = "Description of ABC field"
)
#Valid
#DecimalMin("0")
#DecimalMax("99999999999.99")
public BigDecimal abcField() {
return this.abcField;
}
public void setAbcField(BigDecimal abcField) {
this.abcField = abcField;
}
The multipleOf property is not supported by openapi-generator
https://github.com/OpenAPITools/openapi-generator/issues/2192
You can add a custom constraint validator for your fields
public class CustomValidator implements Validator {
#Override
public boolean supports(Class<?> aClass) {
return GeneratedClass.class.isAssignableFrom(aClass);
}
#Override
public void validate(Object o, Errors errors) {
GeneratedClass generatedClass = (GeneratedClass)o;
//validate
}
}
And add a binder for that validator
#InitBinder("generatedClass")
protected void initBinderForAvatarId(WebDataBinder binder) {
binder.addValidators(new CustomValidator());
}
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.
I have a question about getting custom annotation value which is value of another custom annotation. For example I have a #SqlInfo annotation interface which have two values which is also annotation interfaces.
SqlInfo.java
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface SqlInfo {
CodificationInfo codificationInfo();
DocumentInfo documentInfo();
}
#CodificationInfo and #DocumentInfo is also annotation interfaces. Each of it has his own different values.
CodificationInfo.java
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface CodificationInfo {
enum KEYS {
DOMAIN,
FILE,
TABLE,
CLASS
}
String domain() default "";
String fileName() default "";
String table() default "";
Class codificationClass();
}
While I am using only #CodificationInfo annotation for the class. I am getting values from it by using this method:
Annotation values getter method
public Object getClassAnnotationValue(Class c, String key) {
Annotation annotation = c.getAnnotation(CodificationInfo.class);
return getObjectByKey(annotation, key);
}
private Object getObjectByKey(Annotation annotation, String key) {
if (annotation instanceof CodificationInfo) {
if (key.equalsIgnoreCase(CodificationInfo.KEYS.TABLE.toString())) {
return ((CodificationInfo) annotation).table();
} else if (key.equalsIgnoreCase(CodificationInfo.KEYS.CLASS.toString())) {
return ((CodificationInfo) annotation).codificationClass();
} else if (key.equalsIgnoreCase(CodificationInfo.KEYS.DOMAIN.toString())) {
return ((CodificationInfo) annotation).domain();
} else if (key.equalsIgnoreCase(CodificationInfo.KEYS.FILE.toString())) {
return ((CodificationInfo) annotation).fileName();
}
}
return null;
}
I want to know how to get #CodificationInfo values while I am using #SqlInfo annotation for the class? It means - how to get values from sub-annotation?
P.S.: I know that I can use both annotations separately for the class. But I want to know the any way how to get values from sub-annotation. For example hibernate use it for #AuditOverrides annotation.
If you have a type declared like:
#SqlInfo(codificationInfo = #CodificationInfo(codificationClass = AClass.class)
public class MyType { }
you can reflectively get the inner annotation values with:
final SqlInfo sqlInfoAnnotation = (SqlInfo) c.getAnnotation(SqlInfo.class);
if (sqlInfoAnnotation == null) return;
final CodificationInfo codInfoAnnotation = sqlInfoAnnotation.codificationInfo();
final Class<?> codClass = codInfoAnnotation.codificationClass();
Note: you can avoid having to cast the annotation by not using raw types (prefer Class<?> over Class).
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
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.