001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.rest.server.view.freemarker;
018
019import java.io.*;
020import java.util.*;
021import java.util.concurrent.*;
022
023import org.apache.juneau.http.*;
024import org.apache.juneau.http.response.*;
025import org.apache.juneau.rest.server.*;
026import org.apache.juneau.rest.server.view.*;
027
028import freemarker.template.*;
029
030/**
031 * Mixin that wires Apache FreeMarker view-rendering onto any Juneau REST resource.
032 *
033 * <p>
034 * Compose into a host resource via
035 * {@link Rest#mixins() @Rest(mixins=FreemarkerMixin.class)}; the host then:
036 *
037 * <ol class='spaced-list'>
038 *    <li>Gains a default mount at {@code /freemarker/*} that serves raw FreeMarker templates from
039 *       the importer's classpath by asking the configured {@link Configuration} for the named
040 *       template and rendering it with an empty data model (raw render path; callers who want
041 *       attributes use {@link FreemarkerView} from a typed handler instead).
042 *    <li>Registers {@link FreemarkerViewRenderer} for the mixin's <b>own</b> endpoints (e.g. the
043 *       {@code /freemarker/*} route above) via the mixin's own
044 *       {@link Rest#responseProcessors() @Rest(responseProcessors=...)} declaration.
045 * </ol>
046 *
047 * <h5 class='section'>Auto-wiring the renderer into the host's own endpoints:</h5>
048 *
049 * <p>
050 * A bare {@code @Rest(mixins=FreemarkerMixin.class)} is enough &mdash; a host whose own {@code @RestOp} method
051 * returns {@link FreemarkerView} renders it through the FreeMarker engine with no extra wiring. That works
052 * because {@code FreemarkerMixin} declares
053 * {@link Rest#mergeResponseProcessorsIntoHost() @Rest(mergeResponseProcessorsIntoHost=true)} on its own class,
054 * so the framework folds {@code FreemarkerMixin}'s {@link FreemarkerViewRenderer} into the host's own
055 * response-processor chain automatically.
056 *
057 * <p>
058 * This fold is per-mixin, opt-in, and response-processor-scoped: it happens only because
059 * {@code FreemarkerMixin} declares the opt-in (a mixin that does not is still scoped to its own endpoints, the
060 * standard {@link Rest#mixins() @Rest(mixins=...)} rule), and it folds only the renderer &mdash; never
061 * {@code guards}, {@code serializers}, or any other list-shaped attribute.
062 *
063 * <p>
064 * Two explicit wirings remain supported &mdash; useful when you want to be explicit, or to fold more than the
065 * renderer:
066 *
067 * <ol class='spaced-list'>
068 *    <li><b>{@link Mixin#mergeIntoHost() @Mixin(mergeIntoHost=true)}</b> via the rich {@link Rest#mixinDefs()
069 *       mixinDefs} form &mdash; folds <b>all</b> of {@code FreemarkerMixin}'s list-shaped {@code @Rest}
070 *       attributes into the host's own chain, not just the renderer.
071 *    <li><b>List {@link FreemarkerViewRenderer FreemarkerViewRenderer.class} explicitly</b> in the host's own
072 *       {@code @Rest(responseProcessors=...)} &mdash; the fully manual equivalent.
073 * </ol>
074 *
075 * <p>
076 * The {@link org.apache.juneau.rest.server.processor.ResponseProcessorList} partition pass repositions
077 * {@link FreemarkerViewRenderer} (a
078 * {@link ViewRenderer ViewRenderer}) ahead of
079 * {@code SerializedPojoProcessor} in every case, so the {@link FreemarkerView} bean is dispatched to the
080 * FreeMarker engine rather than bean-serialized.
081 *
082 * <h5 class='figure'>Composition example (microservice):</h5>
083 *
084 * <p class='bjava'>
085 *    <ja>@Rest</ja>(path=<js>"/app"</js>, mixins=FreemarkerMixin.<jk>class</jk>)
086 *    <jk>public class</jk> AppResource <jk>extends</jk> RestServlet {
087 *
088 *       <ja>@Bean</ja> FreemarkerMixin freemarker() {
089 *          <jk>return</jk> FreemarkerMixin.<jsm>create</jsm>()
090 *             .basePath(<js>"/templates/"</js>)
091 *             .templateSuffix(<js>".ftlh"</js>)
092 *             .build();
093 *       }
094 *
095 *       <ja>@RestGet</ja>(<js>"/hello/{name}"</js>)
096 *       <jk>public</jk> View hello(<ja>@Path</ja> String <jv>name</jv>) {
097 *          <jk>return</jk> FreemarkerView.<jsm>of</jsm>(<js>"hello"</js>).attr(<js>"name"</js>, <jv>name</jv>);
098 *       }
099 *    }
100 * </p>
101 *
102 * <h5 class='section'>Configurable mount path:</h5>
103 *
104 * <p>
105 * The default mount {@code /freemarker/*} can be overridden via the SVL variable
106 * {@code ${juneau.freemarker.path:freemarker}} &mdash; set via system property
107 * ({@code -Djuneau.freemarker.path=views}), environment variable
108 * ({@code JUNEAU_FREEMARKER_PATH=views}), or {@code Config} key
109 * ({@code juneau.freemarker.path = views}) to change the runtime mount without subclassing.
110 * Resolution happens once at {@link RestContext} construction time (SVL resolution in {@code @RestOp(path)}).
111 *
112 * <h5 class='section'>Mixin-only deployment:</h5>
113 *
114 * <p>
115 * This resource is designed for composition via {@code @Rest(mixins=...)}. The mount path is
116 * pinned at the op level by
117 * {@link RestGet @RestGet(path="/&#123;juneau.freemarker.path:freemarker&#125;/*")} on {@link #render};
118 * a class-level {@code @Rest(paths=...)} declaration would be silently ignored under the mixin
119 * pattern (see {@link Rest#paths() @Rest(paths)} Javadoc).
120 *
121 * <h5 class='section'>Choosing a Configuration:</h5>
122 *
123 * <p>
124 * The {@code juneau-rest-server-view-freemarker} module ships <b>only</b>
125 * {@code org.freemarker:freemarker} in {@code provided} scope. <b>No configuration bean is
126 * bundled.</b> The bridge resolves a {@link Configuration} at first use from three sources, in
127 * order:
128 *
129 * <ul class='spaced-list'>
130 *    <li><b>User-supplied bean:</b> register a {@code @Bean freemarker.template.Configuration}
131 *       (Spring) or
132 *       {@code BasicBeanStore.put(freemarker.template.Configuration.class, cfg)} (microservice).
133 *       Spring Boot's {@code spring-boot-starter-freemarker} autoconfigures one out of the box;
134 *       the bridge picks it up via {@code BeanStore.getBean(Configuration.class)}.
135 *    <li><b>Bridge default:</b> when nothing is registered, the bridge constructs a default
136 *       {@link Configuration} anchored on a classpath resource root derived from
137 *       {@link #getBasePath() basePath} ({@code "/templates/"} becomes
138 *       {@code ClassLoaderTemplateResolver}-equivalent prefix {@code "/templates"}). The default
139 *       pins {@code IncompatibleImprovements} to the bridge-tested minor version
140 *       ({@code Configuration.VERSION_2_3_34}), sets {@code DefaultEncoding} to {@code UTF-8},
141 *       uses {@code HTMLOutputFormat} so HTML escaping is the natural target, applies
142 *       {@code TemplateUpdateDelayMilliseconds} per the {@link #isCacheTemplates() cacheTemplates}
143 *       flag (production-safe by default; users opt into hot-reload via
144 *       {@link Builder#cacheTemplates(boolean) cacheTemplates(false)}), and sets an
145 *       {@code ObjectWrapper} with {@code exposeFields=true} (see below) unless overridden.
146 * </ul>
147 *
148 * <p>
149 * When no FreeMarker engine is on the classpath, the renderer surfaces
150 * {@link FreemarkerViewRenderer#NO_ENGINE_DIAGNOSTIC} naming the missing dependency.
151 *
152 * <h5 class='section'>Public-field DTOs (getter-only default trap):</h5>
153 *
154 * <p>
155 * FreeMarker's own version-default {@code ObjectWrapper} exposes only JavaBean getters &mdash; a
156 * view-model bean written as a simple DTO with public fields and no getters resolves every
157 * {@code ${bean.field}} reference to {@code null}/missing, <b>silently</b> (a template using
158 * {@code !'default'} fallbacks renders the default with no error at all; a bare {@code ${bean.field}}
159 * with no fallback throws at render time). This bridge avoids that trap by default: the bridge-built
160 * {@link Configuration} sets a {@link DefaultObjectWrapper} with {@code exposeFields=true}
161 * ({@link #DEFAULT_EXPOSE_FIELDS}), so public-field DTOs render their field values out of the
162 * box &mdash; matching how Juneau's own marshalling is comfortable with public-field beans elsewhere.
163 * Use {@link Builder#exposeFields(boolean) exposeFields(false)} to restore FreeMarker's getter-only
164 * behavior, or {@link Builder#objectWrapper(ObjectWrapper) objectWrapper(...)} for full control
165 * (e.g. a {@code BeansWrapper} with custom exposure rules). Both knobs only affect the
166 * bridge-default {@link Configuration}; a user-supplied {@code @Bean Configuration} is used as-is.
167 *
168 * <h5 class='section'>Template suffix:</h5>
169 *
170 * <p>
171 * FreeMarker's API does not expose a configuration-level resolver suffix &mdash; the literal
172 * template name is what {@link Configuration#getTemplate(String) cfg.getTemplate(...)} sees. The
173 * {@link Builder#templateSuffix(String) templateSuffix(...)} builder knob fills the gap: when
174 * non-blank, the bridge appends the configured suffix to template names that don't already end
175 * with it (idempotent), so callers can write {@code FreemarkerView.of("hello")} or request
176 * {@code /freemarker/hello} and have it resolve to {@code hello.ftlh} on the classpath.
177 *
178 * <h5 class='section'>{@code .ftl} vs {@code .ftlh}:</h5>
179 *
180 * <p>
181 * FreeMarker auto-selects HTML escaping by file extension: {@code .ftlh} templates emit
182 * HTML-escaped output; {@code .ftl} templates emit raw output. For HTML responses, prefer
183 * {@code .ftlh} so a future attribute-binding change can't introduce an XSS regression.
184 *
185 * <h5 class='section'>Multiple base paths:</h5>
186 *
187 * <p>
188 * A host with {@code /templates/} and {@code /admin/templates/} template trees registers two
189 * {@code FreemarkerMixin} beans, each mounted via a subclass declaring its own op-level
190 * {@code @RestGet(path=...)} override and its own {@code basePath}. See the
191 * {@code FreemarkerMixin_MockRest_Test} test in {@code juneau-integration-tests} for the canonical
192 * pattern.
193 *
194 * <h5 class='section'>OpenAPI surface:</h5>
195 *
196 * <p>
197 * The greedy {@code /*} handler is not API-meaningful and is excluded from generated
198 * Swagger / OpenAPI specs via {@link OpSwagger#ignore() @OpSwagger(ignore=true)}.
199 *
200 * <h5 class='section'>See Also:</h5><ul>
201 *    <li class='jc'>{@link FreemarkerView}
202 *    <li class='jc'>{@link FreemarkerViewRenderer}
203 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/FreemarkerViewSupport">FreeMarker View Support</a>
204 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerComposition">REST Server — Mixins and Multi-Mount Paths</a>
205 * </ul>
206 *
207 * @since 10.0.0
208 */
209// @formatter:off
210@Rest(
211   responseProcessors={FreemarkerViewRenderer.class},
212   mergeResponseProcessorsIntoHost=true
213)
214public class FreemarkerMixin {
215
216   /** Default base path applied when no {@link Builder#basePath(String)} call has been made. */
217   public static final String DEFAULT_BASE_PATH = FreemarkerDispatcher.DEFAULT_BASE_PATH;
218
219   /** Default template suffix &mdash; empty (literal template names, no implicit suffix). */
220   public static final String DEFAULT_TEMPLATE_SUFFIX = FreemarkerDispatcher.DEFAULT_TEMPLATE_SUFFIX;
221
222   /** Default template-cache flag &mdash; {@code true} (production-safe). */
223   public static final boolean DEFAULT_CACHE_TEMPLATES = FreemarkerDispatcher.DEFAULT_CACHE_TEMPLATES;
224
225   /** Default field-exposure flag &mdash; {@code true} (public-field DTOs render out of the box). */
226   public static final boolean DEFAULT_EXPOSE_FIELDS = FreemarkerDispatcher.DEFAULT_EXPOSE_FIELDS;
227
228   private static final Set<Class<? extends FreemarkerMixin>> SUBTYPES = new CopyOnWriteArraySet<>();
229
230   /**
231    * Registers a subclass so {@link FreemarkerViewRenderer} also finds a bean declared with that subclass as its
232    * {@code @Bean} return type.
233    *
234    * <p>
235    * The bean store is keyed by declared type, so without this a {@code @Bean public ConsoleFreemarkerMixin
236    * freemarker()} was not found and the renderer silently used a plain mixin. Subclasses call this from a static
237    * initializer.
238    *
239    * <h5 class='section'>Example:</h5>
240    * <p class='bjava'>
241    *    <jk>public class</jk> MyMixin <jk>extends</jk> FreemarkerMixin {
242    *       <jk>static</jk> { FreemarkerMixin.<jsm>registerSubtype</jsm>(MyMixin.<jk>class</jk>); }
243    *    }
244    * </p>
245    *
246    * @param type The subclass. Must not be {@code null}.
247    */
248   public static void registerSubtype(Class<? extends FreemarkerMixin> type) {
249      if (type == null)
250         throw new IllegalArgumentException("registerSubtype(type): type must not be null.");
251      SUBTYPES.add(type);
252   }
253
254   /** @return The registered subclasses, in registration order. */
255   public static Set<Class<? extends FreemarkerMixin>> registeredSubtypes() {
256      return Collections.unmodifiableSet(SUBTYPES);
257   }
258
259   private final FreemarkerDispatcher worker;
260
261   /**
262    * Creates a new builder.
263    *
264    * @return A new builder.
265    */
266   public static Builder create() {
267      return new Builder();
268   }
269
270   /**
271    * No-arg constructor &mdash; uses {@link #DEFAULT_BASE_PATH} as the base path,
272    * {@link #DEFAULT_TEMPLATE_SUFFIX} as the template suffix, and
273    * {@link #DEFAULT_CACHE_TEMPLATES} as the cache flag.
274    *
275    * <p>
276    * The mixin walk falls back to this constructor when the
277    * {@link org.apache.juneau.commons.inject.BeanStore BeanStore} does not have a registered
278    * {@code FreemarkerMixin} bean.
279    */
280   public FreemarkerMixin() {
281      this(create());
282   }
283
284   /**
285    * Builder constructor.
286    *
287    * @param builder The builder. Must not be {@code null}.
288    */
289   protected FreemarkerMixin(Builder builder) {
290      worker = builder.worker.build();
291   }
292
293   /**
294    * Returns the base path under which template resources are resolved.
295    *
296    * @return The base path. Never {@code null}.
297    */
298   public String getBasePath() {
299      return worker.getBasePath();
300   }
301
302   /**
303    * Returns the configured template-name suffix.
304    *
305    * @return The template suffix. Never {@code null}.
306    */
307   public String getTemplateSuffix() {
308      return worker.getTemplateSuffix();
309   }
310
311   /**
312    * Returns the template-cache flag.
313    *
314    * @return The cache flag.
315    */
316   public boolean isCacheTemplates() {
317      return worker.isCacheTemplates();
318   }
319
320   /**
321    * Returns the field-exposure flag applied to the bridge-default object wrapper.
322    *
323    * @return The field-exposure flag. Ignored once {@link #getObjectWrapper()} returns non-{@code null}.
324    */
325   public boolean isExposeFields() {
326      return worker.isExposeFields();
327   }
328
329   /**
330    * Returns the user-supplied {@link ObjectWrapper} override, if any.
331    *
332    * @return The object wrapper override, or {@code null} if the bridge builds its own default.
333    */
334   public ObjectWrapper getObjectWrapper() {
335      return worker.getObjectWrapper();
336   }
337
338   /**
339    * Appends {@link #getTemplateSuffix()} to {@code name} if not already present (idempotent).
340    *
341    * @param name The template name. Must not be {@code null}.
342    * @return The template name with the configured suffix appended (if applicable).
343    */
344   public String applyTemplateSuffix(String name) {
345      return worker.applyTemplateSuffix(name);
346   }
347
348   /**
349    * Resolves the active {@link Configuration} via the shared {@link FreemarkerDispatcher} worker.
350    *
351    * @param req The current REST request.
352    * @return The active FreeMarker configuration. Never {@code null}.
353    */
354   public Configuration resolveConfiguration(RestRequest req) {
355      return worker.resolveConfiguration(req);
356   }
357
358   /**
359    * [GET /freemarker/*] &mdash; render a raw template through the FreeMarker engine.
360    *
361    * <p>
362    * The {@code @Path("/*") String path} captures the multi-segment trailing remainder (e.g. a
363    * request for {@code /freemarker/about.ftlh} matches the mount with
364    * {@code path = "about.ftlh"}). Delegates to the shared {@link FreemarkerDispatcher} worker
365    * (path-traversal hardening, suffix application, configuration resolution, and render).
366    *
367    * <p>
368    * Missing template surfaces as the engine's own {@link TemplateNotFoundException}
369    * (HTTP 500 from the renderer's catch-all); missing engine surfaces as
370    * {@link FreemarkerViewRenderer#NO_ENGINE_DIAGNOSTIC}.
371    *
372    * @param path The trailing path segment after {@code /freemarker/} (the template name
373    *    relative to the configured {@link #getBasePath() base path}; configured suffix appended
374    *    if missing).
375    * @param req The current REST request.
376    * @param res The current REST response.
377    * @throws IOException If the underlying response writer fails.
378    * @throws BasicHttpException On boundary violation (403), missing engine (500), or render
379    *    failure (500).
380    */
381   @RestGet(
382      path="/${juneau.freemarker.path:freemarker}/*",
383      summary="FreeMarker view",
384      description="Render a raw FreeMarker template under the configured base path.",
385      swagger=@OpSwagger(ignore=true)
386   )
387   public void render(@Path("/*") String path, RestRequest req, RestResponse res)
388         throws IOException, BasicHttpException {
389      worker.render(path, req, res);
390   }
391
392   /**
393    * Builder for {@link FreemarkerMixin}.
394    *
395    * <p>
396    * Mirrors {@link FreemarkerDispatcher.Builder}'s configuration methods on its own surface and
397    * forwards each call into a held {@link FreemarkerDispatcher.Builder} (worker-bean
398    * composition). Implements {@link ViewMixinBuilder} so {@link #basePath(String)} and
399    * {@link #cacheTemplates(boolean)} are guaranteed to transfer to the sibling JSP / Thymeleaf /
400    * Mustache bridge builders.
401    */
402   public static class Builder implements ViewMixinBuilder<Builder> {
403
404      private final FreemarkerDispatcher.Builder worker = FreemarkerDispatcher.create();
405
406      /** Constructor &mdash; package access for {@link FreemarkerMixin#create()}. */
407      protected Builder() {}
408
409      /**
410       * Sets the classpath base path under which template resources are resolved.
411       *
412       * <p>
413       * Defaults to {@link FreemarkerMixin#DEFAULT_BASE_PATH "/"}. A typical configured
414       * value is {@code "/templates/"} &mdash; the same Spring-Boot-compatible layout
415       * convention the sibling JSP / Thymeleaf / Mustache bridges use.
416       *
417       * @param value The base path. {@code null} or blank values reset to the default
418       *    {@link FreemarkerMixin#DEFAULT_BASE_PATH}.
419       * @return This object.
420       */
421      @Override /* ViewMixinBuilder */
422      public Builder basePath(String value) {
423         worker.basePath(value);
424         return this;
425      }
426
427      /**
428       * Sets the suffix to append to template names that don't already end with it
429       * (idempotent).
430       *
431       * <p>
432       * Defaults to {@link FreemarkerMixin#DEFAULT_TEMPLATE_SUFFIX ""} (no implicit
433       * suffix &mdash; literal template names). Typical configured values are {@code ".ftlh"}
434       * (HTML auto-escape) or {@code ".ftl"} (no auto-escape).
435       *
436       * @param value The new suffix. {@code null} resets to
437       *    {@link FreemarkerMixin#DEFAULT_TEMPLATE_SUFFIX}.
438       * @return This object.
439       */
440      public Builder templateSuffix(String value) {
441         worker.templateSuffix(value);
442         return this;
443      }
444
445      /**
446       * Sets the template-cache flag for the bridge-default configuration.
447       *
448       * <p>
449       * Defaults to {@link FreemarkerMixin#DEFAULT_CACHE_TEMPLATES true} (cache
450       * forever &mdash; production-safe). Set {@code false} to enable dev hot-reload (FreeMarker
451       * re-checks the underlying resource on every render).
452       *
453       * <p>
454       * This flag only affects the bridge-default {@link Configuration}; user-supplied
455       * {@code @Bean Configuration}s are not modified.
456       *
457       * @param value The cache flag.
458       * @return This object.
459       */
460      @Override /* ViewMixinBuilder */
461      public Builder cacheTemplates(boolean value) {
462         worker.cacheTemplates(value);
463         return this;
464      }
465
466      /**
467       * Sets whether the bridge-default {@code ObjectWrapper} exposes public fields (not just
468       * JavaBean getters) to templates.
469       *
470       * <p>
471       * Defaults to {@link FreemarkerMixin#DEFAULT_EXPOSE_FIELDS true} so simple view-model
472       * DTOs written with public fields and no getters render their field values instead of
473       * silently resolving to {@code null}/missing (see the class-level
474       * "Public-field DTOs" section). Has no effect once {@link #objectWrapper(ObjectWrapper)}
475       * has been called with a non-{@code null} value &mdash; the explicit wrapper always wins.
476       *
477       * @param value The field-exposure flag.
478       * @return This object.
479       */
480      public Builder exposeFields(boolean value) {
481         worker.exposeFields(value);
482         return this;
483      }
484
485      /**
486       * Sets a fully custom {@code ObjectWrapper} for the bridge-default configuration,
487       * overriding {@link #exposeFields(boolean)} entirely.
488       *
489       * <p>
490       * Escape hatch for consumers who need full control (e.g. a {@code BeansWrapper} with
491       * custom method/property exposure, or a third-party wrapper) without hand-rolling and
492       * registering a whole replacement {@code Configuration} bean.
493       *
494       * @param value The object wrapper. {@code null} reverts to the bridge-built
495       *    {@link DefaultObjectWrapper} (per {@link #exposeFields(boolean)}).
496       * @return This object.
497       */
498      public Builder objectWrapper(ObjectWrapper value) {
499         worker.objectWrapper(value);
500         return this;
501      }
502
503      /**
504       * Reads the current base path setting (test/inspection helper).
505       *
506       * @return The base path. Never {@code null}.
507       */
508      public String getBasePath() {
509         return worker.getBasePath();
510      }
511
512      /**
513       * Reads the current template-suffix setting (test/inspection helper).
514       *
515       * @return The template suffix. Never {@code null}.
516       */
517      public String getTemplateSuffix() {
518         return worker.getTemplateSuffix();
519      }
520
521      /**
522       * Reads the current cache-templates setting (test/inspection helper).
523       *
524       * @return The cache flag.
525       */
526      public boolean isCacheTemplates() {
527         return worker.isCacheTemplates();
528      }
529
530      /**
531       * Reads the current field-exposure setting (test/inspection helper).
532       *
533       * @return The field-exposure flag.
534       */
535      public boolean isExposeFields() {
536         return worker.isExposeFields();
537      }
538
539      /**
540       * Reads the current object-wrapper override (test/inspection helper).
541       *
542       * @return The object wrapper override, or {@code null} if none has been set.
543       */
544      public ObjectWrapper getObjectWrapper() {
545         return worker.getObjectWrapper();
546      }
547
548      /**
549       * Builds the {@link FreemarkerMixin}.
550       *
551       * @return A new {@link FreemarkerMixin} instance.
552       */
553      public FreemarkerMixin build() {
554         return new FreemarkerMixin(this);
555      }
556   }
557}