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
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;
}
}
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
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.
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
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!