In a Spring RabbitMQ project I am looking for a way to programmatically validate an object that has JSR303 annotations (like #NotNull, #Size, etc) while at the same time requires some custom validation logic. I would normally use a ConstraintValidator in combination with a custom Annotation, but the use of custom Annotations is not an option in this case.
I have the following (simplified) class, which is generated by Swagger and therefore cannot be edited:
#ApiModel(description="User")
public class User {
private String name;
#NotNull
#Size(min = 1, max = 6)
public String getName() {
return this.name;
}
...
}
The additional validation logic is encapsulated in a validator:
#Component
public class UserValidator implements org.springframework.validation.Validator {
#Override
public boolean supports(Class<?> aClass) {
return User.class.equals(aClass);
}
#Override
public void validate(Object o, Errors errors) {
User user = (User) o;
...
if(!valid) {
errors.reject("some rejection");
}
}
}
The service in which the validation occurs:
#Service
#RequiredArgsConstructor
public class SomeService {
private final javax.validation.Validator validator; // might as well be org.springframework.validation.Validator if that works better
public void someMethod(User user) {
if (!validator.validate(user).isEmpty()) {
// handle invalid user
}
...
}
}
However, the UserValidator is not being invoked. Is there some way to make Spring aware of the UserValidator? I have read some topics on using an InitBinder, however as this is not a web MVC project but a rabbitMQ project I'm not sure whether this can be used.
It is not clear from your description how this is relevant to Spring AMQP, but if you want to use a validator on the listener method level, you should configure it respectively:
#Configuration
#EnableRabbit
public class Config implements RabbitListenerConfigurer {
...
#Override
public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
registrar.setValidator(new MyValidator());
}
}
See docs for more info: https://docs.spring.io/spring-amqp/docs/current/reference/html/#rabbit-validation
Related
Iam new to Websockets. I have been trying to use SimpUserRegistry to find session object by Principal. I wrote a custom handshake handler to convert Anonymous users to authenticated users and Iam able to access the Principal name from Websocket session object.
The code for custom handshake handler is shown below
import java.security.Principal;
public class StompPrincipal implements Principal {
private String name;
public StompPrincipal(String name) {
this.name = name;
}
#Override
public String getName() {
return name;
}
}
Handler
class CustomHandshakeHandlerTwo extends DefaultHandshakeHandler {
// Custom class for storing principal
#Override
protected Principal determineUser(
ServerHttpRequest request,
WebSocketHandler wsHandler,
Map<String, Object> attributes
) {
// Generate principal with UUID as name
return new StompPrincipal(UUID.randomUUID().toString());
}
}
But as specified in many questions like this I'am not able to inject the SimpUserRegistry directly.
It throws error
Field simpUserRegistry required a bean of type 'org.springframework.messaging.simp.user.SimpUserRegistry' that could not be found.
The injection point has the following annotations:
- #org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean of type 'org.springframework.messaging.simp.user.SimpUserRegistry' in your configuration.
So I created a configuration class as shown below.
#Configuration
public class UsersConfig {
final private SimpUserRegistry userRegistry = new DefaultSimpUserRegistry();
#Bean
#Primary
public SimpUserRegistry userRegistry() {
return userRegistry;
}
}
Now I can autowire and use it but everytime I try to acess the SimpUserRegistry it is empty.
What could be the cause of this problem?
EDIT:
Showing websocket config
#Configuration
#EnableWebSocket
#Controller
#Slf4j
public class WebSocketConfig implements WebSocketConfigurer {
#Autowired
EventTextHandler2 handler;
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
log.info("Registering websocket handler SocketTextHandler");
registry.addHandler(handler, "/event").setHandshakeHandler(new CustomHandshakeHandlerTwo());
}
}
SimpUserRegistry is an "infrastructure bean" registered/provided by Spring WebSocket, you should not instantiate it directly.
Is your WebSocket Spring configuration correct?
Make sure your application is well configured (ie. your configuration class is being scanned).
SimpUserRegistry is imported by spring-messaging dependency: make sure your configuration class is annotated with #EnableWebSocketMessageBroker.
Official documentation: https://docs.spring.io/spring-framework/docs/5.3.6/reference/html/web.html#websocket-stomp-enable
To back the connected users in Redis, you may want to create a new SimpUserRegistry implementation:
public class RedisSimpUserRegistry implements SimpUserRegistry, SmartApplicationListener {
private final RedisTemplate redisTemplate;
public RedisSimpUserRegistry(RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
[...]
#Override
public void onApplicationEvent(ApplicationEvent event) {
// Maintain Redis collection on event type
// ie. SessionConnectedEvent / SessionDisconnectEvent
}
[...]
}
PS: The #Controller annotation on your config class is not necessary unless you have an endpoint defined in it.
Edit after new comments:
You can see the DefaultSimpUserRegistry implementation to get an idea of how to do it.
To intercept an application event, you have to implement the ApplicationListener interface (in this case SmartApplicationListener).
The supportsEventType method is important to define which event types you want to intercept:
#Override
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
return AbstractSubProtocolEvent.class.isAssignableFrom(eventType);
}
The AbstractSubProtocolEvent have multiple implementations. The most important ones are SessionConnectEvent, SessionDisconnectEvent.
Intercepting (see onApplicationEvent method) these event types will allow your implementation to maintain the desired state in your Redis cache. You could then store users (ids, etc.).
My question is related to bean validation. Apparently SpringBoot comes with two different validation mechanisms, one JSR-303 compilant (in javax.validation package) and the other provided by Spring framework (in org.springframework.validation package).
While it is easy to enforce JSR-303 validation via #Validated and #Valid annotations, I couldn't find the proper way for Spring's ones.
Take the following User bean.
#Entity
public class User {
#Column
#Id
private String id;
#Column(unique = true)
#Username
private String username;
// [...]
}
Where #Username constraint is defined as follows. Basically, it's just a composition of #Pattern and #Size costraints.
#Constraint(validatedBy = {})
#Documented
#Pattern(regexp = "[A-Za-z0-9_]+")
#Retention(RUNTIME)
#Size(max = 24, min = 3)
#Target(FIELD)
public #interface Username {
String message() default "";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
User beans are stored in a repository named UserRepository, defined as follows.
#Repository
public interface UserRepository extends CrudRepository<User, String>, JpaSpecificationExecutor<User> {
}
To access the repository I wrote a service, shown below.
#Transactional
#Service
public class UserService {
#Autowired
private UserRepository userRepository;
#Validated
public void create(#Valid User user) {
userRepository.save(user);
}
}
Until now, things are pretty neat and everything works. With just a couple of annotations I've achieved everything.
Now, I have this other validator (not JSR-303).
// [...]
import org.springframework.validation.Validator;
#Component
public class UniqueUsernameValidator implements Validator {
#Autowired
private UserRepository userRepository;
#Override
public boolean supports(Class<?> clazz) {
return User.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
User user = (User) target;
if (userRepository.count(where(usernameIsEqualTo(user.getUsername()))) > 0) {
errors.rejectValue("username", "username.exists");
}
}
}
Ideally, I would like to have it enforced via the same #Validated and #Valid annotations but until now I've been unlucky. I guess it is definitely possible given that the documentation of the class org.springframework.validation.Validator says
This interface is totally divorced from any infrastructure or context; that is to say it is not coupled to validating only objects in the web tier, the data-access tier, or the whatever-tier. As such it is amenable to being used in any layer of an application, and supports the encapsulation of validation logic as a first-class citizen in its own right.
#Valid is a JSR-303 bean validation annotation, and it won't call your UniqueUsernameValidator Spring Validator.
If you want to do that in your service, you need to invoke it manually:
public void create(#Valid User user) {
Errors errors = new BeanPropertyBindingResult(user, "user");
// Validator component has been previously injected into the service.
uniqueUsernameValidator.validate(user, errors);
if (errors.hasErrors()) {
throw new RuntimeException(errors.toString());
}
userRepository.save(user);
}
I've noticed that JSR-303 validation is completely ignored in Spring when a custom Validator bean annotated with #Component is declared. Interestingly enough said custom validator doesn't even have to filled in or used by any of the classes. The fact that its component scanned by Spring appears to be enough to make Spring skip JSR-303 validation during object binding altogether. Removing #Component from custom Validator and restarting web application enables JSR-303 validation as expected. Annotating custom validators with #Component has its uses eg to have Spring Autowire dependencies.
Consider simple example below:
/* Simple JSR-303 annotated User object */
public class User {
#NotNull #Size(min = 2, max = 5)
protected String username;
#Size(min = 2, max = 32)
protected String firstName;
#Size(min = 2, max = 32)
protected String lastName;
#NotNull #Past #DateTimeFormat(pattern="dd/MM/yyyy")
protected Date dateOfBirth;
#NotNull #Email
protected String email;
protected String phone;
//getters and setters
}
/* Controller example */
#RestController
public class UserController {
#PostMapping("/users/register")
public ResponseEntity postUser(#Valid #RequestBody User user, BindingResult result) {
if (result.hasErrors()) {
return new ResponseEntity(result.getAllErrors(), HttpStatus.INTERNAL_SERVER_ERROR);
}
return new ResponseEntity(user, HttpStatus.CREATED);
}
}
/* Custom validator (doesn't even have to be in use) */
#Component //commenting out #Component annotation enables JSR-303 again
public class SomeValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
//just an example
return false;
}
#Override
public void validate(Object target, Errors errors) {
//empty
}
}
I was scratching my head over this one and couldn't figure out why my JSR-303 objects weren't validated but I managed to narrow it down and replicate this with a Spring Boot project containing above classes.
Why is that? Am I missing something here or is this a Spring bug?
Edit
See demo Spring Boot project on GitHub
So there are a number of problems going on in your code. First, your custom validator doesn't support anything, so it's never going to be invoked. You probably realize that, so I'll leave it up to you to fix it.
Your real problem, though, is that by creating a validator bean like that, Spring will not create the defaultValidator bean (which is a LocalValidatorFactoryBean) nor will it create the methodValidationPostProcessor bean (which is needed to validate method parameters). This is a common problem people run into with Spring: once you do something to disturb the auto-configure process, you have to define things manually. The solution is simple: create a config class that defines these beans. Example:
#Configuration
public class ValidationConfig {
#Bean public LocalValidatorFactoryBean defaultValidator() {
return new LocalValidatorFactoryBean();
}
#Bean public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
}
I need to stop processing of Spring MVC annotations on interface, but bean for this interface should be created.
e.g. I have shared Api interface with MVC REST annotations, Controller implements this Api. In other project I create REST client based on interface (by processing annotations). But when I create client, Spring sees interface as return type and process annotations inside it. So, I need to stop annotations processing when I create REST client, but for controller annotations should work (now they work OK).
#RequestMapping("/resource1")
public interface Api {
#RequestMapping(method = RequestMethod.POST)
Resource1 getResource1();
}
#RestController
public class Controller implements Api {
#Override
public Resource1 getResource1() {
return null;
}
}
#Configuration
public class Config {
#Bean
public Api api() {
return RestClientFactory.createRestClientBasedOnAnnotations(Api.class);
}
}
I solved it by creating new annotation which is used to mark API interface and overriding boolean isHandler(Class<?> beanType) method of org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping. This method originally checks whether class (or any interface that class implements) is annotated with Controller or RequestMapping annotations. I added extra check that looks up for my BackEndApiInterface annotation and if it is found then return false. Here is the code:
#Retention(RetentionPolicy.RUNTIME)
public #interface BackEndApiInterface {
}
#BackEndApiInterface
#RequestMapping("/resource1")
public interface Api {
#RequestMapping(method = RequestMethod.POST)
Resource1 getResource1();
}
#RestController
public class Controller implements Api {
#Override
public Resource1 getResource1() {
return null;
}
}
#Configuration
public class Config {
#Bean
public Api api() {
return RestClientFactory.createRestClientBasedOnAnnotations(Api.class);
}
#Bean
public static RequestMappingHandlerMapping requestMappingHandlerMapping() {
return new RequestMappingHandlerMapping() {
#Override
protected boolean isHandler(Class<?> beanType) {
if (AnnotationUtils.findAnnotation(beanType, BackEndApiInterface.class) != null) {
return false;
}
return super.isHandler(beanType);
}
};
}
}
you could move the annotations to the implementation and just keep the interface as pure java.
I'm working on spring mvc application, where I should aplly validation based on Spring MVC validator. I first step for that I added annotation for class and setup controller and it works fine. And now I need to implement custom validator for perform complex logic, but i want to use existing annotation and just add additional checking.
My User class:
public class User
{
#NotEmpty
private String name;
#NotEmpty
private String login; // should be unique
}
My validator:
#Component
public class UserValidator implements Validator
{
#Autowired
private UserDAO userDAO;
#Override
public boolean supports(Class<?> clazz)
{
return User.class.equals(clazz) || UsersForm.class.equals(clazz);
}
#Override
public void validate(Object target, Errors errors)
{
/*
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "NotEmpty.user");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "login", "NotEmpty.user");
*/
User user = (User) target;
if (userDAO.getUserByLogin(user.getLogin()) != null) {
errors.rejectValue("login", "NonUniq.user");
}
}
}
My controller:
#Controller
public class UserController
{
#Autowired
private UserValidator validator;
#InitBinder
protected void initBinder(final WebDataBinder binder)
{
binder.setValidator(validator);
}
#RequestMapping(value = "/save")
public ModelAndView save(#Valid #ModelAttribute("user") final User user,
BindingResult result) throws Exception
{
if (result.hasErrors())
{
// handle error
} else
{
//save user
}
}
}
So, Is it possible to use custom validator and annotation together? And if yes how?
I know this is a kind of old question but, for googlers...
you should use addValidators instead of setValidator. Like following:
#InitBinder
protected void initBinder(final WebDataBinder binder) {
binder.addValidators(yourCustomValidator, anotherValidatorOfYours);
}
PS: addValidators accepts multiple parameters (ellipsis)
if you checkout the source of org.springframework.validation.DataBinder you will see:
public class DataBinder implements PropertyEditorRegistry, TypeConverter {
....
public void setValidator(Validator validator) {
assertValidators(validator);
this.validators.clear();
this.validators.add(validator);
}
public void addValidators(Validator... validators) {
assertValidators(validators);
this.validators.addAll(Arrays.asList(validators));
}
....
}
as you see setValidator clears existing (default) validator so #Valid annotation won't work as expected.
If I correctly understand your problem, as soon as you use you custom validator, default validation for #NotEmpty annotation no longer occurs. That is common when using spring : if you override a functionnality given by default, you have to call it explicitely.
You have to generate a LocalValidatorFactoryBean and inject it with your message source (if any). Then you inject that basic validator in you custom validator and delegate annotation validation to it.
Using java configuration it could look like :
#Configuration
public class ValidatorConfig {
#Autowired
private MessageSource messageSource;
#Bean
public Validator basicValidator() {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.setValidationMessageSource(messageSource);
return validator;
}
}
Then you modify UserValidator to use it :
#Component
public class UserValidator implements Validator
{
#Autowired
#Qualifier("basicValidator")
private Validator basicValidator;
#Autowired
private UserDAO userDAO;
// ...
#Override
public void validate(Object target, Errors errors)
{
basicValidator.validate(target, errors);
// eventually stop if any errors
// if (errors.hasErrors()) { return; }
User user = (User) target;
if (userDAO.getUserByLogin(user.getLogin()) != null) {
errors.rejectValue("login", "NonUniq.user");
}
}
}
Well for me you have to delete the
#InitBinder
protected void initBinder(final WebDataBinder binder)
{
binder.setValidator(validator);
}
Leave the
#Valid #ModelAttribute("user") final User user,
BindingResult result
And after in the function make
validator.validate(user,result)
This way you will use the validation basic with the #Valid and after you will put make the more complex validation.
Because with the initBinder you are setting the validation with your complex logic and putting a way the basic logic.
Maybe is wrong, i use always the #Valid without any validator.