Springboot - Thymeleaf tries to resolve files it should not - spring

Hi I am currently working on a web project that uses thymeleaf and also JSF (its a legacy system and we can only slowly migrate to thymeleaf thats why JSF is still there and cannot be removed from one day to another since this is a lot of work). Thymeleaf is configured to resolve the views in the webapp directory that lie under the directory "thymeleaf". This works perfectly if I deploy the application directly on a tomcat server. Also pages from other directories then the "thymeleaf" directory are also resolved by the JSF framework.
I added some integration tests in JUnit that are using SpringBoot. Inside these tests I got the problem that thymeleaf now is trying to resolve any page in any directory. JSF is completely ignored and I got a whole bunch of JUnit tests failing because of that. Is there any point why thymeleaf ignores its configuration and wants to resolve all files?
Here is my complete thymeleaf configuration, and as I said this works perfectly if I deploy it on a standalone tomcat.
private ApplicationContext applicationContext;
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
{
this.applicationContext = applicationContext;
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry)
{
String imagesPattern = "/images/**";
String imagesLocation = basePath() + "resources/images/";
registry.addResourceHandler(imagesPattern).addResourceLocations(imagesLocation);
log.info("added resourceHandler (pathPattern: '{}'), (resourceLocation: '{}')",
imagesPattern,
imagesLocation);
String cssPattern = "/css/**";
String cssLocation = basePath() + "resources/css/";
registry.addResourceHandler(cssPattern).addResourceLocations(cssLocation);
log.info("added resourceHandler (pathPattern: '{}'), (resourceLocation: '{}')", cssPattern, cssLocation);
}
#Bean(name = "basepath")
public String basePath()
{
String basepath = "";
File file = new File(Optional.ofNullable(System.getenv("THYMELEAF_APP_RESOURCES"))
.orElse("thymeleaf-resources/"));
if (file.exists())
{
basepath = "file:" + file.getAbsolutePath();
}
if (!basepath.endsWith("/"))
{
basepath += "/";
}
log.info("basepath: {}", basepath);
return basepath;
}
#Bean
#Description("Thymeleaf View Resolver")
public ThymeleafViewResolver viewResolver(String basePath)
{
log.info("setting up Thymeleaf view resolver");
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(templateEngine(basePath));
viewResolver.setCharacterEncoding("UTF-8");
viewResolver.setCache(true);
return viewResolver;
}
public SpringTemplateEngine templateEngine(String basePath)
{
log.info("setting up Thymeleaf template engine.");
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver(basePath));
templateEngine.setEnableSpringELCompiler(true);
return templateEngine;
}
private ITemplateResolver templateResolver(String basePath)
{
log.info("setting up Thymeleaf template resolver");
SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
resolver.setApplicationContext(applicationContext);
resolver.setPrefix(basePath + "thymeleaf/views/");
resolver.setSuffix(".html");
resolver.setTemplateMode(TemplateMode.HTML);
resolver.setCacheable(false);
return resolver;
}
#Bean
public IMessageResolver thymeleafMessageSource(MessageSource messageSource)
{
SpringMessageResolver springMessageResolver = new SpringMessageResolver();
springMessageResolver.setMessageSource(messageSource);
return springMessageResolver;
}
EDIT
I just found that the problem seems to lie much deeper. Having the dependencies of thymeleaf added into my pom.xml seems to be enough for spring boot to load it into the context... I just deleted my ThymeleafConfig class for testing purposes and still thymeleaf tries to resolve the JSF pages... (yes I did maven clean before executing the test)
EDIT 2
I read it now and tried to exclude the ThymeleafAutoConfiguration class but it does not help. My configurations are still overridden. Here is my configuration for this so far. (And yes this is the ONLY EnableAutoConfiguration annotation in the whole project)
#Configuration
#EnableAutoConfiguration(exclude = {ThymeleafAutoConfiguration.class})
#Import({WebAppConfig.class, ThymeleafConfig.class})
public class SpringBootInitializer extends SpringBootServletInitializer
and my ThymeleafConfig class is already added above.

