CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-freemarker--freemarker

Apache FreeMarker is a template engine: a Java library to generate text output based on templates and changing data.

Pending
Overview
Eval results
Files

extensions.mddocs/

Extensions and Integration

FreeMarker provides extensive extension capabilities through custom directives, methods, transforms, and integration with external systems like XML/DOM processing and servlet containers.

Custom Directives

Custom directives allow you to extend the FreeMarker template language with new functionality.

interface TemplateDirectiveModel extends TemplateModel {
  void execute(Environment env, Map params, TemplateModel[] loopVars, 
               TemplateDirectiveBody body) throws TemplateException, IOException;
}

// Body interface for directive content
interface TemplateDirectiveBody {
  void render(Writer out) throws TemplateException, IOException;
}

Custom Directive Example

public class RepeatDirective implements TemplateDirectiveModel {
    
    @Override
    public void execute(Environment env, Map params, TemplateModel[] loopVars,
                       TemplateDirectiveBody body) throws TemplateException, IOException {
        
        // Get the 'count' parameter
        TemplateNumberModel countModel = (TemplateNumberModel) params.get("count");
        if (countModel == null) {
            throw new TemplateModelException("Missing required parameter 'count'");
        }
        
        int count = countModel.getAsNumber().intValue();
        
        // Execute the body 'count' times
        for (int i = 0; i < count; i++) {
            // Set loop variable if provided
            if (loopVars.length > 0) {
                loopVars[0] = new SimpleNumber(i);
            }
            if (loopVars.length > 1) {
                loopVars[1] = new SimpleScalar(i % 2 == 0 ? "even" : "odd");
            }
            
            // Render the body
            body.render(env.getOut());
        }
    }
}

// Usage in template:
// <@repeat count=3 ; index, parity>
//   Item ${index} (${parity})
// </@repeat>

Advanced Directive Features

public class ConditionalIncludeDirective implements TemplateDirectiveModel {
    
    @Override
    public void execute(Environment env, Map params, TemplateModel[] loopVars,
                       TemplateDirectiveBody body) throws TemplateException, IOException {
        
        // Get parameters
        TemplateScalarModel templateName = (TemplateScalarModel) params.get("template");
        TemplateBooleanModel condition = (TemplateBooleanModel) params.get("if");
        
        if (templateName == null) {
            throw new TemplateModelException("Missing required parameter 'template'");
        }
        
        // Check condition (default to true if not specified)
        boolean shouldInclude = condition == null || condition.getAsBoolean();
        
        if (shouldInclude) {
            try {
                // Include the specified template
                Template template = env.getConfiguration().getTemplate(templateName.getAsString());
                env.include(template);
            } catch (IOException e) {
                throw new TemplateException("Failed to include template: " + templateName.getAsString(), env, e);
            }
        } else if (body != null) {
            // Render alternative content if condition is false
            body.render(env.getOut());
        }
    }
}

// Usage in template:
// <@conditionalInclude template="admin-menu.ftl" if=user.isAdmin />
// <@conditionalInclude template="special-offer.ftl" if=user.isPremium>
//   <p>Upgrade to premium to see special offers!</p>
// </@conditionalInclude>

Custom Methods

Implement callable methods that can be invoked from templates.

interface TemplateMethodModel extends TemplateModel {
  Object exec(List arguments) throws TemplateModelException;
}

// Enhanced method model with TemplateModel arguments
interface TemplateMethodModelEx extends TemplateMethodModel {
  Object exec(List arguments) throws TemplateModelException;
}

Custom Method Examples

// String manipulation method
public class CapitalizeMethod implements TemplateMethodModelEx {
    @Override
    public Object exec(List arguments) throws TemplateModelException {
        if (arguments.size() != 1) {
            throw new TemplateModelException("capitalize method expects exactly 1 argument");
        }
        
        TemplateScalarModel arg = (TemplateScalarModel) arguments.get(0);
        String str = arg.getAsString();
        
        if (str == null || str.isEmpty()) {
            return new SimpleScalar("");
        }
        
        return new SimpleScalar(Character.toUpperCase(str.charAt(0)) + str.substring(1).toLowerCase());
    }
}

// Math utility method
public class MaxMethod implements TemplateMethodModelEx {
    @Override
    public Object exec(List arguments) throws TemplateModelException {
        if (arguments.size() < 2) {
            throw new TemplateModelException("max method expects at least 2 arguments");
        }
        
        double max = Double.NEGATIVE_INFINITY;
        for (Object arg : arguments) {
            TemplateNumberModel num = (TemplateNumberModel) arg;
            double value = num.getAsNumber().doubleValue();
            if (value > max) {
                max = value;
            }
        }
        
        return new SimpleNumber(max);
    }
}

