I have a spring 3 controller with a validator for one of the methods. It insists on validating every object on the model. Would anyone be able to explain to me why it does this or if I'm doing something wrong?
According to the docs, 5.7.4.3 Configuring a JSR-303 Validator for use by Spring MVC (http://static.springsource.org/spring/docs/3.0.0.RC3/spring-framework-reference/html/ch05s07.html)
With JSR-303, a single javax.validation.Validator instance typically validates all model objects that declare validation constraints. To configure a JSR-303-backed Validator with Spring MVC, simply add a JSR-303 Provider, such as Hibernate Validator, to your classpath. Spring MVC will detect it and automatically enable JSR-303 support across all Controllers.
Example:
#Controller
public class WhaleController {
#Autowired
private Validator myValidator;
#Autowired
private WhaleService whaleService;
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.setValidator(this.myValidator);
}
#RequestMapping(value="/save-the-whales")
#Transactional
public void saveTheWhales(#Valid WhaleFormData formData, BindingResult errors, Model model) {
if (!errors.hasFieldErrors()) {
Whale whale = new Whale();
whale.setBreed( formData.getBreed() );
this.whaleService.saveWhale( whale );
model.addAttribute("whale", whale);
}
model.addAttribute("errors", errors.getFieldErrors());
}
}
When run it will complain that Whale is an invalid target for myValidator (which is set to validate WhaleFormData, and does so fine). Whale is a POJO with no validation constraints, annotation and no config anywhere. Through trial and error I've found that ANY object placed on the model will attempt to be validated and fail if the validator is not setup to handle it. Primitives are just fine.
Can anyone tell me why this is, point me to the appropriate documentation and/or tell me the best way to put something on the model without having it validated?
In the case above I would like to place "whale" on the model as it will now have a unique whaleId() that it received from my persistence layer.
Thanks!
I guess this behaviour is not covered in the documentation well.
The problem is caused by the following:
By default, #InitBinder-annotated method is called for each non-primitive model attribute, both incoming and outcoming (the purpose of calling it for outcoming attibutes is to allow you to register custom PropertyEditors, which are used by form tags when rendering a form).
DataBinder.setValidator() contains a defensive check that call Validator.supports() and throws an exception if false is returned. So, there is no attempt to perform a validation, just an early check.
The solution is to restrict the scope of #InitBinder to particular attribute:
#InitBinder("whaleFormData")
protected void initBinder(WebDataBinder binder) { ... }
Related
I am building a REST API using spring and hibernate. I have come across the issue where I want to create a user and want to know the best practice on how to validate that the user can be created.
My controller has the #Valid annotation on the User object that gets passed into the method, and this checks for valid structure, however there is no #Unique property that gets picked up by #Valid.
I am using the #Column(unique = true) but this throws an error at the persistence level and I feel like that is quite low level and makes it difficult to throw a custom UsernameAlreadyExistsException().
My question here is what is the best practice in terms of preforming this type of validation. I thought about creating a custom annotation but it seems quite messy especially because as the project grows I would need multiple validators for different fields and it also seems to be closley related to tying the service layer to the annotation which seems messy
In my opinion, using custom annotation is the best approach to do stuff like this, you can inject some bean in ConstraintValidator and perform validation. However you can try one of the below unusual approaches, maybe it will fit your requirements.
Spring AOP
Spring Handler Interceptor
JPA Event Listeners
It's just my opinion about this, in most cases I think I will create custom annotations to handle it.
A good practice would be to put validation both on the database (which we know nothing about, but it is not complicated really) and on the Spring's side.
As #kamil-w already said, a good is to write custom constraint validator, see here for an example.
Keep in mind that you can always pass parameters like to constraint annotation, and then access them in your ConstraintValidator, for example.:
#Entity
public class Member {
// ...
#UniqueField(fieldName = "login", context = Member.class)
private String login;
}
#Component
public class UniqueFieldValidator implements ConstraintValidator<UniqueField, Object> {
#PersistenceUnit
private EntityManagerFactory emf;
private Class validationContext;
private String fieldName;
#Override
public void initialize(UniqueField uniqueField) {
this.validationContext = uniqueField.validationContext();
this.fieldName = uniqueField.fieldName();
}
#Override
public boolean isValid(Object value, ConstraintValidatorContext cxt) {
// use value, this.validationContext, this.fieldName and entity manager to check uniqueness
}
}
I'm integrating spring web sockets capability into an existing spring mvc application, everything works as expected, except for enabling custom Spring Conversion on my inbound messages via #DestinationVariable.
Now I already have custom converters fully working for the http side, ex #RequestParam or #PathVariable but the same conversion on a websocket controller method throws a ConverterNotFoundException
Ex. I have a custom converter that converts String into Users
public class StringToUserConverter implements Converter<String,User>{
#Autowired UserDAO userDAO;
#Override
public User convert(String id) {
return userDAO.getUser(Integer.parseInt(id));
}
}
And this works exactly as expected in my http controllers, where I can pass in an id, and its automatically converted to the domain class
public String myControllerMethod(#RequestParam User user)
However the same does not work for my websocket controller for a parameter annotated with #DestinationVariable
#MessageMapping("/users/{user}")
#SendTo("/users/greetings")
public String send(#DestinationVariable User user) {
return "hello"
}
I stepped through the code and I can see that the DestinationVariableMethodArgumentResolver has the default conversion service which doesnt include my custom coverters
So how do I register custom converters, or a custom ConversionService so that it works for web sockets like it already does for http controllers
So now I'm running into the same issue with #Header annotation for JmsListener methods.
Same idea, #Header User user, throws the ConverterNotFound exception.
#JmsListener(destination = "testTopic")
public void testJmsListener(Message m, #Header User user)..
Here I was trying to pass the user id on the message header, and have spring convert it, but to no avail, only basic default conversions are supported, like strings or numbers.
I have stepped through quite a bit of initialization code in Spring here, and I can see that a new DefaultConversionService gets instantiated in many places, without any consideration for external configuration.
It looks like these modules are not nearly as mature as Spring MVC or the developers took a shortcut. But based on my inspection there is no way to easily configure custom converters.
Ok and here is the very hacky, not recommended, approach that did work. Its pretty convoluted and brittle, Im not going to use it, but just for illustration purposes here is what it took to register a custom converter for #Header jms mapping.
Here Im passing in a user_email on the jms message header, and wanted spring to automatically convert the id/email into the actual domain object User. I already had a working converter that does this well in mvc/http mode.
public class StringToUserConverter implements Converter<String,User>{
#Autowired
UserDAO userDAO;
public User convert(String email) {
return userDAO.getByEmail(email);
}
}
The above part is pretty standard and straight forward. Here comes the idiotically convoluted part. I stepped through the spring jms listener initialization code and found lowest spot where I could cut-in with my custom converter for jms #Header.
I created a service, that will #Autowire one of springs Jms beans, and then sets a custom conversion service on it using #PostConstruct. Even here some of the properties were private, so I had to use reflection to read them
#Service
public class JmsCustomeConverterSetter {
#Autowired
StringToUserConverter stringToUserConverter;
#Autowired
JmsListenerAnnotationBeanPostProcessor jmsPostProcessor;
#PostConstruct
public void attachCustomConverters() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
//create custom converter service that includes my custom converter
GenericConversionService converterService = new GenericConversionService();
converterService.addConverter(stringToUserConverter); //add custom converter, could add multiple here
DefaultConversionService.addDefaultConverters(converterService); //attach some default converters
//reflection to read the private field so i can use it later
Field field = jmsPostProcessor.getClass().getDeclaredField("beanFactory"); //NoSuchFieldException
field.setAccessible(true);
BeanFactory beanFactory = (BeanFactory) field.get(jmsPostProcessor); //IllegalAccessException
DefaultMessageHandlerMethodFactory f = new DefaultMessageHandlerMethodFactory();
f.setConversionService(converterService);
f.setBeanFactory(beanFactory); //set bean factory read using reflection
f.afterPropertiesSet();
jmsPostProcessor.setMessageHandlerMethodFactory(f);
}
}
Creating the DefaultMessageHandlerMethodFactory was based on code I saw in org.springframework.messaging.handler.annotation.support.MessageHandlerMethodFactory.
I would definitely not recommend using this in production. It is fairly brittle and unnecessarily complex.
Spring...sometimes it's a breath of fresh air... and sometimes it's convoluted-clap-trap
I want to seek a best practice for applying business rules when working with spring data rest.
Lets consider following scenario:
I have a Customer and Order in #OneToMany relationship.
I have a business rule saying that Customer needs to have verified flag set to be able to make orders
So I need to make sure that whenever someone POSTs to /orders the Customer making the call is verified.
I'm considering using beforeSave Validators autowiring other service/repositories into the Validator and check whatever needs to be checked.
Is there better way of achieving the same?
There are several ways to solve this. As far as my knowledge goes:
Usage of spring security annotations like #PreAuthorize. The intended use of these annotations is however for security purposes and you are mentioning business rules. I would use these for user authorization rules Spring data rest security chapter
The use of validators as you mentioned yourself. Spring data rest Validators
Use spring data rest events Spring data rest events. You can create global event handlers, however here you need to determine the entity type. I would go with Annotated event handlers to perform business logic Spring data rest annotated event handler
So just for the sake of world piece I'm adding my solution. Went with #2.
The documentation is pretty clear on how to proceed so just sharing few tips which may save you time.
You need to assign validators manually, auto-discovery doesn't work
Manually spelling event type is error prone, some helper Enum could be handy.
Like:
/**
* "beforeSave" gets called on PATCH/PUT methods
* "beforeCreate" on POST
* "beforeDelete" on DELETE
*/
enum Event {
ON_CREATE("beforeCreate"), ON_UPDATE("beforeSave"),
ON_DELETE("beforeDelete");
private String name;
Event(String name) {
this.name = name;
}
}
...
private static void addValidatorForEvents(ValidatingRepositoryEventListener eventListener, Validator validator, Event... events) {
Arrays.asList(events).forEach(event -> eventListener.addValidator(event.name, validator));
}
One out of the box solution you can use to solve your Business rules related problems, is using Spring AOP. What you can do, is define an Annotation (say #X) and place that annotation on top of your POST call.
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface X{}
Next what you need to do is, create an aspect, and run your custom validation logic in this aspect as follows,
#Aspect
#Component
public class CustomAspect {
//You can autowire beans here
#Around("#annotation(qualified name of X)")
public Object customMethod(ProceedingJoinPoint joinPoint) throws Throwable {
flag = customLogic();
if (flag){
return joinPoint.proceed(); //return if logic passes, otherwise
}else{
throw new BusinessRuleException("Business rule violated");
}
}
private boolean customLogic(){
//your custom logic goes here
}
}
And finally apply this annotation on top of any method in controller layer like:
#X
#RequestMapping(method = RequestMethod.POST, value = "do-something")
public void callSomething(HttpServletRequest request) throws Exception {
// your business logic goes here
}
Only thing to note above is that you need to pass HttpServletRequest request explicitly to your controller method in order to AOP aspect get the same context for manipulation of user session related attributes like session_id, etc.
Above solution will help you add business rules on top of your Business Logic and help you with all kinds of pre validations you want to build in your web application. It is a pretty handy application of Spring AOP. Do reach out in case of any
tl;dr: how to enable spring's ResourceUrlEncodingFilter for spring boot Error pages?
(Question written while using spring boot 1.3.7.RELEASE and Spring Framework/MVC 4.2.4.RELEASE)
Some background: We have a fairly standard spring boot/spring webmvc project using Thymeleaf as the view layer. We have the out-of-the-box spring boot Resource Chain enabled to serve static assets.
Our thymeleaf views have standard url-encoding syntax in them such as <script th:src="#{/js/some-page.js}"></script>. This relies on Spring's org.springframework.web.servlet.resource.ResourceUrlEncodingFilter to transform the url into an appropriately-versioned url such as /v1.6/js/some-page.js.
Our error handling is done by:
setting server.error.whitelabel.enabled=false
subclassing spring boot's default BasicErrorController to override public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response)
relying on our already-configured thymeleaf view resolvers to render our custom error page
The problem is: the ResourceUrlEncodingFilter isn't applying on our error pages. I assume it's a lack of the filter being registered for ERROR dispatched requests, but it's not obvious to me: a) how to customize this in spring boot; and b) why this wasn't done by default.
Update 1:
The issue seems to be with a combination of OncePerRequestFilter and the ERROR dispatcher. Namely:
ResouceUrlEncodingFilter does not bind to the ERROR dispatcher by default. While overriding this is messy it's not impossible, but doesn't help due to:
OncePerRequestFilter (parent of ResourceUrlEncodingFilter) sets an attribute on the Request indicating it's been applied so as to not re-apply. It then wraps the response object. However, when an ERROR is dispatched, the wrapped response is not used and the filter does not re-wrap due to the request attribute still being present.
Worse still, the logic for customizing boolean hasAlreadyFilteredAttribute is not overridable by request. OncePerRequestFilter's doFilter() method is final, and getAlreadyFilteredAttributeName() (the extension point) does not have access to the current request object to get the dispatcher.
I feel like I must be missing something; it seems impossible to use versioned resources on a 404 page in spring boot.
Update 2: A working but messy solution
This is the best I've been able to come up with, which still seems awfully messy:
public abstract class OncePerErrorRequestFilter extends OncePerRequestFilter {
#Override
protected String getAlreadyFilteredAttributeName() {
return super.getAlreadyFilteredAttributeName() + ".ERROR";
}
#Override
protected boolean shouldNotFilterErrorDispatch() {
return false;
}
}
public class ErrorPageCapableResourceUrlEncodingFilter extends OncePerErrorRequestFilter {
// everything in here is a perfect copy-paste of ResourceUrlEncodingFilter since the internal ResourceUrlEncodingResponseWrapper is private
}
// register the error-supporting version if the whitelabel error page has been disabled ... could/should use a dedicated property for this
#Configuration
#AutoConfigureAfter(WebMvcAutoConfiguration.class)
#ConditionalOnClass(OncePerErrorRequestFilter.class)
#ConditionalOnWebApplication
#ConditionalOnEnabledResourceChain
#ConditionalOnProperty(prefix = "server.error.whitelabel", name = "enabled", havingValue="false", matchIfMissing = false)
public static class ThymeleafResourceUrlEncodingFilterErrorConfiguration {
#Bean
public FilterRegistrationBean errorPageResourceUrlEncodingFilterRegistration() {
FilterRegistrationBean reg = new FilterRegistrationBean();
reg.setFilter(new ErrorPageCapableResourceUrlEncodingFilter());
reg.setDispatcherTypes(DispatcherType.ERROR);
return reg;
}
}
Better solutions?
This has been reported in spring-projects/spring-boot#7348 and a fix is on its way.
It seems you've made an extensive analysis of the issue; too bad you didn't report this issue earlier. Next time, please consider raising those on the Spring Boot tracker.
Thanks!
Is it possible with JSR-303 bean validation to write a custom annotation that can talk to a back end service?
We accomplished this type of validation with the "old school" Spring validators. In that case, the validator was a Spring bean and could have other services injected into it. Then that validator is injected into the controller.
An example might be an annotation (perhaps #EmailExists) to verify if an email already exists. I can only do this with a SQL query using one of our services. I would prefer to "validate" this alongside the other annotations and check it as soon as possible and not have to explicity do it in a back end service.
NOTE: We are using iBatis/MyBatis so I can't use any JPA/Hibernate tricks :-)
thanks!
That's definitely possible. Spring provides dependency injection support also within constraint validators. So you can simply inject any required services in your custom validators like this:
public class EmailExistsValidator implements ConstraintValidator<EmailExists, String> {
#Inject
private EmailValidationService service;
#Override
public void initialize(EmailExists constraintAnnotation) {}
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
return service.exists(value);
}
}
Depending on your concrete scenario it might be a good idea to first check "cheap" constraints such as #NotNull and only if these constraints are valid check more expensive constraints such as #EmailExists.
You can do this with help of group sequences and a redefined default group sequence for your type.