Having the dependencies of thymeleaf added into my pom.xml seems to be enough for spring boot to load it into the context...
If this has surprised you then I would recommend spending some time to take a step back and read about how Spring Boot works and, in particular, it's auto-configuration feature. This section of the reference documentation is a good place to start.
In short, Spring Boot adopts a convention over configuration approach. If a dependency is on the classpath, Spring Boot assumes that you want to use it, and configures it with sensible defaults. This is what it's doing with Thymeleaf. You can disable this auto-configuration for a specific dependency using the excludes attribute on #SpringBootApplication:
#SpringBootApplication(exclude={ThymeleafAutoConfiguration.class})
public class ExampleApplication {
}
You can also use the spring.autoconfigure.exclude property to provide a comma-separated list of auto-configuration classes to exclude. Each entry in the list should be the fully-qualified name of an auto-configuration class. You could use this property with #TestPropertySource to disable auto-configuration on a test-by-test basis.

I have been struggling with a similar issue for hours and finally found out the root cause.
If you have a dependency to *-data-rest in your pom like this:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
you will have to add Thymeleaf to your pom as well even if you use a another template engine (Freemarker, JSP, ...) everywhere else.
Reason: to expose a JpaRepository as a rest service Spring Boot requires Thymeleaf. I do not understand why this is not defined as a dependency of spring-boot-starter-data-rest so that Maven resolves it automatically.
In my opinion it is a Spring Boot configuration bug.

Related

Application failed to start after spring boot version upgrade from 2.1.18.RELEASE to 2.2.0.RELEASE

When we upgrade the spring-boot-starter-parent version from 2.1.8.RELEASE to 2.2.0.RELEASE, the application is not loading few beans. Due to this, application is failing. #PostConstuct is not able to add BCFIPS Provider in security provider.
#Configuration
#Slf4j
#ComponentScan(basePackages = "com.xxx.yyy.ekms.sdk")
#ConditionalOnProperty(name = "ekms.enabled", havingValue = "true")
public class EKMSClientSdkConfiguration extends ClientConfiguration
{
#PostConstruct
public void addSecurityProvider()
{
Security.addProvider(new BouncyCastleFipsProvider());
}
#Bean
public ApiClientBuilder apiClientBuilder()
{
return new DefaultApiClientBuilder();
}
}
Also, apiClientBuilder bean is not getting created.
The EKMSClientSdkConfiguration is extending ClientConfiguration, which is coming as part of another application jar. This class is not having any annotation.
public abstract class ClientConfiguration {
public ClientConfiguration()
{
}
public abstract void addSecurityProvider();
#Bean
public EKMSClient restClient() {
return new EKMSRestClientImpl(this.apiClient());
}
#Bean
public ApiClient apiClient() {
return Configuration.getDefaultApiClient();
}
}
In our case, EKMSClientSdkConfiguration bean is not getting created and the #PostConstruct is also not getting executed.
I went through the Spring Boot 2.2.RELEASE notes which is pointing to Spring Framework 5.2 upgrade guide. Here, I learned that spring boot 2.2.0 RELEASE is using Spring framework 5.2. In Spring framework 5.2, we have many changes.
It looks like this is the root cause of bean not getting loaded, but I am not sure about it.
Any help will be appreciated. Let me know if additional information is needed.
I found spring.main.lazy-initialization=true property in my application which was causing the above issue. When I removed it from the application.properties, This issue is resolved. This is the major change which was introduced in 2.2.0.RELEASE of spring boot

JAX-RS, Spring & ServletConfig: How to access Servlet Config in Configurator

