I'm working on a Spring Boot project where I use Freemarker for templating. I have configured multiple TemplateLoaders in Freemarker's configuration to load templates from two different CDN URLs: a baselineUrl and a dynamicUrl. The idea is that if a template is not found in the baselineUrl, it should fall back to the dynamicUrl.
Here is my configuration :
@Slf4j
@Configuration
@ConfigurationProperties("email")
@Data
public class FtlConfiguration {
// https://dynamic/bhnpay-static/notification-template/ftl
private String baselineUrl;
// https://baseline/bhnpay-static/notification-template/ftl
private String dynamicUrl;
private long retentionTimeMillis;
@Bean("emailConfiguration")
public freemarker.template.Configuration createFtlConfigBean() throws IOException {
freemarker.template.Configuration cfg = new freemarker.template.Configuration(
freemarker.template.Configuration.VERSION_2_3_31);
cfg.setDefaultEncoding(NotificationConstants.UTF_8);
cfg.setLogTemplateExceptions(false);
cfg.setWrapUncheckedExceptions(true);
cfg.setFallbackOnNullLoopVariable(false);
cfg.setLocalizedLookup(false);
List<TemplateLoader> loaders = new ArrayList<>();
// CloudTemplateLoader is just a wrapper on UrlTemplateLoader
loaders.add(new CloudTemplateLoader(new URL(baselineUrl)));
loaders.add(new CloudTemplateLoader(new URL(dynamicUrl)));
cfg.setTemplateLoader(
new MultiTemplateLoader(loaders.toArray(new TemplateLoader[0])));
cfg.setCacheStorage(new MruCacheStorage(5000, Integer.MAX_VALUE));
cfg.setTemplateUpdateDelayMilliseconds(retentionTimeMillis);
return cfg;
}
}
Below is how i am trying to load template :
@Component
@Slf4j
public class FtlTemplateHandler {
private final Configuration configuration;
@Autowired
public FtlTemplateHandler(@Qualifier("emailConfiguration") Configuration configuration) {
this.configuration = configuration;
}
public String getTemplateAsHtml(String emailTemplateUrl) {
try {
Template template = configuration.getTemplate(emailTemplateUrl);
// Process the template
} catch (IOException e) {
try {
log.info("removing template from cache {}", emailTemplateUrl);
configuration.removeTemplateFromCache(emailTemplateUrl);
Thread.sleep(10000);
} catch (IOException | InterruptedException ex) {
// process exception
}
}
}
}
My cloud template loader :
@Slf4j
public class CloudTemplateLoader extends URLTemplateLoader {
private URL root;
public CloudTemplateLoader(URL root) {
super();
this.root = root;
}
public URL getRoot() {
return root;
}
public void setRoot(URL root) {
this.root = root;
}
@Override
protected URL getURL(String template) {
try {
return new URL(root, template);
} catch (MalformedURLException e) {
log.error("Error while loading templates from cloud.");
}
return null;
}
}
Getting below error :
Caused by: java.io.IOException: Server returned HTTP response code: 403 for URL: https://baseline/bhnpay-static/notification-template/ftl/en-US/b9b09aa4-fd39-4812-924e-1fe7f6d4ed51.ftl
at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:2000)
at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1589)
at java.base/sun.net.www.protocol.http.HttpURLConnection.getHeaderField(HttpURLConnection.java:3256)
at java.base/java.net.HttpURLConnection.getHeaderFieldDate(HttpURLConnection.java:601)
at java.base/java.net.URLConnection.getLastModified(URLConnection.java:567)
at java.base/sun.net.www.protocol.https.HttpsURLConnectionImpl.getLastModified(HttpsURLConnectionImpl.java:392)
at freemarker.cache.URLTemplateSource.lastModified(URLTemplateSource.java:94)
at freemarker.cache.URLTemplateLoader.getLastModified(URLTemplateLoader.java:50)
at freemarker.cache.MultiTemplateLoader$MultiSource.getLastModified(MultiTemplateLoader.java:142)
at freemarker.cache.MultiTemplateLoader.getLastModified(MultiTemplateLoader.java:99)
at freemarker.cache.TemplateCache.getTemplateInternal(TemplateCache.java:439)
but the template is present at the dynamic url, that means after looking at baseline cdn it is not checking the dynamic cdn.
I have verified that the URLs are correct and accessible. I tried clearing the cache to ensure it's not a caching issue, but the fallback still doesn't work.
Validating the connection to the url before returning it worked for me actually.
@Override
protected URL getURL(String template) {
try {
URL url = new URL(root, template);
if (isTemplateAvailable(url)) {
return url;
} else {
log.warn("Template not found or inaccessible at URL: {}", url);
}
} catch (MalformedURLException e) {
log.error("Malformed URL when loading template {}: ", template, e);
} catch (IOException e) {
log.error("IOException when checking template availability: ", e);
}
return null;
}
private boolean isTemplateAvailable(URL url) throws IOException {
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("HEAD");
connection.connect();
int responseCode = connection.getResponseCode();
connection.disconnect();
return responseCode == HttpURLConnection.HTTP_OK;
}