I have a message bundle under my resources in src/main the resources with messages are named messages_en_US.properties i.e. and I am getting the message to my JSP.
#Component
public class Messages {
private MessageSource messageSource;
#Autowired
public Messages(MessageSource messageSource) {
this.messageSource = messageSource;
}
private MessageSourceAccessor accessor;
#PostConstruct
private void init() {
accessor = new MessageSourceAccessor(messageSource);
}
public String get(String code) {
return accessor.getMessage(code);
}
}
When I used the code above without any beans I had an exception that informed me there is no code for a message. So I added a BeanHelper class as below.
#Configuration
#ComponentScan("com")
public class BeansHelper {
#Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:messages");
return messageSource;
}
}
When now I am trying to go on localhost:8000/ I am getting an 404 that says there is no message available. What is wrong in this code? My messages are under src/main in the resources. But the JSP of course are in WEB-INF/views/ any ideas guys how to solve the problem? I correctly added message properties file to my Local.
Related
I have the simple class to catch some exceptions like this:
#ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(RuntimeException.class)
public ResponseEntity<Object> handleRuntimeException(Exception ex) {
log.debug("[RuntimeException throw]");
return new ResponseEntity<>(responseMessage, HttpStatus.OK);
}
}
Then I can throw new RuntimeException and can see the output log in console.
It well done works but I have to move the RestResponseEntityExceptionHandler.class to another project as a lib and add this like a maven dependency in pom.xml.
Also in the main project added ControllerAdvice which extends moved:
#ControllerAdvice
public class ResponseEntityExceptionHandler extends RestResponseEntityExceptionHandler { }
After that it does not work. I have the next exception:
org.springframework.context.NoSuchMessageException: No message found under code 'description' for locale 'ru_RU'.
When RestResponseEntityExceptionHandler was in the main project, I had the next bean definition:
#Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource
= new ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:messages");
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}
and in the 'resource' folder I had messager_ru.properties file. I moved the messageSource bean config and properties file too.
Perhaps the point is that the project that I moved all this to is a library (no main function)?
I have to create different messageSources programmatically and put them in a Bean in order to use the correct one when needed.
The application must have a messageSource for each of our Customers, so i created a Configuration class
#Configuration
public class MessageSourceConfig implements BeanFactoryAware {
private BeanFactory beanFactory;
#Autowired
private ICompanyService service;
private Map<Company, MessageSource> messageSourceMap = new HashMap<Company, MessageSource>();
// default messageSource
#Bean
#Primary
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:messages");
messageSource.setUseCodeAsDefaultMessage(true);
messageSource.setCacheSeconds(5);
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}
#Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
#PostConstruct
public void onPostConstruct() {
ConfigurableBeanFactory configurableBeanFactory = (ConfigurableBeanFactory) beanFactory;
Iterable<Company> companies = service.findAll();
for(Company c : companies) {
String beanName= c.getSlug()+"_messageSource";
MessageSource bean = getCompanyMessageSource(c);
configurableBeanFactory.registerSingleton(beanName, bean);
messageSourceMap.put(c, bean);
}
}
private MessageSource getCompanyMessageSource(Company company) {
ReloadableResourceBundleMessageSource ms = new ReloadableResourceBundleMessageSource();
ms.setBasename("classpath:" + company.getSlug() + "/messages");
ms.setUseCodeAsDefaultMessage(true);
ms.setCacheSeconds(5);
ms.setDefaultEncoding("UTF-8");
return ms;
}
public MessageSource companyMessageSource(Company company) {
return messageSourceMap.get(company);
}
In this way we have a default messageSource and one specific messageSource for each Company.
The idea was to put this specific messageSources into a Map and then accessing the correct one from the map when we need it.
The problem is that companyMessageSource should be a bean, but i cannot pass a parameter to the bean, how can i access dynamically the correct source?
I am not entirely sure I understand how you want to use the created beans, but one way to get the registered singletons of MessageSource is to get them programmatically something like this:
#Service
public class CompanyService {
#Autowired
private ApplicationContext applicationContext;
public void useCompanySpecificMessageSource(Company c) {
MessageSource ms = applicationContext.getBean(c.getSlug() + "_messageSource");
log.debug(ms.getMessage("code", null, new Locale("en", "GB"));
}
}
Hope this helps.
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;
}
}
i have message source defined in my java config as :
#Bean(name = "messageSource")
public MessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasenames(
"/i18n/ir/kia/industry/webapp/entity",
"/i18n/ir/kia/industry/webapp/formErrors",
"/i18n/ir/kia/industry/webapp/frontend",
"/i18n/ir/kia/industry/webapp/frontendPages");
return messageSource;
}
it works fine when using site and messages are displayed correctly, but when trying to write spring test with :
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {TestContext.class, SpringMVC.class})
#WebAppConfiguration
public abstract class AbstractTestClass {
protected MockMvc mockMvc;
#Autowired
private WebApplicationContext webApplicationContext;
#Before
public void setUp() throws Exception {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
}
and a test class that simply extends it, i get error Can't find bundle for base name /i18n/ir/kia/industry/webapp/entity.
it works fine when starting tomcat and using message source in jsp files, but no luck when testing it. i have tried moving i18n folder under WEB-INF but it did not help it neither.
the target folder looks like this and please do not tell me add i18n folder to target resources ...
i managed to solve the problem by removing the message source base names using spring profile feature. i changed the message source part to :
#Bean(name = "messageSource")
#Profile(value = {"dev","prod"})
public MessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasenames(
"/i18n/ir/kia/industry/webapp/entity",
"/i18n/ir/kia/industry/webapp/formErrors",
"/i18n/ir/kia/industry/webapp/frontend",
"/i18n/ir/kia/industry/webapp/frontendPages");
messageSource.setCacheSeconds(5);
return messageSource;
}
#Bean(name = "messageSource")
#Profile("test")
public MessageSource testMessageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
return messageSource;
}
and add test profile to my test unit with
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {TestContext.class, SpringMVC.class})
#WebAppConfiguration
#ActiveProfiles("test")
public abstract class AbstractTestClass {
that when i was able to run my tests, but that is a way around for solving problem. i'm still confused for what was the reason of error in the first place.
it that some sort of bug? or i'm doing something wrong?