Confused about ThymeleafConfig in Spring Boot - spring

I added a ThymeleafConfig to my Spring Boot application so I could configure the template mode to HTML5. Prior to adding it, the Spring Boot application could find the home.html template. After adding it now I get a:
org.thymeleaf.exceptions.TemplateInputException: Error resolving template "home", template might not exist or might not be accessible by any of the configured Template Resolvers
My directory structure is a standard resources/templates/home.html
Here is my ThmyeleafConfig:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.thymeleaf.templateresolver.ServletContextTemplateResolver;
#Configuration
public class ThymeleafConfig {
#Bean
public ServletContextTemplateResolver defaultTemplateResolver() {
ServletContextTemplateResolver resolver = new ServletContextTemplateResolver();
resolver.setPrefix("/templates/");
resolver.setSuffix(".html");
resolver.setTemplateMode("LEGACYHTML5");
resolver.setCacheable(false);
return resolver;
}
#Bean
public ResourceBundleMessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasename("messages");
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}
}
I'm pretty sure I've followed what the examples are doing but there's obviously something I've missed. Any suggestions how I can fix this so it finds the templates properly?

There's no need to declare your own beans. You can configure the mode using application.properties:
spring.thymeleaf.mode=LEGACYHTML5

Related

How to add a URL prefix in Spring Boot

I have a Spring Boot 2 app, mainly used for REST endpoints, and I want to add a prefix to endpoints via a bean configuration instead of having a setting in the application.yml file that makes the prefix global. i.e example.com/api/ I know that you can have this configured with a annotation on the controller classes as well, but I want to know if this can be done with a bean.
You can do it in the following way:
import org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration;
import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.DispatcherServlet;
#Configuration
public class DispatcherServletCustomConfiguration {
#Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
#Bean
public ServletRegistrationBean servletRegistrationBean() {
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(
dispatcherServlet(), "/api/");
servletRegistrationBean.setName(DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME);
return servletRegistrationBean;
}
}

swagger-ui not working with custom XML ObjectMapper

