Thymeleaf 3 Upgrade - fails to find templates from classpath

classic Classic list List threaded Threaded
3 messages Options
Reply | Threaded
Open this post in threaded view
|

Thymeleaf 3 Upgrade - fails to find templates from classpath

xtian

I just upgraded from Thymeleaf 2 to 3.0.1.RELEASE and my code stopped working.

I have many template resolvers configured in the WebMvcConfigurerAdapter. One loads templates from the servlet context under "/WEB-INF/views", another loads them from the classpath under "net/somepackage/views"

I use a th:replace attribute to insert a fragment that should be loaded from the classpath but I get a FileNotFoundException because the fragment is being loaded from the servlet context instead, like if I never configured the other template resolver.

The WebMvcConfigurerAdapter:


	@Bean
	public ITemplateResolver webTemplateResolver() {
		SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
 	        resolver.setApplicationContext(applicationContext);
		resolver.setPrefix("/WEB-INF/views");
		resolver.setSuffix(".html");
		resolver.setCharacterEncoding("UTF-8");
		resolver.setTemplateMode(TemplateMode.HTML);
		resolver.setCacheable(config.isProductionEnvironment());
		resolver.setOrder(1);
		return resolver;
	}

	@Bean
	public ClassLoaderTemplateResolver clTemplateResolver() {
		ClassLoaderTemplateResolver resolver = new ClassLoaderTemplateResolver();
		resolver.setPrefix("net/somepackage/views");
		resolver.setSuffix(".html");
		resolver.setCharacterEncoding("UTF-8");
		resolver.setTemplateMode(TemplateMode.HTML);
		resolver.setCacheable(config.isProductionEnvironment());
		resolver.setOrder(3); 
		return resolver;
	}

The /WEB-INF/views/home.html template:

	<!DOCTYPE html>
	...
	<div th:replace="~{/common/form/text::body(fieldName='email')}"></div>
	...

The "/common/form/text.html" fragment is located in "net/somepackage/views" but Thymeleaf makes no attempt at looking for it in the classpath. I get the following error:

    Caused by: java.io.FileNotFoundException: Could not open ServletContext resource [/WEB-INF/views/common/form/text.html]

Is there a different way to specify alternative template resolvers now?

As a side note: if I use a trailing slash when defining the template resolver prefix, like in "/WEB-INF/views/", which is what used to work, the exception shows a double slash in the path, so I removed it.

Reply | Threaded
Open this post in threaded view
|

Re: Thymeleaf 3 Upgrade - fails to find templates from classpath

danielfernandez
Administrator
Did that exact code work for you in that scenario using 2.1? Or in 2.1 you were using a ServletContextTemplateResolver, which now in 3.0.1 you converted into a SpringResourceTemplateResolver (which is a good thing to do anyway)?

Anyway, this worked for you before because you were benefitting from an implementation detail: the fact that the resource resolvers used by your template resolvers with order < 3 were actually able to detect whether a resource existed or not at resolution time, something they were not required to do by spec.

In order to fully determine which templates a template resolver should be resolving without the need to depend on specific implementations, you should be using the "resolvablePatterns" properties of your template resolver objects.

But also, since 3.0 you can use the new "checkExistence" property in your template resolvers (including the SpringResourceTemplateResolver), which will allow you to instruct the resolver to check whether a resource actually exists or not before really reading it. At the expense of maybe performing two read operations on the same resource, which might be inconvenient for some scenarios — or more specifically, for some types of resources.
Reply | Threaded
Open this post in threaded view
|

Re: Thymeleaf 3 Upgrade - fails to find templates from classpath

xtian
Yes I was using a ServletContextTemplateResolver that I converted following the migration guidelines.

As by your suggestion, I added the following lines to the webTemplateResolver() in order to exclude templates prefixed with "/common/" from it:

                Set<String> patterns = new HashSet<>();
                patterns.add("^(?!/common/).*"); // Do not start with "/common/"
                resolver.setResolvablePatterns(patterns);

It doesn't work and when debugging PatternSpec it shows that the regexp gets converted to "^^\(\?!/common/\)\.(?:.*?)$", which doesn't match anything.

Looking at the source I see that PatternUtils is escaping all special characters, so I guess it doesn't accept a regexp and I'm out of luck:

    public static Pattern strPatternToPattern(final String pattern) {
        final String pat =
            pattern.replace(".", "\\.").replace("(", "\\(").replace(")", "\\)").
                replace("[","\\[").replace("]","\\]").replace("?","\\?").replace("$","\\$").replace("+","\\+").
                replace("*","(?:.*?)");
        return Pattern.compile('^' + pat + '$');
    }

I will now try by reversing the logic so that the clTemplateResolver() is executed first and I don't need such a complex pattern...