Search code examples
spring-bootfreemarker

Unable to import a .ftl file inside my .ftl file - freemarker


i am trying to templatise my java class using freemarker.

My config bean is :

  @Bean
  public FreeMarkerConfigurer freeMarkerConfigurer() {
    FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
        String[] paths = Stream.of(
                "classpath:/templates",
            "classpath:/templates/dao/",
            "classpath:/templates/pom/",
            "classpath:/common/")
        .map(path -> path.replace("/", File.separator).replace("\\", File.separator))
        .toArray(String[]::new);
    configurer.setTemplateLoaderPaths(paths);
    configurer.setDefaultEncoding("UTF-8");
    configurer.setPostTemplateLoaders();
    return configurer;
  }

my template file Type.ftl :

<#import "/common/functions.ftl" as function>
<#assign className = "${data.className}">
<#assign typeName = "${function.camelToSnakeCase(data.className)}">
<#assign packagePath = structure.packagePathPrefix + "." + structure.module?replace("-", ".") + ".core.dao.udt">

package ${packagePath};



@Data
@AllArgsConstructor
@NoArgsConstructor(force = true)
@Builder(toBuilder = true)
@UserDefinedType("${typeName}")
public class ${className} {

<#list data.columns as column>
  @Column("${column.name}")
  private ${column.dataType} ${column.name?uncap_first};

</#list>
}

My ftl file in resouces

it works if i refer my function.ftl from currect directory but fails when i refer it from the commons/functions.ftl. i have to use my common functions from /common/functions.ftl so that all templates can use the common functions. But my template gives the following error :

2024-02-27T14:44:54.793-06:00 ERROR 58803 --- [nio-8090-exec-1] freemarker.runtime                       : Error executing FreeMarker template

freemarker.core._MiscTemplateException: Template importing failed (for parameter value "/common/functions.ftl"):
Template not found for name "common/functions.ftl".
The name was interpreted by this TemplateLoader: MultiTemplateLoader(loader1 = org.springframework.ui.freemarker.SpringTemplateLoader@4f1fb188, loader2 = ClassTemplateLoader(resourceLoaderClass=org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer, basePackagePath="" /* relatively to resourceLoaderClass pkg */)).

----
FTL stack trace ("~" means nesting-related):
    - Failed at: #import "/common/functions.ftl" as fu...  [in template "dao/udt/Type.ftl" at line 3, column 1]
----
    at freemarker.core.LibraryLoad.accept(LibraryLoad.java:67) ~[freemarker-2.3.32.jar:2.3.32]
    at freemarker.core.Environment.visit(Environment.java:335) ~[freemarker-2.3.32.jar:2.3.32]
    at freemarker.core.Environment.visit(Environment.java:341) ~[freemarker-2.3.32.jar:2.3.32]
    at freemarker.core.Environment.process(Environment.java:314) ~[freemarker-2.3.32.jar:2.3.32]
    at freemarker.template.Template.process(Template.java:383) ~[freemarker-2.3.32.jar:2.3.32]
    at org.springframework.ui.freemarker.FreeMarkerTemplateUtils.processTemplateIntoString(FreeMarkerTemplateUtils.java:50) ~[spring-context-support-6.1.3.jar:6.1.3]

i tried relative path like :

<#import "../../../common/functions.ftl" as function>

but u get error again :

2024-02-27T14:55:44.552-06:00 ERROR 60233 --- [nio-8090-exec-1] freemarker.runtime                       : Error executing FreeMarker template

freemarker.core._MiscTemplateException: Template importing failed (for parameter value "../../../common/functions.ftl"):
Template not found for name "dao/udt/../../../common/functions.ftl".
Reason given: Backing out from the root directory is not allowed.
The name was interpreted by this TemplateLoader: MultiTemplateLoader(loader1 = FileTemplateLoader(baseDir="/Users/rahulverma/git/by-rahul/one-click-code/template-services-code-core/target/classes/templates", canonicalBasePath="/Users/rahulverma/git/by-rahul/one-click-code/template-services-code-core/target/classes/templates/"), loader2 = FileTemplateLoader(baseDir="/Users/rahulverma/git/by-rahul/one-click-code/template-services-code-core/target/classes/templates/dao", canonicalBasePath="/Users/rahulverma/git/by-rahul/one-click-code/template-services-code-core/target/classes/templates/dao/"), loader3 = FileTemplateLoader(baseDir="/Users/rahulverma/git/by-rahul/one-click-code/template-services-code-core/target/classes/templates/pom", canonicalBasePath="/Users/rahulverma/git/by-rahul/one-click-code/template-services-code-core/target/classes/templates/pom/"), loader4 = FileTemplateLoader(baseDir="/Users/rahulverma/git/by-rahul/one-click-code/template-services-code-core/target/classes/common", canonicalBasePath="/Users/rahulverma/git/by-rahul/one-click-code/template-services-code-core/target/classes/common/"), loader5 = ClassTemplateLoader(resourceLoaderClass=org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer, basePackagePath="" /* relatively to resourceLoaderClass pkg */)).

appreciate any thoughts on how to resolve this?

edit :

enter image description here i have this final multi module project(in cyan lines) in which i collect template path of each module as a bean(pink lines) and finally inject it in the configuration. (pink lines)

i am expecting the function.ftl template having functions/macro to import in EntityTemplate.ftl. (yellow lines) please note that the function.ftl is altogether in a different package than EntityTemplate.ftl. Is this functionality achievable in freeMarker that i can import common functions in my other modules ? if yes how can i achieve this ? i tries giving a template /common in the classpath so that template engine can search it but it could not and throw a template not found exception . any thoughts on how to achieve this ? currently i have to add functions.ftl to each directory as it is only able to discover the .ftl files locally. enter image description here


Solution

  • With "classpath:/templates", "classpath:/templates/dao/", "classpath:/templates/pom/", "classpath:/common/", you specify template root directories. That is all files inside "dao" will appear on the top level as far as FreeMarker templates see it.

    So you should simply have "classpath:/templates", and then put commons inside that. Of course you can place commons elsewhere too, but then you have to give its parent directory in the template loader path.

    Tip: You can use ${current_template_name} to understand what the current template name (path) is.