I am trying to write a custom bean validator and show validation messages based on locale on the user interface.
To do that, I created a validator like the following:
#Component
public class MyCustomValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return MyClass.class.equals(clazz);
}
#Override
public void validate(Object target, Errors errors) {
MyClass myObject = (MyClass)target;
if (StringUtils.isBlank(myObject.getName())) {
errors.rejectValue("name", "{myproject.myclass.validation.name});
}
}
}
I have also registered messageSource and validator beans:
#Configuration
#ComponentScan(basePackages = "com.mycompany.myproject")
public class MyProjectWebMvcConfig {
#Bean
public MessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.addBasenames("locale/myproject");
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}
#Bean
public LocalValidatorFactoryBean validator() {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.setValidationMessageSource(this.messageSource());
return validator;
}
}
In my controller I used initBinder to register my validators:
#RestController
#RequestMapping("/api/mytype")
public class MyController extends MyBaseController {
#Autowired
private MyCustomValidator myCustomValidator;
#InitBinder
protected void initBinder(WebDataBinder binder) {
super.initBinder(binder);
binder.addValidators(this.myCustomValidator);
}
#PostMapping(value = "/")
public ResponseEntity<OperationResult<Void>> save(#Valid #RequestBody MyClass myObject, BindingResult bindingResult, HttpServletRequest request) {
if (!bindingResult.hasErrors()) {
...
}
...
}
}
public class MyBaseController {
#Autowired
protected Validator validator;
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.setValidator(validator);
}
}
Still, validation error messages appear like {myproject.myclass.validation.name} on the user interface. It seems like messages are not read from messageSource even if I have set validation message source of LocalValidatorFactoryBean.
On the other hand, if I use #NotNull(message = {myproject.myclass.validation.name}) annotation instead of using a custom validator, the validation error message appears correctly.
Couldn't figure out what I am missing.
Try this
errors.rejectValue("name", "myproject.myclass.validation.name");
Check encoding of messageSource files. It should be utf-8.
Related
I need to intercept methods from a interface, and found this implementation of MethodInterceptor, which I tested on a new spring app and worked.
The problem is, I can't seem to get it working on the spring application I need it to.
#Configuration
public class TestMethodConfig {
#Autowired
private TestService testService;
#Bean
#Primary
public ProxyFactoryBean testProxyFactoryBean() {
ProxyFactoryBean testProxyFactoryBean = new ProxyFactoryBean();
testProxyFactoryBean.setTarget(testService);
testProxyFactoryBean.setInterceptorNames("testMethodInterceptor");
return testProxyFactoryBean;
}
}
#Service
public class TestServiceImpl implements TestService{
#Override
public void testMethod(String test) {
System.out.println("testService String");
}
}
public interface TestService{
void testMethod(String test);
}
#RestController
public class Controller {
#Autowired
private TestService testProxyFactoryBean;
#GetMapping(value = "/test")
public void test(){
testProxyFactoryBean.testMethod("valor");
}
}
#Component
public class TestMethodInterceptor implements MethodInterceptor {
#Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("before method");
System.out.println("invocation: " + Arrays.toString(invocation.getArguments()));
Object retVal = invocation.proceed();
System.out.println("after method");
return retVal;
}
}
I used Spring Actuator to check the beans relations, and I found that the #Autowired TestService on Controller should be getting assigned to testProxyFactoryBean, but its getting assigned to the TestServiceImpl bean instead, so I believe there is a problem creating the proxy.
In short
I don't know how/why it was:
on a new spring app and worked.
but:
I can't seem to get it working on the spring application I need it to.
..can probably be fixed!
Make it consistent
Or:
#Configuration
public class TestMethodConfig {
#Autowired
private TestService testService;
}
...
// !!
public class TestServiceImpl implements TestService{
#Override
public void testMethod(String test) {
System.out.println("testService String");
}
}
...
#Service // !!!
public interface TestService{
void testMethod(String test);
}
...
#RestController
public class Controller {
#Autowired
private TestService testProxyFactoryBean;
...
Or: Impl!
(Use Interface and Impl consistently!)
In Detail
6.4. Using the ProxyFactoryBean to Create AOP Proxies
esp. Proxying Interfaces.
So with "least impact" (and java config), it should be:
#Configuration
public class TestMethodConfig {
// !!! Impl from component-scan (#Service), NOT interface:
#Autowired
private TestServiceImpl testServiceImpl; // or define custom, or "inline"...
#Bean
#Primary // only if you need it, better would be: distinct!
public ProxyFactoryBean testProxyFactoryBean() {
ProxyFactoryBean testProxyFactoryBean = new ProxyFactoryBean();
// !!! set proxyInterface as documented:
testProxyFactoryBean.setProxyInterface(TestService.class);
testProxyFactoryBean.setTarget(testServiceImpl);
testProxyFactoryBean.setInterceptorNames("testMethodInterceptor");
// ...
return testProxyFactoryBean;
}
}
..enjoy! ;)
I implemented a OncePerRequestFilter, where in the doFilterInternal() I would like to use an utilization class, that used JdbcTemplate and user data from a properties file. I realized that it couldn't reach the data from the properties file (database connection and variables) and has null value all the time. As I found on the internet it's, because of the different context.
I could successfully setup a new jdbc datasource locally, but I wouldn't like to duplicate the code, so I would like to inject simply the sources the same way as I did everywhere else like in RestControllers (#Value, #Autowired).
Any idea, how could I inject these in my utilization class that will be used in the servlet filter or directly in my filter?
Thank you!
UPDATE - code snippets:
In the RestController, the injection of JdbcTemplate works properly, but in the filter I cannot inject it, always throws nullPointerException.
#SpringBootApplication
public class AsdApplication {
public static void main(String[] args) {
SpringApplication.run(AsdApplication.class, args);
}
public static class ApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Filter[] getServletFilters() {
DelegatingFilterProxy delegateFilterProxy = new DelegatingFilterProxy();
delegateFilterProxy.setTargetBeanName("MyFilter");
return new Filter[] { delegateFilterProxy };
}
#Override
protected Class<?>[] getRootConfigClasses() {
return null;
}
#Override
protected Class<?>[] getServletConfigClasses() {
return null;
}
#Override
protected String[] getServletMappings() {
return null;
}
}
}
#RestController
public class RestCtrl {
#Autowired
private JdbcTemplate jdbcTemplate;
#GetMapping("/test")
public ResponseEntity<String> getTest() {
String result = jdbcTemplate.queryForObject("<query>", String.class);
System.out.println("result in ctrl: " + result);
return new ResponseEntity<>("asd ad asd asd asd", HttpStatus.OK);
}
}
#Component(value = "MyFilter")
public class MyFilter extends OncePerRequestFilter {
#Autowired
private JdbcTemplate jdbcTemplate;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String result = jdbcTemplate.queryForObject("<query>", String.class);
System.out.println("result in filter: " + result);
User currentUser = new User("username", "password", new ArrayList<>());
UsernamePasswordAuthenticationToken authenticatedUser = new UsernamePasswordAuthenticationToken(
currentUser, null, currentUser.getAuthorities()
);
SecurityContextHolder.getContext().setAuthentication(authenticatedUser);
filterChain.doFilter(request, response);
}
}
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable().authorizeRequests().anyRequest().authenticated();
httpSecurity.addFilterBefore(new MyFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
spring.datasource.url=jdbc:<sqlserver>
spring.datasource.username=<user>
spring.datasource.password=<pass>
spring.datasource.driver-class-name=com.microsoft.sqlserver.jdbc.SQLServerDriver
As you are actually using Spring Boot and want to make it part of the Spring Security filter chain (which is something different!) what you need to do is
Create an #Bean method to create the filter and make it a bean
Create an #Bean method and add a FilterRegistration bean to prevent the bean from being registered as a filter by Spring Boot
Configure Spring Security.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable().authorizeRequests().anyRequest().authenticated();
httpSecurity.addFilterBefore(myFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
#Bean
public MyFilter myFilter() {
return new MyFilter();
}
#Bean
public FilterRegistrationBean<MyFilter> myFilterRegistationBean() {
FilterRegistationBean frb = new FilterRegistrationBean(myFilter());
frb.setEnabled(false);
return frb;
}
Finally remove the #Component from your MyFilter as you don't need it and it would create an additional instance. All prior changes (like the ApplicationInitializer etc. you can remove.
NOTE: As you are using Spring Security and somehow use this for authentication, instead of extending OncePerRequestFilter I suggest you extend the Spring Security AbstractAuthenticationProcessingFilter which integrates better with Spring Security (like fireing events for authentication, logging etc.).
I see you are creating a new instance of MyFilter instead of using the one managed by Spring with #Component(value = "MyFilter")
httpSecurity.addFilterBefore(new MyFilter(), UsernamePasswordAuthenticationFilter.class);
Hence you will hit a NPE since jdbcTemplate is null. You can inject the instance managed be Spring instead of creating a new one.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
#Qualifier("MyFilter")
private MyFilter myFilter;
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable().authorizeRequests().anyRequest().authenticated();
httpSecurity.addFilterBefore(myFilter, UsernamePasswordAuthenticationFilter.class);
}
}
You should use this:
Through this class you can get different Spring Boot Beans in a non Bean class.
#Component
public class ApplicationContextUtils implements ApplicationContextAware {
private static ApplicationContext ctx;
#Override
public void setApplicationContext(ApplicationContext appContext)
throws BeansException {
ctx = appContext;
}
public static ApplicationContext getApplicationContext() {
return ctx;
}
}
Then after creating it, get your bean this way:
ApplicationContext appCtx = ApplicationContextUtils.getApplicationContext();
// Here you get your dependency
ARequiredClass dependency = appCtx.getBean(ARequiredClass.class);
I am trying to send a custom error for anonymous unauthorized requests through a custom AuthenticationEntryPoint
public class AccessDeniedAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Autowired
private Messages messages;
#Override
public void commence(final HttpServletRequest request,
final HttpServletResponse response,
final AuthenticationException authException) throws IOException {
response.setStatus(HttpStatus.SC_UNAUTHORIZED);
response.setContentType(MediaType.APPLICATION_JSON);
response.setCharacterEncoding("UTF-8");
final ObjectMapper mapper = new ObjectMapper();
final ApplicationErrorResponse error = new ApplicationErrorResponse(
Message.PERMISSION_FAILED.toString(),
messages.get(Message.PERMISSION_FAILED.toString()));
if (response.getWriter() != null) {
response.getWriter().write(mapper.writeValueAsString(error));
response.getWriter().flush();
}
}
The Messages bean encapsulates the default MessageSource (messages.properties) and returns the values through a MessageSourceAccessor
#Component
public class Messages {
#Autowired
private MessageSource messageSource;
private MessageSourceAccessor accessor;
#PostConstruct
private void init() {
accessor = new MessageSourceAccessor(messageSource);
}
public String get(String code) {
return accessor.getMessage(code);
}
}
However when an AccessDendiedException is raised and the commence method in the AccessDeniedAuthenticationEntryPoint is called, the injected/autowired Messages object is null.
I just couldn't get Spring to successfully inject Messages in a custom Authentication Entry Point.
I even tried registering a bean of type MessageSource
#Bean
public MessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasenames("messages");
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}
and tried autowiring it in the AuthenticationEntryPoint but it didn't work too.
How can I successfully inject/autowire a MessageSource in a custom AuthenticationEntryPoint?
I'm using Spring Boot 1.3.5 with Rest Controllers and everything is working fine.
I am also using Spring's validation sample techniques from the official documentation (JSR-303 Bean Validation API and Spring's validator interface, i tried both and faced the same problem) and the validations are working, but I am not able to configure custom messages.
I have configured a messages.properties file, and I can access the messages on this file just fine. However this validation seems not to be capable of reading or accessing my messages source (messages.properties) configured automatically via spring boot.
I can access the messages directly from the messages source object injected in controller via #Autowired (there's a comment in the code). However, the binding result of the Spring's validator interface or the JSR-303 Bean Validation seems to not be capable of accessing the messages.properties loaded in MessageSource. The result I have is that my errors have codes but don't have default messages.
Here is my Application class:
#SpringBootApplication
#ImportResource({ "classpath:security/cas-context.xml", "classpath:security/cas-integration.xml",
"classpath:security/security.xml" })
#EnableAutoConfiguration(exclude = VelocityAutoConfiguration.class) // http://stackoverflow.com/questions/32067759/spring-boot-starter-cache-velocity-is-missing
public class Application extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public ServletRegistrationBean cxfServlet() {
return new ServletRegistrationBean(new CXFServlet(), "/services/*");
}
#Bean(name = Bus.DEFAULT_BUS_ID)
public SpringBus springBus() {
return new SpringBus();
}
#Bean
public Nfse nfseService() {
return new NfseImpl();
}
#Bean
public Endpoint endpoint() {
EndpointImpl endpoint = new EndpointImpl(springBus(), nfseService());
endpoint.publish("/nfseSOAP");
return endpoint;
}
}
Here is my Bean:
public class Protocolo {
private Long id;
#NotNull
#Min(1)
#Max(1)
private String protocolo;
private StatusProtocoloEnum status;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getProtocolo() {
return protocolo;
}
public void setProtocolo(String protocolo) {
this.protocolo = protocolo;
}
public StatusProtocoloEnum getStatus() {
return status;
}
public void setStatus(StatusProtocoloEnum status) {
this.status = status;
}
}
Here is My rest controller:
#RestController
public class ProtocoloController {
#Autowired
private MessageSource messageSource;
#Autowired
private ProtocoloDAO protocoloDAO;
#RequestMapping(value = "/prot", method = RequestMethod.POST)
public void testar(#Valid #RequestBody Protocolo p) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
System.out.println(auth.getAuthorities());
System.out.println(messageSource.getMessage("protocolo.tamanho", null, null));
// IN THIS PART I'M ABLE TO PRINT THE MESSAGE IF VALIDATION IS DISABLED
System.out.println(p.getProtocolo());
}
}
So, this code works fine and the method is not called since i'm calling the method with a invalid Protocolo. However, my angularJS client receives the response with the errors codes populated but with all the default messages empty since the validation is not seeing my loaded messages.properties.
Is there a way to make my Spring validation Interfaces or JSR-303 validation incorporate the loaded message.properties (messagesource) in spring boot ? How can i correct this ? If it's necessary i can paste my code sample of Spring Validation interfaces also.
Thank's a lot,
Tarcísio.
TEST CODE:
#RestController
public class ProtocoloController {
#Autowired
private MessageSource messageSource;
#Autowired
private ProtocoloDAO protocoloDAO;
#RequestMapping(value = "/prot", method = RequestMethod.POST)
public void testar(#Valid #RequestBody Protocolo p, BindingResult bindingResult) {
System.out.println(messageSource.getMessage("Min.protocolo.protocolo", null, null));
if (bindingResult.hasErrors()) {
System.out.println(bindingResult.getFieldError().getDefaultMessage());
System.out.println(bindingResult.getFieldError().getCode());
}
System.out.println(p.getProtocolo());
}
}
Edit:
Known Bug in Spring Boot 1.5.3 see https://github.com/spring-projects/spring-boot/issues/8979
In Spring Boot since 1.5.3 you need to do this
#Configuration
public class ValidationMessageConfig {
#Bean
public LocalValidatorFactoryBean mvcValidator(MessageSource messageSource) {
LocalValidatorFactoryBean factory = new LocalValidatorFactoryBean();
factory.setValidationMessageSource(messageSource);
return factory;
}
}
and then it will work.
With version 1.5.2 and before you can extend WebMVcConfigurerAdapter
#Configuration
public class ProfileMvcConfig extends WebMvcConfigurerAdapter {
private MessageSource messageSource;
#Autowired
public ProfileMvcConfig(MessageSource messageSource) {
this.messageSource = messageSource;
}
/**
* This method is overridden due to use the {#link MessageSource message source} in bean validation.
*
* #return A Validator using the {#link MessageSource message source} in bean validation.
*/
#Override
public Validator getValidator() {
LocalValidatorFactoryBean factory = new LocalValidatorFactoryBean();
factory.setValidationMessageSource(messageSource);
return factory;
}
}
also see the documentation
In Spring Boot applicaton MessageSource is configured with a MessageSourceAutoConfiguration and you don't need to autowire it. For jsr303, create proper key-value pair in the messages.properties file. For "protocolo" field, you should have following values in property file.
NotNull.protocolo.protocolo=Field cannot be left blank
Min.protocolo.protocolo=Minimum value must be {1}
You can also check messages from property file like below in your code.
public void testar(#Valid #RequestBody Protocolo p,BindingResult bindingResult) {
if(bindingResult.hasErrors()) {
System.out.println(bindingResult.getFieldError().getDefaultMessage());
}
}
you should have following values in property file:
Min.protocolo.protocolo=Minimum value must be {1}
then in the controller you obtain the message by calling function getMessage from messageSource object
Test code:
#RequestMapping(value = "/prot", method = RequestMethod.POST)
public void testar(#Valid #RequestBody Protocolo p, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
bindingResult.getFieldErrors().forEach(fieldError ->
System.out.println(messageSource.getMessage(fieldError, Locale.getDefault()))
);
}
System.out.println(p.getProtocolo());
}
I solved this in custom message in Spring validation read the last part of my answer.
Check this example as well.
I used a custom validator with custom annotation. I needed to change code in my custom validator.
public class PersonValidator implements ConstraintValidator {
#Override
public boolean isValid(final Person person, final ConstraintValidatorContext context) {
if (somethingIsInvalid()) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("Something is invalid.").addConstraintViolation();
return false;
}
return true;
}
}
At first I code a RestController with ExceptionHandlers and everything works well. When I added an advice that wrap my #RequestMapping (with #Around) it broke my ExceptionHandler. When exception is thrown. E.g., TypeMisMatchException, All of a sudden, my controller lost its autowired beans (all of them are null).
I added a check in #PostConstruct and saw that my controller actually wires the beans.
MyRestContoller:
#RestController
#ControllerAdvice
public class MyRestController implements MyRestControllerInterface<Throwable> {
#Autowired private ApplicationContext applicationContext;
#Autowired private FirstBean firstBean;
#Autowired private SecondBean secondBean;
#PostConstruct
private void init() {
//just to print that beans are auto wired properly
//All beans are ok
}
#RequestMapping(value=“/”, method=RequestMethod.POST)
public ResponseEntity<String> postData(#SuppressWarnings("unused") #PathVariable ValidVersion version, #RequestBody DataRequest dataRequest) throws Throwable {
//stuff
return new ResponseEntity<>(newObj, HttpStatus.CREATED);
}
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
#ExceptionHandler(Throwable.class)
private ErrorInfo handleBadRequest(HttpServletRequest req, Exception e) {
log.error("Error serving URL '" + req.getRequestURL()+ "': ", e);
//do something with firstBean
//but if thrown with configured advice the firstBean is null,
//and secondBean and applicationContext...
//
return new ErrorInfo(ErrorCodes.InternalServerError, req.getRequestURL().toString(), null, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
My Aspect:
#Service
#Aspect
public class SomeAspect {
#Autowired private GraphiteBeanReporter graphiteBeanReporter;
#Pointcut("within(#org.springframework.web.bind.annotation.RestController *)")
public void restController() {}
#Pointcut("execution(* com.sample.MyRestController.*(..))")
public void methodPointcut() {}
#Around("restController() && methodPointcut()")
public Object whatToDoAround(ProceedingJoinPoint joinPoint) throws Throwable {
String metricName = joinPoint.getSignature().getName();
log.trace("inside the aspect of method '{}', metricName);
Context time = graphiteBeanReporter.initAndStartTimerMetic(metricName);
try {
Object result = joinPoint.proceed();
return result;
} finally {
if (time != null)
time.stop();
}
}
Just to add more information
The MyRestController and SomeAspect beans are configured in MVCCOnfig.class
while other business logic beans (i.e. FirstBean, SecondBean and more) are configured in AppConfig.class
My Initializer:
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
public WebAppInitializer() {
// stuff
}
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] { SecurityConfig.class, AppConfig.class };
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { MVCConfig.class };
}
}
Edit
added the implementation of whatToDoAround advice. It's basically report metric ('timer') to Graphite server using autowired bean. As I mentioned, if no exception is thrown in the controller, the metric is reported as expected.
Update -
specifically, ValidVersion is an enum that represent my API valid versions. An Exception is thrown where in the url #PathVariable is set with unknown String (one that can't be converted to enum value). This exception doesn't trigger any of my advices. I even tried to use #AfterThrowing without any success.