// Date formatting method
public class FormatDateMethod implements TemplateMethodModelEx {
    @Override
    public Object exec(List arguments) throws TemplateModelException {
        if (arguments.size() != 2) {
            throw new TemplateModelException("formatDate method expects exactly 2 arguments: date and pattern");
        }
        
        TemplateDateModel dateModel = (TemplateDateModel) arguments.get(0);
        TemplateScalarModel patternModel = (TemplateScalarModel) arguments.get(1);
        
        Date date = dateModel.getAsDate();
        String pattern = patternModel.getAsString();
        
        SimpleDateFormat formatter = new SimpleDateFormat(pattern);
        return new SimpleScalar(formatter.format(date));
    }
}

// Usage in templates:
// ${capitalize("hello world")} -> "Hello world"
// ${max(10, 20, 15, 30)} -> 30
// ${formatDate(currentDate, "yyyy-MM-dd")} -> "2024-03-15"

Text Transforms

Transform template content through custom processing.

interface TemplateTransformModel extends TemplateModel {
  Writer getWriter(Writer out, Map args) throws TemplateModelException, IOException;
}

Built-in Utility Transforms

FreeMarker provides several built-in utility transforms for common text processing tasks:

// HTML escaping transform
class HtmlEscape implements TemplateTransformModel {
  HtmlEscape();
  Writer getWriter(Writer out, Map args) throws TemplateModelException, IOException;
}

// XML escaping transform
class XmlEscape implements TemplateTransformModel {
  XmlEscape();
  Writer getWriter(Writer out, Map args) throws TemplateModelException, IOException;
}

// Normalize newlines transform  
class NormalizeNewlines implements TemplateTransformModel {
  NormalizeNewlines();
  Writer getWriter(Writer out, Map args) throws TemplateModelException, IOException;
}

// Capture output transform
class CaptureOutput implements TemplateTransformModel {
  CaptureOutput();
  Writer getWriter(Writer out, Map args) throws TemplateModelException, IOException;
}

Usage:

Configuration cfg = new Configuration(Configuration.VERSION_2_3_34);

// Register built-in transforms
cfg.setSharedVariable("htmlEscape", new HtmlEscape());
cfg.setSharedVariable("xmlEscape", new XmlEscape());
cfg.setSharedVariable("normalizeNewlines", new NormalizeNewlines());
cfg.setSharedVariable("captureOutput", new CaptureOutput());

Template usage:

<#-- HTML escaping -->
<@htmlEscape>
  <p>This & that will be escaped: <b>bold</b></p>
</@htmlEscape>

<#-- XML escaping -->
<@xmlEscape>
  <data value="quotes & brackets < > will be escaped"/>
</@xmlEscape>

<#-- Normalize line endings -->
<@normalizeNewlines>
  Text with mixed
  line endings
</@normalizeNewlines>

Transform Examples

// Upper case transform
public class UpperCaseTransform implements TemplateTransformModel {
    @Override
    public Writer getWriter(Writer out, Map args) throws TemplateModelException, IOException {
        return new FilterWriter(out) {
            @Override
            public void write(char[] cbuf, int off, int len) throws IOException {
                String str = new String(cbuf, off, len);
                out.write(str.toUpperCase());
            }
            
            @Override
            public void write(String str) throws IOException {
                out.write(str.toUpperCase());
            }
        };
    }
}

// HTML escaping transform
public class HtmlEscapeTransform implements TemplateTransformModel {
    @Override
    public Writer getWriter(Writer out, Map args) throws TemplateModelException, IOException {
        return new FilterWriter(out) {
            @Override
            public void write(String str) throws IOException {
                out.write(escapeHtml(str));
            }
            
            private String escapeHtml(String str) {
                return str.replace("&", "&amp;")
                         .replace("<", "&lt;")
                         .replace(">", "&gt;")
                         .replace("\"", "&quot;")
                         .replace("'", "&#39;");
            }
        };
    }
}

// Usage in templates:
// <@upperCase>hello world</@upperCase> -> HELLO WORLD
// <@htmlEscape><script>alert('xss')</script></@htmlEscape> -> &lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt;

XML and DOM Integration

Node Model for DOM Processing

class NodeModel implements TemplateNodeModel, TemplateHashModel, TemplateSequenceModel, TemplateScalarModel {
  // Factory methods
  static NodeModel wrap(Node node);
  static NodeModel parse(InputSource is) throws SAXException, IOException, ParserConfigurationException;
  static NodeModel parse(File f) throws SAXException, IOException, ParserConfigurationException;
  static NodeModel parse(String xml) throws SAXException, IOException, ParserConfigurationException;
  
  // Node access
  Node getNode();
  String getNodeName() throws TemplateModelException;
  String getNodeType() throws TemplateModelException;
  TemplateNodeModel getParentNode() throws TemplateModelException;
  TemplateSequenceModel getChildNodes() throws TemplateModelException;
  