I'm working on a spring boot app that should have swagger-ui enabled.
When accessing http://localhost:8080/swagger-ui.html there is an error popup:
"Unable to infer base url ..."
Additionaly, http://localhost:8080/v2/api-docs shows:
error on line 1 at column 1: Document is empty
The source-code of this page is a json, but it's requested as Content-Type application/xhtml+xml;charset=UTF-8
The cause of this seems to be my custom Jackson configuration:
#Configuration
public class JacksonConfig {
#Bean
public MappingJackson2XmlHttpMessageConverter mappingJackson2XmlHttpMessageConverter() {
return new MappingJackson2XmlHttpMessageConverter(objectMapper());
}
#Bean
public ObjectMapper objectMapper() {
JacksonXmlModule xmlModule = new JacksonXmlModule();
xmlModule.setDefaultUseWrapper(false);
XmlMapper objectMapper = new XmlMapper(xmlModule);
objectMapper
.registerModule(new ParameterNamesModule())
.registerModule(new Jdk8Module())
.registerModule(new JavaTimeModule());
objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.UPPER_CAMEL_CASE);
objectMapper
.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false)
.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
return objectMapper;
}
}
With the following dependency:
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
The problem is also described here: https://github.com/springfox/springfox/issues/1835
So my question is: How do I specify the priority of the jackson message converter to get swagger-ui working?
I just stumbled upon the solution while re-reading my own question.
Just add this to the above JacksonConfig class (don't know if ordering is important, but it works).
#Bean
public MappingJackson2HttpMessageConverter jsonConverter() {
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = new ObjectMapper();
jsonConverter.setObjectMapper(objectMapper);
return jsonConverter;
}
Inside the swagger code, it checks to see if an ObjectMapper exists, and if not, it creates one to use. If a bean has been created that uses either an ObjectMapper or XMLMapper, then Swagger will use this instance, and get corrupted. The way round this is to create a bean for the ObjectMapper and use the #Primary annotation, then create the XMLMapper bean you want to use.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
#Configuration
public class MessageResponseXMLMapper {
#Bean
#Primary
public ObjectMapper customObjectMapper() {
return new ObjectMapper();
}
#Bean(name="customXmlMapper")
public XmlMapper customXmlMapper() {
return new Jackson2ObjectMapperBuilder()
.indentOutput(true)
.createXmlMapper(true)
.propertyNamingStrategy(PropertyNamingStrategy.UPPER_CAMEL_CASE)
.build(); }
}
Hope this helps

Spring PropertySourcesPlaceholderConfigurer in library

Background: I am writing a library that will be compiled into a JAR file. That library will be used as a dependency in a number of web applications. Both the library and the web apps are using Spring. There is an onus on the web application to run a ComponentScan on the library classes to pick up any Spring Beans / configuration.
Ask: Within the library I want to load properties from a property file using PropertySourcesPlaceholderConfigurer. Something like this:
package com.blah;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
#Configuration
#PropertySource("classpath:properties/blah.${environment}.properties")
public class AppConfig {
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
This is working fine.
Question: if the web-app that loads this library as a dependency also uses PropertySourcesPlaceholderConfigurer to load properties, will there be a conflict between the two? Will one override the other (even if the properties are different)? Or can they live in peaceful harmony side-by-side?
Using Spring 3.2.4
UPDATE
As per Bogdan Oros's answer below, it looks like this is OK i.e. they will not conflict and both sets of properties will be loaded. I created two Config files:
#Configuration
#PropertySource("classpath:properties/blah.stage1.properties")
public class BlahClientConfig1 {
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
and
#Configuration
#PropertySource("classpath:properties/blah.stage2.properties")
public class BlahClientConfig2 {
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
When I run my tests, I can successfully retrieve property values from both blah.stage1.properties and blah.stage2.properties
You can do an experiment a create same bean in two different configurations in same classpath and it will work
#Configuration
class AConfiguration {
#Bean
public static PropertySourcesPlaceholderConfigurer resolver() {
return new PropertySourcesPlaceholderConfigurer();
}
}
#Configuration
class B {|
#Bean
public static PropertySourcesPlaceholderConfigurer resolver() {
return new PropertySourcesPlaceholderConfigurer();
}
}
It will work correctly because spring resolves such situations.
You'll find that one bean is overridden by the other.
But if you will help spring and setup names explicitly
#Configuration
class A {
#Bean(name="resolver")
public static PropertySourcesPlaceholderConfigurer resolver() {
return new PropertySourcesPlaceholderConfigurer();
}
}
#Configuration
class B {
#Bean(name="resolver")
public static PropertySourcesPlaceholderConfigurer resolver() {
return new PropertySourcesPlaceholderConfigurer();
}
}
This situation will cause a injection failure, because it cannot decide which bean to inject.
It is explained here and also it can be configured with DefaultListableBeanFactory. Check this answer.

Add a Servlet Filter in a Spring Boot application

I'd like to have ETag suport. For this purpose there is a ShallowEtagHeaderFilter which does all the work. How can I add it without declaring it in my web.xml (which actually does not exist, because I somehow got by without it so far)?
P.S. I use Spring Boot 1.1.4
P.P.S. Here's a full solution
package cuenation.api;
import org.springframework.boot.context.embedded.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.ShallowEtagHeaderFilter;
import javax.servlet.DispatcherType;
import java.util.EnumSet;
#Configuration
public class WebConfig {
#Bean
public FilterRegistrationBean shallowEtagHeaderFilter() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new ShallowEtagHeaderFilter());
registration.setDispatcherTypes(EnumSet.allOf(DispatcherType.class));
registration.addUrlPatterns("/cue-categories");
return registration;
}
}
When using Spring Boot
As mentioned in the reference documentation, the only step needed is to declare that filter as a Bean in a configuration class, that's it!
#Configuration
public class WebConfig {
#Bean
public Filter shallowEtagHeaderFilter() {
return new ShallowEtagHeaderFilter();
}
}
When using Spring MVC
You're probably already extending a WebApplicationInitializer. If not, then you should convert your webapp configuration from a web.xml file to a WebApplicationInitializer class.
If your context configuration lives in XML file(s), you can create a class that extends AbstractDispatcherServletInitializer - if using configuration classes, AbstractAnnotationConfigDispatcherServletInitializer is the proper choice.
In any case, you can then add Filter registration:
#Override
protected Filter[] getServletFilters() {
return new Filter[] {
new ShallowEtagHeaderFilter();
};
}
Full examples of code-based Servlet container initialization are available in the Spring reference documentation.
A bit late answer.
My solution was to create custom annotation:
import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Component;
// ...
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
#Component
public #interface Filter {
#AliasFor(annotation = Component.class, attribute = "value")
String value() default "";
}
And then simply apply it to the filter implementations:
#Filter
public class CustomFilter extends AbstractRequestLoggingFilter {
#Override
protected void beforeRequest(HttpServletRequest request, String message) {
logger.debug("before req params:", request.getParameterMap());
}
#Override
protected void afterRequest(HttpServletRequest request, String message) {
logger.debug("after req params:", request.getParameterMap());
}
}
See more: #AliasFor, Spring custom annotations question

Using multiple Resources Bundles with Thymeleaf

I would like to use multiple resources bundles within a Spring MVC application using Thymeleaf. I am unable to access
Project structure (EAR)
MyProject (includes both projects below through the Deployment Assembly)
MyProjectEJB
MyProjectWeb
src
baseproject
configuration
ThymeleafConfig
WebConfig
WebContent
WEB-INF
lib
my libs...
messages
global
GlobalResources (got both GlobalResources_fr.properties and GlobalResources_en.properties).
user
UserResources (got both UserResources_fr.properties and UserResources_en.properties).
views
user
createOrUpdateUserForm.html
WebConfig.java
package baseproject.configuration;
import java.util.Locale;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = "baseproject.controller")
public class WebConfig extends WebMvcConfigurerAdapter {
public ResourceBundleMessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
String[] strBaseNames = {
"/WEB-INF/messages/global/GlobalResources",
"/WEB-INF/messages/user/UserResources",
};
messageSource.setUseCodeAsDefaultMessage(true);
messageSource.setDefaultEncoding("UTF-8");
// # -1 : never reload, 0 always reload
messageSource.setCacheSeconds(0);
messageSource.setBasenames(strBaseNames);
return messageSource;
}
#Bean
public LocaleChangeInterceptor localeChangeInterceptor() {
LocaleChangeInterceptor result = new LocaleChangeInterceptor();
result.setParamName("lang");
return result;
}
#Bean
public LocaleResolver localeResolver() {
SessionLocaleResolver sessionLocaleResolver = new SessionLocaleResolver();
sessionLocaleResolver.setDefaultLocale(Locale.ENGLISH);
return sessionLocaleResolver;
}
#Override
public void addInterceptors(InterceptorRegistry interceptorRegistry) {
interceptorRegistry.addInterceptor(localeChangeInterceptor());
}
}
ThymeleafConfig.java
package baseproject.configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.thymeleaf.spring4.SpringTemplateEngine;
import org.thymeleaf.spring4.view.ThymeleafViewResolver;
import org.thymeleaf.templateresolver.ServletContextTemplateResolver;
#Configuration
public class ThymeleafConfig {
#Bean
public ServletContextTemplateResolver templateResolver() {
ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver();
templateResolver.setPrefix("/WEB-INF/views/");
templateResolver.setSuffix(".html");
//NB, selecting HTML5 as the template mode.
templateResolver.setTemplateMode("HTML5");
templateResolver.setCacheable(false);
return templateResolver;
}
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver());
return templateEngine;
}
#Bean
public ViewResolver viewResolver() {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(templateEngine());
viewResolver.setOrder(1);
viewResolver.setViewNames(new String[]{"*"});
viewResolver.setCache(false);
return viewResolver;
}
}
Application Context XML files
<context:annotation-config />
<context:component-scan base-package="baseproject.controller" />
HTML file code
<label for="strFirstName" th:text="#{first.name} + #{:}">First Name</label>
<input type="text" id="strFirstName" name="strFirstName" th:value="*{strFirstName}" />
When it comes to the #{first.name}, I always see ??first.name_en??. I would like to be able to use multiple bundles, like the first name (#{first.name}) would come from UserResources and ${:} would come from GlobalResources (as it is used across the entire application). I am coming from Struts 1.3.5, and I was using the following tag:
<bean:message bundle="Bundle name from the struts-config.xml file)" key="first.name" />
I am looking for the equivalent using Spring and Thymeleaf.
Many thanks for help.
Problem fixed.
Two things:
I had to put my resource bundles in my classpath, so I needed to change the following code to point to the right place:
String[] strBaseNames = {
"ca.gc.baseproject.messages.global.GlobalResources",
"ca.gc.baseproject.messages.user.UserResources",
};
Missing #Bean annotation on the following method:
#Bean
public SpringTemplateEngine templateEngine()
I have also tried to let my resource bundles in the WEB-INF folder but no success. I am comfortable in putting my bundles in the classpath, so they can also be used across the Java application.
Put your resource files into "WebContent/WEB-INF/messages/global or user" instead of "WebContent/messages/global or user".
Hope this helps.
If it still is actual problem:
The fix is:
Just remove #EnableWebMvc and LocaleChangeInterceptor will work fine!

Resources