I have troubles getting a javax.servlet.ServletConfig into a class annotated with org.springframework.context.annotation.Configuration.
My team decided that we should use spring for dependency injection and I'm trying to use it to migrate one of our simple Rest services.
My constraints are:
JAX-RS: We have several REST Services implemented JAX-RS and we don't really want to change that.
Not bound to a specific implementation of JAX-RS (Jersey & RESTEasy work fine for us and we can change from one to the other without changing underlying code)
Import as few dependencies as possible from spring: at the moment I import only org.springframework:spring-context from the spring project.
No API breakage: Deprecated is fine but the service should keep working during the transition, using our old way of doing things.
A string parameter is defined in the service's web.xml. I need to get it, instantiate a Bean with it and inject the resulting bean at several place in the code.
I don't want to mess with Spring Boot/MVC/... as the service already works and I just want the Dependency Injection part.
What I already have:
The code use javax.ws.rs.core.Application, with a class that look like that:
public class MyApplication extends Application {
#Context
private ServletConfig cfg;
public DSApplication() {
}
#Override
public Set<Class<?>> getClasses() {
return new HashSet<>();
}
#Override
public Set<Object> getSingletons() {
Set<Object> set = new HashSet<>();
String injectionStr = cfg.getInitParameter("injection");
boolean injection = false;
if (null != injectionStr && !injectionStr.isEmpty()) {
injection = Boolean.valueOf(injectionStr);
}
if (injection) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
DSServiceProducer.class,
CContextBeanProvider.class
);
IDSService service = context.getBean(IDSService.class);
set.add(service);
} else {
set.add(new DSService()); //Old way
}
return set;
}
}
I need the servlet config in CContextBeanProvider, which look like:
#Configuration
public class CContextBeanProvider {
private ServletConfig cfg; // How to get this here ?
#Bean
public CContextBean cContextBean() {
String bean = cfg.getInitParameter("cpuContext");
return new CContextBean(bean);
}
}
CContextBean is a setting bean initialized from a string found in the web.xml of the service.
Is it possible ?
Do you have any idea how ?
Would it be easier with CDI, knowing that we run on base Tomcat ? (I've already find this if I need to use tomcat with CDI)
Could you please try to add all jersey CDI related jars to your applications ?

Spring Boot urls with .html ending

I have very simple hello world created with spring boot (just starter web, no thymeleaf etc.).
I want to handle /hello url and it should produces view from /static/view.html
I have /static/view.html and simple method in my controller:
#RequestMapping("/hello")
public String hello2() {
return "hello.html";
}
The problem is that it causes error:
Circular view path [hello.html]: would dispatch back to the current
handler URL [/hello.html] again
I figured out that it does not matter if I visit /hello or /hello.html, Spring treats them the same.
How can I return simple, static html with the same name as the url path and which object in spring mvc/boot causes mapping url like /example.html to just /example?
You have to perform next steps:
Introduce MVC configuration:
#Configuration
#EnableWebMvc
public class MvcConfiguration extends WebMvcConfigurerAdapter{
#Bean
public ViewResolver getViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/");
return resolver;
}
#Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
Put your hello.html into /src/main/webapp/WEB-INF folder
Make sure you have compile dependencies like this:
compile("org.springframework.boot:spring-boot-starter-web")
compile("org.apache.tomcat.embed:tomcat-embed-jasper")
I posted gradle code, if you have maven use similar xml.
NOTE Step 3 is the most important step because if you're using spring boot it applies auto-configurations depending on what you have in classpath. For instance if you add thymeleaf dependency
compile("org.springframework.boot:spring-boot-starter-thymeleaf")
that will most likelly break the code because thymeleaf will introduce its own auto-configured view resolver.
Delete the method in your controller. A #RequestMapping is not needed for static content.

Spring Boot: Web.xml and embedded server jar

I'm trying to convert a legacy spring-mvc app to Spring boot (in order to have a self contained JAR enabling easier upgrade to Java-8).
I see no reason to use replace my existing web.xml file with code as the code looks like configuration and web.xml is more established.
Is it possible to use my existing web.xml in a Spring Boot application (in embedded JAR mode)?
Edit: I also want to avoid using #EnableAutoConfiguration
Thanks
ok, thanks to Mecon, I'm slightly closer. I had to remove the ContextLoaderListener in the web.xml; also had to import the xml Spring config even though it was referenced in the contextConfigLocation.
#Configuration
#ComponentScan
#EnableAutoConfiguration
#ImportResource(value = {"classpath:/webapp-base.xml"})
public class WebApp {
#Autowired
private ServerProperties serverProperties;
#Autowired
private MediaConfiguration mediaConfig;
#Bean
public EmbeddedServletContainerFactory servletContainer() {
JettyEmbeddedServletContainerFactory factory = new JettyEmbeddedServletContainerFactory();
factory.setContextPath(serverProperties.getContextPath());
factory.addConfigurations(new WebXmlConfiguration());
factory.addServerCustomizers(server -> {
List<Handler> resourceHandlers = getResourceHandlers();
Handler original = server.getHandler();
HandlerList handlerList = new HandlerList();
Handler[] array = getHandlers(original, resourceHandlers);
handlerList.setHandlers(array);
server.setHandler(handlerList);
}
);
return factory;
}
private List<Handler> getResourceHandlers() {
return mediaConfig.getMappings().stream().map(m -> {
ContextHandler contextHandler = new ContextHandler(m.getUrlpath());
ResourceHandler resourceHandler = new ResourceHandler();
resourceHandler.setResourceBase(m.getFilepath());
contextHandler.setHandler(resourceHandler);
return contextHandler;
}).collect(Collectors.toList());
}
private Handler[] getHandlers(Handler original, List<Handler> resourceHandlers) {
ArrayList<Handler> handlers = new ArrayList<>();
handlers.add(original);
handlers.addAll(resourceHandlers);
return handlers.toArray(new Handler[resourceHandlers.size()+1]);
}
public static void main(String[] args) {
SpringApplication.run(WebApp.class, args);
}
}
You don't need Spring-Boot to have a self-contained JAR, all you really need is Embedded Tomcat, or Jetty.
Create a class with public static void main(String[] a), and this Class will be used when the Jar is "executed" by java -jar myWebapp.jar command.
In the main method, you can fire up the Embedded Tomcat or Jetty, and make it load your webapp by referring to existing web.xml.

Deployment of resources on embedded Tomcat server using Spring Boot

I have a project where data from several sources are being processed into some data structures. After the program is done building these structures, I want it to set up a server that enables users to fine-tune these structures manually. I decided that Spring MVC on an embedded Tomcat server set up using Spring Boot is just what I need.
I want to use Thymeleaf as view technology, and therefore did
#Configuration
#ComponentScan
#EnableAutoConfiguration
public class Main {
public static void main(String... args) throws Exception {
// Lots of initialization ...
SpringApplication.run(Main.class, args);
}
#Bean
public ServletContextTemplateResolver templateResolver() {
ServletContextTemplateResolver resolver = new ServletContextTemplateResolver();
resolver.setPrefix("/resources/views/");
resolver.setSuffix(".html");
resolver.setTemplateMode("HTML5");
resolver.setCacheable(false);
return resolver;
}
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine engine = new SpringTemplateEngine();
engine.setTemplateResolver(templateResolver());
return engine;
}
#Bean
public ViewResolver viewResolver() {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(templateEngine());
viewResolver.setOrder(1);
viewResolver.setViewNames(new String[]{"*"});
viewResolver.setCache(false);
return viewResolver;
}
}
and
#Controller
public class WebController {
#RequestMapping(value="/greeting", method=RequestMethod.GET)
public String greeting() {
return "greeting";
}
}
But even though there is a view file at /resources/views/greeting.html, the server's reply to the URL http://localhost:8080/greeting is
org.thymeleaf.exceptions.TemplateInputException: Error resolving template "greeting", template might not exist or might not be accessible by any of the configured Template Resolvers
After stepping through the code in a debugger it appears that at some point, ServletContext, which is supposed to return the view file as a stream, looks in a temporary folder like
C:\Users\Michael\AppData\Local\Temp\tomcat-docbase.971027024999448548.8080
which is empty.
Now I get that I need to either
Have the resources deployed to the temporary folder when the server starts up
Have the server operate in the directory where the resources already are
My problem just is that I don't know how to do either, or which approach is the best. Something tells me that 1 is the better wisdom, but any suggestion is welcome.
Edit
Ok, I ended up with something that seems to work. While Joe's answer definitely helped me get on the way, it also appears that I had to change my Maven configuration in a way that puzzles me.
After putting the template greeting.html into /resources/templates/greeting.html and adding resources to the build path, I got the error
javax.servlet.ServletException: Circular view path [greeting]: would dispatch back to the current handler URL [/word/greeting] again. Check your ViewResolver setup! (Hint: This may be the result of an unspecified view, due to default view name generation.)
In other words, Thymeleaf seemed to not be properly configured. After some fiddling I ended up changing the version of spring-boot-starter-parent in pom.xml from 0.5.0.BUILD-SNAPSHOT to 0.5.0.M6:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<!--<version>0.5.0.BUILD-SNAPSHOT</version>-->
<version>0.5.0.M6</version>
</parent>
and removing the version tag from the Thymeleaf dependency
<dependencies>
<!-- ... -->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring3</artifactId>
<!--<version>${thymeleaf-spring3-version}</version>-->
</dependency>
</dependencies>
After this, it worked.
Can someone please explain why I needed to change the version of spring-boot-starter-parent to be able to remove the version tag from thymeleaf-spring3, and why that was necessary?
The servlet context root is not the best place for templates in an embedded server. There is a way to do it, but if I were you I would go with the flow and use the classpath. If you allow Spring Boot to configure the template resolver (also recommended) then it will look in classpath:/templates by default. There are several samples that use thymeleaf in the Boot code base, so it should be easy to modify one of those if you have different requirements.

Resources