  // Hash model implementation
  TemplateModel get(String key) throws TemplateModelException;
  boolean isEmpty() throws TemplateModelException;
  
  // Sequence model implementation  
  TemplateModel get(int index) throws TemplateModelException;
  int size() throws TemplateModelException;
  
  // Scalar model implementation
  String getAsString() throws TemplateModelException;
  
  // XPath support
  TemplateModel exec(List arguments) throws TemplateModelException;
}

DOM Processing Examples

// Parse XML and make available to template
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new File("data.xml"));

Map<String, Object> dataModel = new HashMap<>();
dataModel.put("doc", NodeModel.wrap(doc));

// Template usage:
// ${doc.documentElement.nodeName} -> root element name
// <#list doc.getElementsByTagName("item") as item>
//   ${item.textContent}
// </#list>

// XPath queries:
// ${doc["//item[@id='123']"].textContent}
// <#assign items = doc["//item[position() <= 5]"]>

XML Namespace Support

public class NamespaceAwareNodeModel extends NodeModel {
    private final Map<String, String> namespaces;
    
    public NamespaceAwareNodeModel(Node node, Map<String, String> namespaces) {
        super(node);
        this.namespaces = namespaces;
    }
    
    // XPath with namespace support
    @Override
    public TemplateModel exec(List arguments) throws TemplateModelException {
        // Configure XPath with namespace context
        XPathFactory xpathFactory = XPathFactory.newInstance();
        XPath xpath = xpathFactory.newXPath();
        xpath.setNamespaceContext(new SimpleNamespaceContext(namespaces));
        
        // Execute XPath query
        String expression = ((TemplateScalarModel) arguments.get(0)).getAsString();
        // ... XPath execution logic
        
        return super.exec(arguments);
    }
}

Bean Integration Extensions

Static Models Access

Access static methods and fields from templates:

class StaticModels implements TemplateHashModel {
  StaticModels(BeansWrapper wrapper);
  TemplateModel get(String key) throws TemplateModelException;
  boolean isEmpty() throws TemplateModelException;
}

// Individual static model for a class
class StaticModel implements TemplateHashModel, TemplateScalarModel {
  StaticModel(Class clazz, BeansWrapper wrapper);
  TemplateModel get(String key) throws TemplateModelException;
  boolean isEmpty() throws TemplateModelException;
  String getAsString() throws TemplateModelException;
}

Usage example:

BeansWrapper wrapper = new BeansWrapper(Configuration.VERSION_2_3_34);
Map<String, Object> dataModel = new HashMap<>();
dataModel.put("statics", wrapper.getStaticModels());

// Template usage:
// ${statics["java.lang.Math"].PI} -> 3.141592653589793
// ${statics["java.lang.System"].currentTimeMillis()} -> current timestamp
// ${statics["java.util.UUID"].randomUUID()} -> random UUID

Enum Models Access

Access enum constants from templates:

class EnumModels implements TemplateHashModel {
  EnumModels(BeansWrapper wrapper);
  TemplateModel get(String key) throws TemplateModelException;
  boolean isEmpty() throws TemplateModelException;
}

Usage example:

Map<String, Object> dataModel = new HashMap<>();
dataModel.put("enums", wrapper.getEnumModels());

// Template usage:
// ${enums["java.time.DayOfWeek"].MONDAY} -> MONDAY
// ${enums["java.util.concurrent.TimeUnit"].SECONDS} -> SECONDS

Servlet Integration

Servlet-specific Extensions

// Servlet context integration
class ServletContextHashModel implements TemplateHashModel {
  ServletContextHashModel(ServletContext sc, ObjectWrapper wrapper);
  TemplateModel get(String key) throws TemplateModelException;
  boolean isEmpty() throws TemplateModelException;
}

// HTTP request integration  
class HttpRequestHashModel implements TemplateHashModel {
  HttpRequestHashModel(HttpServletRequest request, ObjectWrapper wrapper);
  TemplateModel get(String key) throws TemplateModelException;
  boolean isEmpty() throws TemplateModelException;
}

// HTTP session integration
class HttpSessionHashModel implements TemplateHashModel {
  HttpSessionHashModel(HttpSession session, ObjectWrapper wrapper);
  TemplateModel get(String key) throws TemplateModel;
  boolean isEmpty() throws TemplateModelException;
}

Servlet Usage Example

// In a servlet
protected void doGet(HttpServletRequest request, HttpServletResponse response) 
    throws ServletException, IOException {
    
    Configuration cfg = getConfiguration();
    
    Map<String, Object> dataModel = new HashMap<>();
    dataModel.put("request", new HttpRequestHashModel(request, cfg.getObjectWrapper()));
    dataModel.put("session", new HttpSessionHashModel(request.getSession(), cfg.getObjectWrapper()));
    dataModel.put("application", new ServletContextHashModel(getServletContext(), cfg.getObjectWrapper()));
    
    Template template = cfg.getTemplate("page.ftl");
    response.setContentType("text/html");
    template.process(dataModel, response.getWriter());
}

