Class FreemarkerMixin
- Direct Known Subclasses:
ConsoleFreemarkerMixin
Compose into a host resource via
@Rest(mixins=FreemarkerMixin.class); the host then:
- Gains a default mount at
/freemarker/*that serves raw FreeMarker templates from the importer's classpath by asking the configuredConfigurationfor the named template and rendering it with an empty data model (raw render path; callers who want attributes useFreemarkerViewfrom a typed handler instead). - Registers
FreemarkerViewRendererfor the mixin's own endpoints (e.g. the/freemarker/*route above) via the mixin's own@Rest(responseProcessors=...)declaration.
Auto-wiring the renderer into the host's own endpoints:
A bare @Rest(mixins=FreemarkerMixin.class) is enough — a host whose own @RestOp method
returns FreemarkerView renders it through the FreeMarker engine with no extra wiring. That works
because FreemarkerMixin declares
@Rest(mergeResponseProcessorsIntoHost=true) on its own class,
so the framework folds FreemarkerMixin's FreemarkerViewRenderer into the host's own
response-processor chain automatically.
This fold is per-mixin, opt-in, and response-processor-scoped: it happens only because
FreemarkerMixin declares the opt-in (a mixin that does not is still scoped to its own endpoints, the
standard @Rest(mixins=...) rule), and it folds only the renderer — never
guards, serializers, or any other list-shaped attribute.
Two explicit wirings remain supported — useful when you want to be explicit, or to fold more than the renderer:
@Mixin(mergeIntoHost=true)via the richmixinDefsform — folds all ofFreemarkerMixin's list-shaped@Restattributes into the host's own chain, not just the renderer.- List
FreemarkerViewRenderer.classexplicitly in the host's own@Rest(responseProcessors=...)— the fully manual equivalent.
The ResponseProcessorList partition pass repositions
FreemarkerViewRenderer (a
ViewRenderer) ahead of
SerializedPojoProcessor in every case, so the FreemarkerView bean is dispatched to the
FreeMarker engine rather than bean-serialized.
Composition example (microservice):
Configurable mount path:
The default mount /freemarker/* can be overridden via the SVL variable
${juneau.freemarker.path:freemarker} — set via system property
(-Djuneau.freemarker.path=views), environment variable
(JUNEAU_FREEMARKER_PATH=views), or Config key
(juneau.freemarker.path = views) to change the runtime mount without subclassing.
Resolution happens once at RestContext construction time (SVL resolution in @RestOp(path)).
Mixin-only deployment:
This resource is designed for composition via @Rest(mixins=...). The mount path is
pinned at the op level by
@RestGet(path="/{juneau.freemarker.path:freemarker}/*") on render(java.lang.String, org.apache.juneau.rest.server.RestRequest, org.apache.juneau.rest.server.RestResponse);
a class-level @Rest(paths=...) declaration would be silently ignored under the mixin
pattern (see @Rest(paths) Javadoc).
Choosing a Configuration:
The juneau-rest-server-view-freemarker module ships only
org.freemarker:freemarker in provided scope. No configuration bean is
bundled. The bridge resolves a Configuration at first use from three sources, in
order:
- User-supplied bean: register a
@Bean freemarker.template.Configuration(Spring) orBasicBeanStore.put(freemarker.template.Configuration.class, cfg)(microservice). Spring Boot'sspring-boot-starter-freemarkerautoconfigures one out of the box; the bridge picks it up viaBeanStore.getBean(Configuration.class). - Bridge default: when nothing is registered, the bridge constructs a default
Configurationanchored on a classpath resource root derived frombasePath("/templates/"becomesClassLoaderTemplateResolver-equivalent prefix"/templates"). The default pinsIncompatibleImprovementsto the bridge-tested minor version (Configuration.VERSION_2_3_34), setsDefaultEncodingtoUTF-8, usesHTMLOutputFormatso HTML escaping is the natural target, appliesTemplateUpdateDelayMillisecondsper thecacheTemplatesflag (production-safe by default; users opt into hot-reload viacacheTemplates(false)), and sets anObjectWrapperwithexposeFields=true(see below) unless overridden.
When no FreeMarker engine is on the classpath, the renderer surfaces
FreemarkerViewRenderer.NO_ENGINE_DIAGNOSTIC naming the missing dependency.
Public-field DTOs (getter-only default trap):
FreeMarker's own version-default ObjectWrapper exposes only JavaBean getters — a
view-model bean written as a simple DTO with public fields and no getters resolves every
${bean.field} reference to null/missing, silently (a template using
!'default' fallbacks renders the default with no error at all; a bare ${bean.field}
with no fallback throws at render time). This bridge avoids that trap by default: the bridge-built
Configuration sets a DefaultObjectWrapper with exposeFields=true
(DEFAULT_EXPOSE_FIELDS), so public-field DTOs render their field values out of the
box — matching how Juneau's own marshalling is comfortable with public-field beans elsewhere.
Use exposeFields(false) to restore FreeMarker's getter-only
behavior, or objectWrapper(...) for full control
(e.g. a BeansWrapper with custom exposure rules). Both knobs only affect the
bridge-default Configuration; a user-supplied @Bean Configuration is used as-is.
Template suffix:
FreeMarker's API does not expose a configuration-level resolver suffix — the literal
template name is what cfg.getTemplate(...) sees. The
templateSuffix(...) builder knob fills the gap: when
non-blank, the bridge appends the configured suffix to template names that don't already end
with it (idempotent), so callers can write FreemarkerView.of("hello") or request
/freemarker/hello and have it resolve to hello.ftlh on the classpath.
.ftl vs .ftlh:
FreeMarker auto-selects HTML escaping by file extension: .ftlh templates emit
HTML-escaped output; .ftl templates emit raw output. For HTML responses, prefer
.ftlh so a future attribute-binding change can't introduce an XSS regression.
Multiple base paths:
A host with /templates/ and /admin/templates/ template trees registers two
FreemarkerMixin beans, each mounted via a subclass declaring its own op-level
@RestGet(path=...) override and its own basePath. See the
FreemarkerMixin_MockRest_Test test in juneau-integration-tests for the canonical
pattern.
OpenAPI surface:
The greedy /* handler is not API-meaningful and is excluded from generated
Swagger / OpenAPI specs via @OpSwagger(ignore=true).
See Also:
- Since:
- 10.0.0
-
Nested Class Summary
Nested Classes -
Field Summary
FieldsModifier and TypeFieldDescriptionstatic final StringDefault base path applied when noFreemarkerMixin.Builder.basePath(String)call has been made.static final booleanDefault template-cache flag —true(production-safe).static final booleanDefault field-exposure flag —true(public-field DTOs render out of the box).static final StringDefault template suffix — empty (literal template names, no implicit suffix). -
Constructor Summary
ConstructorsModifierConstructorDescriptionNo-arg constructor — usesDEFAULT_BASE_PATHas the base path,DEFAULT_TEMPLATE_SUFFIXas the template suffix, andDEFAULT_CACHE_TEMPLATESas the cache flag.protectedFreemarkerMixin(FreemarkerMixin.Builder builder) Builder constructor. -
Method Summary
Modifier and TypeMethodDescriptionapplyTemplateSuffix(String name) AppendsgetTemplateSuffix()tonameif not already present (idempotent).static FreemarkerMixin.Buildercreate()Creates a new builder.Returns the base path under which template resources are resolved.freemarker.template.ObjectWrapperReturns the user-suppliedObjectWrapperoverride, if any.Returns the configured template-name suffix.booleanReturns the template-cache flag.booleanReturns the field-exposure flag applied to the bridge-default object wrapper.static Set<Class<? extends FreemarkerMixin>>static voidregisterSubtype(Class<? extends FreemarkerMixin> type) Registers a subclass soFreemarkerViewRendereralso finds a bean declared with that subclass as its@Beanreturn type.voidrender(String path, RestRequest req, RestResponse res) [GET /freemarker/*] — render a raw template through the FreeMarker engine.freemarker.template.ConfigurationResolves the activeConfigurationvia the sharedFreemarkerDispatcherworker.
-
Field Details
-
DEFAULT_BASE_PATH
Default base path applied when noFreemarkerMixin.Builder.basePath(String)call has been made.- See Also:
-
DEFAULT_TEMPLATE_SUFFIX
Default template suffix — empty (literal template names, no implicit suffix).- See Also:
-
DEFAULT_CACHE_TEMPLATES
Default template-cache flag —true(production-safe).- See Also:
-
DEFAULT_EXPOSE_FIELDS
Default field-exposure flag —true(public-field DTOs render out of the box).- See Also:
-
-
Constructor Details
-
FreemarkerMixin
public FreemarkerMixin()No-arg constructor — usesDEFAULT_BASE_PATHas the base path,DEFAULT_TEMPLATE_SUFFIXas the template suffix, andDEFAULT_CACHE_TEMPLATESas the cache flag.The mixin walk falls back to this constructor when the
BeanStoredoes not have a registeredFreemarkerMixinbean. -
FreemarkerMixin
Builder constructor.- Parameters:
builder- The builder. Must not benull.
-
-
Method Details
-
registerSubtype
Registers a subclass soFreemarkerViewRendereralso finds a bean declared with that subclass as its@Beanreturn type.The bean store is keyed by declared type, so without this a
@Bean public ConsoleFreemarkerMixin freemarker()was not found and the renderer silently used a plain mixin. Subclasses call this from a static initializer.Example:
public class MyMixinextends FreemarkerMixin {static { FreemarkerMixin.registerSubtype (MyMixin.class ); } }- Parameters:
type- The subclass. Must not benull.
-
registeredSubtypes
- Returns:
- The registered subclasses, in registration order.
-
create
Creates a new builder.- Returns:
- A new builder.
-
getBasePath
Returns the base path under which template resources are resolved.- Returns:
- The base path. Never
null.
-
getTemplateSuffix
Returns the configured template-name suffix.- Returns:
- The template suffix. Never
null.
-
isCacheTemplates
Returns the template-cache flag.- Returns:
- The cache flag.
-
isExposeFields
Returns the field-exposure flag applied to the bridge-default object wrapper.- Returns:
- The field-exposure flag. Ignored once
getObjectWrapper()returns non-null.
-
getObjectWrapper
Returns the user-suppliedObjectWrapperoverride, if any.- Returns:
- The object wrapper override, or
nullif the bridge builds its own default.
-
applyTemplateSuffix
AppendsgetTemplateSuffix()tonameif not already present (idempotent).- Parameters:
name- The template name. Must not benull.- Returns:
- The template name with the configured suffix appended (if applicable).
-
resolveConfiguration
Resolves the activeConfigurationvia the sharedFreemarkerDispatcherworker.- Parameters:
req- The current REST request.- Returns:
- The active FreeMarker configuration. Never
null.
-
render
public void render(@Path("/*") String path, RestRequest req, RestResponse res) throws IOException, BasicHttpException [GET /freemarker/*] — render a raw template through the FreeMarker engine.The
@Path("/*") String pathcaptures the multi-segment trailing remainder (e.g. a request for/freemarker/about.ftlhmatches the mount withpath = "about.ftlh"). Delegates to the sharedFreemarkerDispatcherworker (path-traversal hardening, suffix application, configuration resolution, and render).Missing template surfaces as the engine's own
TemplateNotFoundException(HTTP 500 from the renderer's catch-all); missing engine surfaces asFreemarkerViewRenderer.NO_ENGINE_DIAGNOSTIC.- Parameters:
path- The trailing path segment after/freemarker/(the template name relative to the configuredbase path; configured suffix appended if missing).req- The current REST request.res- The current REST response.- Throws:
IOException- If the underlying response writer fails.BasicHttpException- On boundary violation (403), missing engine (500), or render failure (500).
-