I have recently started working on custom Annotations. I created a sample spring boot project where it works fine, but When I try to integrate with my project code, the validation class is not getting invoked.
ValidateField.java
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
#Documented
#Target(value = { ElementType.METHOD, ElementType.FIELD, ElementType.TYPE })
#Constraint(validatedBy = { RequestDataValidator.class })
#Retention(value = RetentionPolicy.RUNTIME)
public #interface ValidateField {
String message() default "invalid or value not present";
int revision() default 1;
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
RequestDataValidator.java
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class RequestDataValidator implements ConstraintValidator<ValidateField, String>{
#Override
public void initialize(ValidateField constraintAnnotation) {
System.out.println("in Initialize method");
}
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
System.out.println("in Initialize method");
return (value != null && !value.isEmpty());
}
}
The controller class is as follows.
Please note that the #Valid annotation is used with #RequestBody
import javax.validation.Valid;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.springframework.web.bind.annotation.RequestBody;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
#Api
#Produces({MediaType.APPLICATION_JSON})
#Path("/service")
public interface UIController {
#POST
#Path("/getRules")
#Produces({MediaType.APPLICATION_JSON})
#ApiOperation(
value = "Respond Hello <name>!",
response = Response.class
)
#ApiResponses(
value = {
#ApiResponse(code = 404, message = "Service not available"),
#ApiResponse(code = 500, message = "Unexpected Runtime error")
})
public Response getRules(#Valid#RequestBody SampleRequest discountPromoDetailsRequest);
}
I have checked if the annotation gets applied to the field. Turns out it does. I checked it by using
field.getDeclaredAnnotations()
SampleRequest.java
public class SampleRequest {
private String name;
#ValidateField
private String lastName;
private Long phNum;
private String address;
.
.
//getters and setters of the same.
}
I am now not sure what can be the cause for the RequestDataValidator not getting called.
I am working on this from last 3 days and I still don't have it working in the project.
Any Help is highly appreciated.
Related
To capture logTime of each controller call, I added #LogTime annotation for package level but it is not working. I am not able to figure out why it is working with ElementType.TYPE at class level annotation but not with ElementType.PACKAGE at package level. Could you please help?
package com.test.aop;
import io.micronaut.aop.Around;
import io.micronaut.context.annotation.Type;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.PACKAGE})
#Around
#Type(LogTimeInterceptor.class)
public #interface LogTime {
}
LogTimeInterceptor
package com.test.aop;
import io.micronaut.aop.MethodInterceptor;
import io.micronaut.aop.MethodInvocationContext;
import javax.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StopWatch;
#Singleton
public class LogTimeInterceptor implements MethodInterceptor<Object, Object> {
private static final Logger LOGGER = LoggerFactory.getLogger(LogTimeInterceptor.class);
#Override
public Object intercept(MethodInvocationContext<Object, Object> context) {
var timer = new StopWatch(context.getDeclaringType().getSimpleName() + "." + context.getName());
try {
timer.start();
return context.proceed();
} finally {
timer.stop();
LOGGER.info("StopWatch Task name:{} running time:{} sec", timer.getId(), timer.getTotalTimeSeconds());
}
}
}
package-info.java
#LogTime
package com.test.controllers;
import com.test.aop.LogTime;
I am trying to create an annotation and pass a variable as parameter to the impl method. Below is my code to create an annotation.
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
#Documented
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = Loggable1Impl.class)
public #interface Loggable1 {
String message() default "Invalid userId";
}
Below is my impl class
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class Loggable1Impl implements ConstraintValidator<Loggable, String> {
public void initialize(Loggable contactNumber) {
}
public boolean isValid(String generatedUserId, ConstraintValidatorContext cxt) {
System.out.println("generatedUserId in Loggable1 :: " + generatedUserId);
return true;
}
}
I am trying to use it in a spring-boot application
#CrossOrigin(origins = "*", allowedHeaders = "*")
#PostMapping("/api1/{user}")
public Topic regiterUser(#PathVariable String user ) {
String generatedUserId = user + "1";
return generatedUserId ;
}
I want to pass generatedUserId to my annotaion Loggable1
Please assist
I'm trying to Log unique request id for each http request using MDC to my resource for debugging and trace a particular request. For same I've created one custom annotation #TagRequestID. Below is the for logging request id. But what I'm not able to achieve the request is not going via DynamicFilter implementation what I think there should be some method or way in VertxResteasyDeployment class which should help to resolve this.
Basically the request is not going via filter even I tried with setProviders method of VertxResteasyDeployment class. Can someone please guide what I'm missing here ? I believe there should be some config which request to pass request via DynamicFeature implementation where we inject RequestIdConfig bean. (Assume I've RequestIdConfig bean created).
#TagRequestID code :
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface TagRequestID {
}
custom class RequestIdConfig :
import lombok.Data;
import javax.validation.constraints.NotNull;
#Data
public class RequestIdConfig {
#NotNull
private String resourcePackage;
#NotNull
private String requestIdHeaderKey;
#NotNull
private Boolean requestIdMandatoryFlag;
}
custom class RequestIdFeature :
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import javax.ws.rs.ConstrainedTo;
import javax.ws.rs.RuntimeType;
import javax.ws.rs.container.DynamicFeature;
import javax.ws.rs.container.ResourceInfo;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.FeatureContext;
import javax.ws.rs.ext.Provider;
#Slf4j
#AllArgsConstructor
#Provider
public class RequestIdFeature
implements DynamicFeature {
RequestIdConfig requestIdConfig;
#Override
public void configure(ResourceInfo resourceInfo, FeatureContext featureContext) {
log.info("testing now===");
final Class<?> resourceClass = resourceInfo.getResourceClass();
//Check if the current resource is to validated
if (resourceClass.getPackage().getName().startsWith(requestIdConfig.getResourcePackage())) {
//Check if the Validation annotation is present
if (resourceInfo.getResourceMethod().getAnnotation(TagRequestID.class) != null) {
log.info(resourceInfo.getResourceMethod() + " registered for clientID validation");
featureContext.register(new RequestIdFilter(requestIdConfig, resourceInfo));
}
}
}
}
custom class for filter :
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jboss.logging.MDC;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.ResourceInfo;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.Provider;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.UUID;
#Data
#AllArgsConstructor
#Slf4j
#Provider
public class RequestIdFilter
implements ContainerRequestFilter {
RequestIdConfig requestIdConfig;
ResourceInfo resourceInfo;
public static final String REQUEST_ID = "request-Id";
#Override
public void filter(ContainerRequestContext containerRequestContext) throws IOException {
log.info("ClientIdValidation filter method invoked");
Method resourceMethod = resourceInfo.getResourceMethod();
// Validate Clients
validatePermissions(containerRequestContext);
}
private void validatePermissions(final ContainerRequestContext containerRequestContext) {
String requestId = containerRequestContext.getHeaderString(requestIdConfig.getRequestIdHeaderKey());
//Make sure the Header key is present if mandatory flag is true
if (requestIdConfig.getRequestIdMandatoryFlag() && StringUtils.isAnyEmpty(requestId)) {
throw new WebApplicationException(requestIdConfig.getRequestIdHeaderKey() + " can't be null", Response.Status.UNAUTHORIZED);
}
//If no request ID present, generate a UUID
if (StringUtils.isAnyEmpty(requestId)) {
requestId = UUID.randomUUID()
.toString();
}
containerRequestContext.setProperty(REQUEST_ID, requestId);
MDC.put(REQUEST_ID, requestId);
}
}
Resource or Controller code :
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.container.AsyncResponse;
import javax.ws.rs.container.Suspended;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import io.vertx.core.Vertx;
import io.vertx.core.WorkerExecutor;
public class Processor {
#POST
#TagRequestID
#Path("/update_record")
#Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_JSON })
public void updateEvent(String data) throws Exception{
//do something here
}
Server code from where we run this:
import mypackage.Processor;
import io.vertx.core.AbstractVerticle;
import org.jboss.resteasy.plugins.server.vertx.VertxRequestHandler;
import org.jboss.resteasy.plugins.server.vertx.VertxResteasyDeployment;
import org.springframework.context.ApplicationContext;
public class VertxServer extends AbstractVerticle {
VertxServer(final ApplicationContext context) {
}
#Override
public void start() throws Exception {
VertxResteasyDeployment deployment = new VertxResteasyDeployment();
deployment.start();
deployment.getRegistry().addPerInstanceResource(Processors.class);
vertx.createHttpServer()
.requestHandler(new VertxRequestHandler(vertx, deployment))
.listen(8080);
}
}
Once server is up and running then just hit two request simultaneously on the above controller . i.e :
curl -X POST \ http://localhost:8080/v1/update_record \ -H
'Cache-Control: no-cache' \ -H 'Content-Type: application/json' \
-H 'Postman-Token: c9494189-4ac9-9f6c-44f6-216186c74431' \ -d '{"id":"123"}'
In VertxServer.java register your dynamicFeature instance in providerFactory before you register your resources. This dynamicFeatures and filters registered in providerFactory will be used while registering resources.
Your code will go like this:
VertxResteasyDeployment deployment = new VertxResteasyDeployment();
deployment.start();
deployment.getProviderFactory().register(new RequestIdFeature(getRequiredBean());
deployment.getRegistry().addPerInstanceResource(Processors.class);
vertx.createHttpServer()
.requestHandler(new VertxRequestHandler(vertx, deployment))
.listen(8080);
VertxServer.java
deployment.getProviderFactory().register(RequestIdFilter.class)
I wrote a custom validation to validate an id (Which is a path Param) as UUID in my #GET method of #RestController but this validation doesn't seem to be working. Even during the debug the control doesn't go to custom validation.
#RestController
#RequestMapping("/rateplan")
#Validated
public class RatePlanServiceController {
#RequestMapping(value = "/{ratePlanId}", method = RequestMethod.GET)
#ResponseStatus(HttpStatus.OK)
public void get(#UUID #PathVariable("ratePlanId") String ratePlanId) {
loggerFactory.warn("Get with Rate plan id {}", ratePlanId);
loggerFactory.info("Get with Rate plan id {}", ratePlanId);
loggerFactory.error("Get with Rate plan id {}", ratePlanId);
loggerFactory.debug("Get with Rate plan id {}", ratePlanId);
// return iRatePlanService.getRatePlan(ratePlanId);
}
}
I wrote the custom annotation for validation of UUID as follow.
import org.springframework.stereotype.Component;
import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.regex.Pattern;
#Target(ElementType.PARAMETER)
#Constraint(validatedBy = {UUID.IdValidator.class})
#Retention(RetentionPolicy.RUNTIME)
public #interface UUID {
String message() default "{invalid.uuid}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
#Component
public class IdValidator implements ConstraintValidator<UUID, String> {
private static final Pattern id_PATTERN =
Pattern.compile("^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$");
public boolean isValid(String value, ConstraintValidatorContext context) {
if (!(id_PATTERN.matcher(value).matches())) {
return false;
}
return true;
}
public void initialize(UUID parameters) {
}
}
}
Can anyone let me know why is it not working. Even if I provide a garbage ratePlanId like '345%#7^34' it able to go inside GET method.
Solved this by adding a bean in Application configuration file. To validate a path Param in Spring you need to add this bean in your configuration class.
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
How to create a validator restrictions for more fields through annotation in spring 4.* for example
#UniqueValidator
#Entity
#Table(name = "persons")
#UniqueValidator(message="Peson already exist",columns={"name","lastName"})
public class {
#Column
private String name;
#Column
private String lastName;
}
roughly speaking..
select ... from persons where name='qwerty' and lastName='asdfgh'
Here's one way to do it in Spring MVC and JSR 303 validation:
Create a constraint annotation:
package com.awgtek.model.validation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
/**
* The target field should be unique in the data store.
*/
#Documented
#Constraint(validatedBy = UniqueNameValidator.class)
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface UniqueName {
/**
* #return the error message template
*/
String message() default "{com.awgtek.model.validation.UniqueName.message}";
/**
* #return the groups the constraint belongs to
*/
Class<?>[] groups() default {};
/**
* #return the payload associated to the constraint
*/
Class<? extends Payload>[] payload() default {};
/**
* #return a class an instance of which will be used to check the existence of a name.
*/
Class<? extends NameExistenceChecker> nameExistenceChecker();
}
Apply this annotation at the class level to the model class:
package com.awgtek.model;
import com.awgtek.model.validation.MyNameExistenceChecker;
import com.awgtek.model.validation.UniqueName;
import com.awgtek.model.validation.UniqueNameConstraint;
#UniqueName(groups = UniqueNameConstraint.class,
nameExistenceChecker = MyNameExistenceChecker.class)
public class ConfigurationAttribute {
private String name;
private String lastName;
// getters and setters omitted
}
The UniqueNameValidator:
package com.awgtek.model.validation;
import com.awgtek.service.MyService;
import javax.inject.Inject;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class UniqueNameValidator implements ConstraintValidator<UniqueName, Object> {
#Inject
private NameExistenceCheckerFactory nameExistenceCheckerFactory;
private NameExistenceChecker nameExistenceChecker;
#Override
public void initialize(UniqueName constraintAnnotation) {
nameExistenceChecker = nameExistenceCheckerFactory.get(constraintAnnotation.nameExistenceChecker());
}
#Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
return !nameExistenceChecker.nameExists(value);
}
}
The UniqueNameValidator then depends on a couple of classes:
package com.awgtek.model.validation;
import javax.inject.Inject;
import org.springframework.stereotype.Component;
#Component
public class NameExistenceCheckerFactory {
#Inject
private NamespaceNameExistenceChecker namespaceNameExistenceChecker;
#Inject
private MyNameExistenceChecker myNameExistenceChecker;
public NameExistenceChecker get(Class<? extends NameExistenceChecker> clazz) {
if (clazz.equals(MyNameExistenceChecker.class)) {
return myNameExistenceChecker;
}
throw new IllegalStateException("Unknown NameExistenceChecker");
}
}
package com.awgtek.model.validation;
public interface NameExistenceChecker {
boolean nameExists(Object objectWithName);
}
The implementation class that actually looks up whether the item exists in the database (via a service):
package com.awgtek.model.validation;
import com.awgtek.model.MyModelClass;
import com.awgtek.service.MyService;
import javax.inject.Inject;
import org.springframework.stereotype.Component;
#Component
public class AttributeNameExistenceChecker implements NameExistenceChecker {
#Inject
private MyService myService;
#Override
public boolean nameExists(Object objectWithName) {
MyModelClass myObject = (MyModelClass) objectWithName;
return myService.itemAlreadyExists(myObject.getName(), myObject.getLastName());
}
}