// Template usage:
// ${request.requestURI} -> current request URI
// ${session.id} -> session ID
// ${application.serverInfo} -> server information

JSR-223 Scripting Integration

class FreeMarkerScriptEngine extends AbstractScriptEngine {
  FreeMarkerScriptEngine();
  FreeMarkerScriptEngine(Configuration config);
  
  Object eval(String script, ScriptContext context) throws ScriptException;
  Object eval(Reader reader, ScriptContext context) throws ScriptException;
  Bindings createBindings();
  ScriptEngineFactory getFactory();
}

class FreeMarkerScriptEngineFactory implements ScriptEngineFactory {
  String getEngineName();
  String getEngineVersion();
  List<String> getExtensions();
  List<String> getMimeTypes();
  List<String> getNames();
  String getLanguageName();
  String getLanguageVersion();
  ScriptEngine getScriptEngine();
}

Extension Registration

Global Extension Registration

Configuration cfg = new Configuration(Configuration.VERSION_2_3_34);

// Register custom directives
cfg.setSharedVariable("repeat", new RepeatDirective());
cfg.setSharedVariable("conditionalInclude", new ConditionalIncludeDirective());

// Register custom methods
cfg.setSharedVariable("capitalize", new CapitalizeMethod());
cfg.setSharedVariable("max", new MaxMethod());
cfg.setSharedVariable("formatDate", new FormatDateMethod());

// Register transforms
cfg.setSharedVariable("upperCase", new UpperCaseTransform());
cfg.setSharedVariable("htmlEscape", new HtmlEscapeTransform());

// Register utility objects
cfg.setSharedVariable("statics", wrapper.getStaticModels());
cfg.setSharedVariable("enums", wrapper.getEnumModels());

Per-Template Extension Registration

Map<String, Object> dataModel = new HashMap<>();

// Add custom extensions for specific templates
dataModel.put("xmlUtils", new XmlUtilityMethods());
dataModel.put("dateUtils", new DateUtilityMethods());
dataModel.put("stringUtils", new StringUtilityMethods());

Template template = cfg.getTemplate("advanced.ftl");
template.process(dataModel, out);

Extension Best Practices

Error Handling in Extensions

public class SafeDirective implements TemplateDirectiveModel {
    @Override
    public void execute(Environment env, Map params, TemplateModel[] loopVars,
                       TemplateDirectiveBody body) throws TemplateException, IOException {
        try {
            // Directive implementation
            doExecute(env, params, loopVars, body);
        } catch (Exception e) {
            // Log error for debugging
            logger.error("Error in SafeDirective", e);
            
            // Provide meaningful error message
            throw new TemplateException("SafeDirective failed: " + e.getMessage(), env, e);
        }
    }
}

Parameter Validation

public class ValidatedMethod implements TemplateMethodModelEx {
    @Override
    public Object exec(List arguments) throws TemplateModelException {
        // Validate argument count
        if (arguments.size() != 2) {
            throw new TemplateModelException(
                String.format("Expected 2 arguments, got %d", arguments.size()));
        }
        
        // Validate argument types
        if (!(arguments.get(0) instanceof TemplateScalarModel)) {
            throw new TemplateModelException("First argument must be a string");
        }
        
        if (!(arguments.get(1) instanceof TemplateNumberModel)) {
            throw new TemplateModelException("Second argument must be a number");
        }
        
        // Proceed with implementation
        return doExec(arguments);
    }
}

Thread Safety Considerations

// Thread-safe extension (no mutable state)
public class ThreadSafeMethod implements TemplateMethodModelEx {
    @Override
    public Object exec(List arguments) throws TemplateModelException {
        // Use only local variables and immutable objects
        String input = ((TemplateScalarModel) arguments.get(0)).getAsString();
        return new SimpleScalar(processInput(input));
    }
    
    private String processInput(String input) {
        // Thread-safe processing logic
        return input.toUpperCase();
    }
}

// For stateful extensions, use ThreadLocal or synchronization
public class StatefulMethod implements TemplateMethodModelEx {
    private final ThreadLocal<SomeState> state = new ThreadLocal<SomeState>() {
        @Override
        protected SomeState initialValue() {
            return new SomeState();
        }
    };
    
    @Override
    public Object exec(List arguments) throws TemplateModelException {
        SomeState currentState = state.get();
        // Use currentState safely
        return processWithState(currentState, arguments);
    }
}

Install with Tessl CLI

npx tessl i tessl/maven-org-freemarker--freemarker

docs

caching-loading.md

core-processing.md

exception-handling.md

extensions.md

index.md

object-wrapping.md

output-formats.md

template-models.md

tile.json