1. What is Trimou?
Trimou is yet another Mustache implementation written in Java. Since 1.5 a helpers API inspired by Handlebars.js is also supported. The goal is to provide a simple to use and easy to extend templating engine for any Java SE or Java EE application. There are some ready-to-use extensions which provide integration with CDI, Servlets, PrettyTime, HtmlCompressor, google-gson, Spring MVC and Dropwizard (see Extensions section for more info).
Note
|
Trimou is available under the Apache License 2.0. |
2. Features
The basic features of every Mustache implementation are defined in the Mustache spec. Trimou passes all the tests (version 1.1.2), except "Section - Alternate Delimiters" from lambdas optional module.
The most notable enhancements are:
-
Template caching
-
Template inheritance
-
Basic i18n support
-
Extended processing of lambdas
-
Helpers API (inspired by Handlebars.js)
-
Extension points (template locators, resolvers, text support, …)
2.1. Variables, interpolation tags
Spec description: https://github.com/mustache/spec/blob/master/specs/interpolation.yml
Note
|
The interpolation tag’s content is called a key hereafter. The key consists of one or more dot-separated parts - a key part ("foo" → ["foo"] , "foo.bar.name" → ["foo, "bar", "name"] ). The current object the key part is resolved against is called a context object hereafter.
|
In general Trimou interpolation works very similar to the spec description - walk the context object stack from top to bottom, and resolve the value for the given part of the key (first part againts the context stack, other parts against the result from the former resolution).
However there are some differences:
-
it’s possible to use
this
keyword to reference the object atop the context object stack; the spec only allows.
, -
if the context object is an instance of a
java.util.Map
, value of the entry with the given key is resolved, -
for any context object Trimou tries to:
-
find and invoke an accessible public method with no params, non-void return type and name
keypart
,getKeypart
orisKeypart
defined on the context object’s class and its superclasses (except forjava.lang.Object
), -
find an accessible public field with name
keypart
and get its value,
-
-
java.util.List
and array elements can be accessed via index (the key must be an unsigned integer), -
an iteration metada object is available inside an iteration block, the default alias is
iter
:-
the alias can be configured, see Configuration,
-
this metadata has some useful properties:
iter.index
(the first element is at index1
),iter.position
(the first element has position0
),iter.hasNext
,iter.isFirst
anditer.isLast
, -
alternatively
iterIndex
,iterHasNext
,iterIsFirst
anditerIsLast
keywords can be used,
-
-
Trimou allows you to define a resolver that does not handle the context object stack at all (e.g. looks up a CDI bean).
{{foo}} <1> {{foo.bar}} <2> {{list.0}} <3> {{array.5}} <4> {{#items}} {{iter.index}} <5> {{#iter.isFirst}} The is the first one! {{/iter.isFirst}} <6> {{#iter.isLast}} This is the last one! {{/iter.isLast}} <7> {{name}} <8> {{#iter.hasNext}}, {{/iter.hasNext}} <9> {{/items}} {{#quxEnumClass.values}} <10> {{this}} {{/quxEnumClass.values}}
-
Try to get a value of key "foo" from the context object stack, e.g. if the supplied data context object is an instance of Map get the value of key "foo"
-
Try to get a value of key "bar" from the context object resolved in <1>, e.g. try to invoke bar(), getBar() or isBar() on the "foo" instance or get the value of the field with name "bar"
-
Get the first element
-
Get the sixth element
-
The current iteration index (the first element is at index 1)
-
Render the text for the first iteration
-
Render the text for the last iteration
-
"name" is resolved against the context object stack (iteration element, supplied data map)
-
Render a comma if the iteration has more elements (iterHasNext is true)
-
It’s also possible to invoke static methods; quxEnumClass is an enum class here and we iterate over the array returned from static method values()
Note
|
The set of resolvers may be extended - so in fact the above mentioned applies to the default set of resolvers only. |
2.1.1. Escaping HTML
The interpolated value is escaped unless &
is used. The spec only tests the basic escaping (&
, "
, <
, >
). Trimou also escapes all ISO-8859-1 characters by default.
{{foo}} <1> {{& foo}} <2> {{{foo}}} <3>
-
Escape foo
-
Do not escape foo
-
Do not escape foo; works only for default delimiters!
Tip
|
You can implement your own escaping logic, e.g. to improve escaping performance - see Configure the engine and TextSupport sections. |
2.2. Sections
Spec description: https://github.com/mustache/spec/blob/master/specs/sections.yml
The section content is rendered one or more times if there is an object found for the given key. If the found object is:
-
non-empty
Iterable
or array, the content is rendered for each element, -
a
Boolean
of valuetrue
, the content is rendered once, -
an instance of
Lambda
, the content is processed according to the lambda’s specification, -
any other non-null object represents a nested context.
The section content is not rendered if there is no object found, or the found object is:
-
a
Boolean
of valuefalse
, -
an
Iterable
with no elements, -
an empty array.
{{#boolean}} This line will be rendered only if "boolean" key resolves to java.lang.Boolean#TRUE, or true {{/boolean}} {{#iterable_or_array}} This line will be rendered for each element, and the element is pushed on the context object stack {{/iterable_or_array}}
2.3. Inverted sections
Spec description: https://github.com/mustache/spec/blob/master/specs/inverted.yml
The content is rendered if there is no object found in the context, or is a Boolean
of value false
, or is an Iterable
with no elements, or is an empty array.
{{^iterable}} This line will be rendered if the resolved iterable has no elements {{/iterable}}
2.4. Partials
Spec description: https://github.com/mustache/spec/blob/master/specs/partials.yml
Partials only work if at least one template locator is in action. Otherwise the template cache is not used and there is no way to locate the required partial (template). See Configure the engine and Template locator sections for more info.
{{#items}} {{>item_detail}} - process the template with name "item_detail" for each iteration element {{/items}}
2.5. Delimiters
Spec description: https://github.com/mustache/spec/blob/master/specs/delimiters.yml
{{=%% %%=}} - from now on use custom delimiters %%foo.name%% - interpolate "foo.name" %%={{ }}=%% - switch back to default values
Tip
|
It’s also possible to change the delimiters globally, see Configuration. |
2.6. Lambdas
Spec description: https://github.com/mustache/spec/blob/master/specs/lambdas.yml
You can implement org.trimou.lambda.Lambda
interface in order to define a lambda/callable object. Predefined abstract org.trimou.lambda.SpecCompliantLambda
follows the behaviour defined by the spec:
Lambda makeMeBold = new SpecCompliantLambda() {
@Override
public String invoke(String text) {
return "<b>" + text + "</b>";
}
}
and template
{{#makeMeBold}} Any text...{{name}} {{/makeMeBold}}
results in:
<b>Any text...{{name}}</b> -> the variable is not interpolated
However this might be a little bit more useful:
Lambda makeMeUppercase = new InputProcessingLambda() {
@Override
public String invoke(String text) {
return text.toUpperCase();
}
@Override
public boolean isReturnValueInterpolated() {
return false;
}
}
and template
{{#makeMeUppercase}} Any text...{{name}} {{/makeMeUppercase}}
results in:
ANY TEXT...EDGAR -> the variable is interpolated before the lambda invoke() method is invoked
See org.trimou.lambda.Lambda
API javadoc for more info.
2.7. Extending templates
This feature is not supported in the spec. Trimou basically follows the way mustache.java implements the template inheritance. In the extended template, the sections to extend are defined - use $
to identify such sections. In extending templates, the extending sections are defined - again, use $
to identify such sections. Sections to extend may define the default content.
Following template with name "super":
This a template to extend {{$header}} -> section to extend The default header {{/header}} In between... {{$content}} -> section to extend The default content {{/content}} © 2013
can be extended in this way:
Hello world! {{<super}} {{$header}} -> extending section My own header {{/header}} Only extending sections are considered... {{/super}} Lalala...
and the result is:
Hello world! This a template to extend <1> My own header <2> In between... The default content <3> © 2013 <4> Lalala...
-
"super" start
-
section "header" is extended
-
section "content" has the default content
-
"super" end
2.8. Built-in helpers
Since version 1.5.0 helpers API inspired by Handlebars.js is supported. There are five helpers registered automatically: if
, unless
, each
, with
(see http://handlebarsjs.com for examples :-) and is
(an inline version of if).
Tip
|
if and unless helpers also support multiple params evaluation. See the javadoc for more info.
|
Other helpers may be registered via MustacheEngineBuilder.registerHelper()
or MustacheEngineBuilder.registerHelpers()
methods. org.trimou.handlebars.HelpersBuilder
is useful when registering most built-in helpers with sensible default names.
See also Helper section to know how to create your own custom helpers.
Note
|
Handlebars support is enabled by default. See HANDLEBARS_SUPPORT_ENABLED in Configuration properties.
|
Trimou provides some useful helpers which are not registered automatically:
Class | Description | Default name |
---|---|---|
|
Works similarly as the JSP c:choose tag - it renders the content of the first |
choose |
|
Works similarly as the Java switch statement. |
switch |
|
Works similarly as WithHelper except the current hash is pushed on the context stack. |
set |
|
Works similarly as the partial tag except the name of the template to include may be obtained dynamically. |
include |
|
Logs debug messages. |
log |
|
Takes all the objects specified as the parameters and joins the |
join |
|
Embeds the template source (by default as a JavaScript snippet). |
embed |
|
Renders a block if the param is/isn’t null. |
isNull/isNotNull |
|
Renders a block if the first param does/doesn’t equal to the second param. |
isEq/isNotEq |
|
Renders a block/text if the param is an even number. |
isEven |
|
Renders a block/text if the param is an odd number. |
isOdd |
|
Displays localized messages. |
N/A |
|
Displays localized times. |
N/A |
2.8.1. Example of ResourceBundleHelper use
Suppose we have the following resource bundle file:
my.message.key=My name is %s! hello.key.messageformat=Hello {0}!
We can use a ResourceBundleHelper
to render messages:
MustacheEngine engine = MustacheEngineBuilder
.newBuilder()
.registerHelper("msg", new ResourceBundleHelper("messages")) <1>
.build();
...
{{msg "my.message.key" "Martin"}} {{msg "hello.key.messageformat" "world" format="message"}}
My name is Martin! Hello world!
3. How to use
3.1. Get started
First, get the trimou-core.jar
and its dependencies (guava
,slf4j-api
and commons-lang3
at the moment).
<dependency>
<groupId>org.trimou</groupId>
<artifactId>trimou-core</artifactId>
<version>${version.trimou}</version>
</dependency>
And now for something completely different…
3.1.1. The simplest possible scenario
String data = "Hello world!";
String template = "{{this}}";
MustacheEngine engine = MustacheEngineBuilder.newBuilder().build(); <1>
Mustache mustache = engine.compileMustache("myTemplateName", template); <2>
String output = mustache.render(data); <3>
<1> <2> <3>
String output = MustacheEngineBuilder
.newBuilder()
.build()
.compileMustache("myTemplateName", template)
.render(data);
// Both snippets will render "Hello world!"
-
Build the engine
-
Compile the template
-
Render the template
Note
|
Instances of MustacheEngineBuilder are not reusable. The builder is considered immutable once the
build() method is called - subsequent invocations of any modifying method or build() methods result in IllegalStateException .
|
3.1.2. Provide your own Appendable
MustacheEngine engine = MustacheEngineBuilder.newBuilder().build();
Mustache mustache = engine.compileMustache("fooTemplate", "{{foo}}");
// It's possible to pass a java.lang.Appendable impl, e.g. any java.io.Writer
StringWriter writer = new StringWriter();
mustache.render(writer, ImmutableMap.<String, Object> of("foo", "bar"));
// writer.toString() -> "bar"
3.1.3. Configure the engine
You may want to:
-
Add template locators; see also Make use of template cache and template locators
-
Add thread-safe global data objects (available during execution of all templates)
-
Add custom resolvers; see also Resolver
-
Add template listeners; see also MustacheListener
-
Register additional helpers; see also Helper
-
Set custom
TextSupport
implementation; see TextSupport -
Set custom
LocaleSupport
implementation; see LocaleSupport -
Set custom
MissingValueHandler
; see MissingValueHandler -
Set custom
KeySplitter
; see KeySplitter -
Set configuration properties; see Configuration properties
Simply use appropriate MustacheEngineBuilder
methods, e.g.:
MustacheEngine engine = MustacheEngineBuilder
.newBuilder()
.addGlobalData("fooLambda", mySuperUsefulLambdaInstance)
.build();
3.2. Make use of template cache and template locators
Template locators automatically locate the template contents for the given template id (name, path, …). So that it’s not necessary to supply the template contents every time the template is compiled. Moreover if the template cache is enabled the compiled template is automatically put in the cache and no compilation happens the next time the template is requested.
Note
|
Template locators are required for partials! |
MustacheEngine engine = MustacheEngineBuilder
.newBuilder()
.addTemplateLocator(new FilesystemTemplateLocator(1, "/home/trimou/resources", "txt")) <1>
.build();
Mustache mustache = engine.getMustache("foo"); <2>
String output = mustache.render(null);
-
Add a filesystem-based template locator with priority 1, root path "/home/trim/resources", template files have suffix "txt"
-
Get the template with name "foo" from the template cache, compile it if not compiled before
There may be more than one template locators registered with the engine. Locators with higher priority are called first.
Tip
|
Use MustacheEngine#invalidateTemplateCache() to invalidate all template cache entries and force recompilation.
|
See also TemplateLocator SPI.
3.2.1. Note about file encoding
Trimou does not perform any file encoding detection and conversion. Instead any template locator must provide a java.io.Reader
instance which is able to convert between Unicode and a other character encodings. Built-in locators don’t detect file encoding but use system file encoding by default. But it’s possible (and recommended) to define the default file encoding with configuration property EngineConfigurationKey.DEFAULT_FILE_ENCODING
(see also configuration properties).
Note
|
Applications are encouraged to always define a default file encoding per every MustacheEngine instance. |
3.3. Configuration properties
Trimou engine properties can be configured through system properties, trimou.properties
file or the property can be set manually with MustacheEngineBuilder.setProperty(String, Object)
method. Manually set properties have higher priority than system properties which have higher priority than properties from trimou.properties
file.
Note
|
Trimou logs all configuration properties and values during engine initialization |
Enum value / property key | Default value | Description |
---|---|---|
START_DELIMITER org.trimou.engine.config.startDelimiter |
{{ |
The default start delimiter. |
END_DELIMITER org.trimou.engine.config.endDelimiter |
}} |
The default end delimiter |
PRECOMPILE_ALL_TEMPLATES org.trimou.engine.config.precompileAllTemplates |
false |
If enabled, all available templates from all available template locators will be compiled during engine initialization. |
REMOVE_STANDALONE_LINES org.trimou.engine.config.removeStandaloneLines |
true |
Remove "standalone lines" from each template during compilation to fullfill the spec requirements (and get more readable output :-) |
REMOVE_UNNECESSARY_SEGMENTS org.trimou.engine.config.removeUnnecessarySegments |
true |
Remove unnecessary segments (e.g. comments and delimiters tags) from each template during compilation. Having this enabled results in spec not-compliant output, but may improve performance a little bit. |
NO_VALUE_INDICATES_PROBLEM org.trimou.engine.config.noValueIndicatesProblem |
false |
DEPRECATED - see MissingValueHandler SPI. By default a variable miss returns an empty string. If set to |
DEBUG_MODE org.trimou.engine.config.debugMode |
false |
Debug mode disables the template cache and provides some more logging during template rendering. |
CACHE_SECTION_LITERAL_BLOCK org.trimou.engine.config.cacheSectionLiteralBlock |
false |
The section-based literal blocks can be cached. This may be useful to optimize some lambdas processing scenarios, though it’s memory intensive. |
TEMPLATE_RECURSIVE_INVOCATION_LIMIT org.trimou.engine.config.templateRecursiveInvocationLimit |
10 |
The limit of recursive template invocation (partials, template inheritance); 0 - recursive invocation is forbidden. |
SKIP_VALUE_ESCAPING org.trimou.engine.config.skipValueEscaping |
false |
If |
DEFAULT_FILE_ENCODING org.trimou.engine.config.defaultFileEncoding |
System property "file.encoding" |
The encoding every template locator should use if reading template from a file. System file encoding by default. |
TEMPLATE_CACHE_ENABLED org.trimou.engine.config.templateCacheEnabled |
true |
The template cache is enabled by default. If set to false every |
TEMPLATE_CACHE_EXPIRATION_TIMEOUT org.trimou.engine.config.templateCacheExpirationTimeout |
0 |
The template cache expiration timeout in seconds. Zero and negative values mean no timeout. The template cachec never expires by default. |
HANDLEBARS_SUPPORT_ENABLED org.trimou.engine.config.handlebarsSupportEnabled |
true |
Handlebars support is enabled by default. Right now only handlebars-like helpers are supported. |
REUSE_LINE_SEPARATOR_SEGMENTS org.trimou.engine.config.reuseLineSeparatorSegments |
true |
If set to |
ITERATION_METADATA_ALIAS org.trimou.engine.config.iterationMetadataAlias |
iter |
The alias for iteration metadata object available inside an iteration block. |
RESOLVER_HINTS_ENABLED org.trimou.engine.config.resolverHintsEnabled |
true |
If set to |
3.4. Basic i18n support
Trimou has a basic i18n support. There are some optional components provided to handle i18n requirements. All these components rely on org.trimou.engine.locale.LocaleSupport
implementation to get the current Locale
, see also LocaleSupport.
Type | Class | Description |
---|---|---|
Resolver |
|
Basic number formatting. |
Resolver |
|
Basic date and time formatting. |
Resolver |
|
Resolves localized messages. Unlike |
Helper |
|
This is an alternative to |
Helper |
|
The most flexible way of rendering localized messages. Supports message parameters and multiple resource bundles. |
Lambda |
|
Renders localized messages. Unlike |
3.4.1. DateTimeFormatResolver example
MustacheEngine engine = MustacheEngineBuilder
.newBuilder()
.setProperty(DateTimeFormatResolver.CUSTOM_PATTERN_KEY, "DD-MM-YYYY HH:mm") <1>
.addResolver(new DateTimeFormatResolver()) <2>
.build();
Mustache mustache = engine.getMustache("foo");
String output = mustache.render(ImmutableMap.<String, Object> of("now", new Date()));
-
DateTimeFormatResolver also supports custom formatting pattern
-
Manually add resolver
Now: {{now.formatCustom}}
results in something similar:
Now: 03-05-2013 22:05
3.5. Debugging
If you encounter a problem during template processing/rendering, try to:
-
configure Simple Logging Facade for Java (SLF4J) - increase the log level for
org.trimou
loggers -
enable debug mode - this disables the template cache and provides some more logging during template rendering (otherwise disabled due to performance)
-
implement your own MissingValueHandler - to handle variable miss during interpolation of a variable tag
-
use LogHelper - this might useful for production environments
4. How to extend
Basically, all the extension points are focused on MustacheEngine
configuration. Some components may be automatically added using the org.trimou.engine.config.ConfigurationExtension
and JDK service-provider loading facility. Others may be added manually via MustacheEngineBuilder
methods. See existing extensions to get acquainted with the basic principles.
Note
|
Automatic org.trimou.engine.config.ConfigurationExtension processing may be disabled per engine - see also MustacheEngineBuilder#omitServiceLoaderConfigurationExtensions() .
|
4.1. Resolver
Resolvers define the set of resolvable objects for your templates. The built-in set of resolvers should satisfy most of the basic requirements.
4.1.1. Custom resolvers
Warning
|
Implementing/adding a custom resolver may have serious impact on the engine functionality and performance. |
All resolvers have a priority and resolvers with higher priority are called first. Keep in mind that all resolvers must be thread-safe. There are two ways to extend the basic set of resolvers:
-
automatically via
org.trimou.engine.config.ConfigurationExtension
, -
you can also use
MustacheEngineBuilder.addResolver()
method.
Tip
|
trimou-extension-cdi extension provides CDIBeanResolver to lookup normal-scoped CDI beans with name. trimou-extension-servlet extension provides HttpServletRequestResolver to get the current Servlet request wrapper.
|
4.2. TemplateLocator
Template locators automatically locate the template contents for the given template identifier. The form of the template identifier is not defined, however in most cases the id will represent a template name, e.g. foo
and foo.html
, or virtual path like order/orderDetail
. The default virtual path separator is /
and can be configured via org.trimou.engine.locator.PathTemplateLocator.VIRTUAL_PATH_SEPARATOR_KEY
. Template locators may only be added with MustacheEngineBuilder.addTemplateLocator()
method.
There are three basic built-in implementations. org.trimou.engine.locator.FilesystemTemplateLocator
finds templates on the filesystem, within the given root directory (watch out, this wouldn’t be likely portable across various operating systems). org.trimou.engine.locator.ClassPathTemplateLocator
makes use of ClassLoader, either thread context class loader (TCCL) or custom CL set via constructor. org.trimou.engine.locator.MapTemplateLocator
is backed by a Map
. See javadoc for more configuration info.
Tip
|
Locators with higher priority are called first. |
Tip
|
trimou-extension-servlet extension provides org.trimou.servlet.locator.ServletContextTemplateLocator to be used in web apps deployed to a servlet container.
|
4.3. TextSupport
org.trimou.engine.text.TextSupport
is used to escape variable text if necessary (see also Escaping HTML). You can set the custom instance with org.trimou.engine.MustacheEngineBuilder.setTextSupport()
method. Implement your own logic to extend functionality or improve performance!
4.4. LocaleSupport
org.trimou.engine.locale.LocaleSupport
allows the engine and its components (e.g. resolvers) to get the current locale via getCurrentLocale()
. You can set the custom org.trimou.engine.locale.LocaleSupport
instance with org.trimou.engine.MustacheEngineBuilder.setLocaleSupport()
method.
4.5. MustacheListener
Any registered org.trimou.engine.listener.MustacheListener
receives notifications about template processing. In particular compilationFinished()
method is invoked when a template is compiled, renderingStarted()
and renderingFinished()
methods are invoked for each template rendering. parsingStarted()
is invoked right before a template is processed by the parser. Listeners are invoked in the order of their registration, except for renderingFinished()
method which is invoked in reverse order.
There are two ways to register a custom listener:
-
MustacheEngineBuilder.addMustacheListener()
method, -
automatically via
org.trimou.engine.config.ConfigurationExtension
(extension listeners are always registered after manually added listeners).
Note
|
Code inside a listener may throw an unchecked exception - this aborts further processing of template and no more listeners are invoked afterwards. |
4.6. Helper
org.trimou.handlebars.Helper
API is inspired by Handlebars but it’s not 100% compatible. Mainly it does not define "inverse" section, so for example if
helper doesn’t support else
block. On the other hand any helper is able to validate the tag definition (see Helper.validate()
) and fail fast if there’s invalid number of arguments etc.
A helper may be registered via MustacheEngineBuilder.registerHelper()
or MustacheEngineBuilder.registerHelpers()
methods. Note that each helper must be registered with a unique name. If there are more helpers registered with the same name an IllegalArgumentException
is thrown during engine build. There are some built-in helpers registered automatically.
Warning
|
The number of registered helpers should not affect the engine performance (unlike the number of registered resolvers). |
The main advantage of helpers is the ability to consume multiple parameters and optional hash map. Check out org.trimou.handlebars.Options
and the source of built-in helpers to see what helpers can do.
4.7. MissingValueHandler
org.trimou.engine.interpolation.MissingValueHandler
handles variable miss (no value found) during interpolation of a variable tag. By default org.trimou.engine.interpolation.NoOpMissingValueHandler
is used so that a miss does not result in any special operation. However you can set your own handler through the MustacheEngineBuilder.setMissingValueHandler()
method. There is also org.trimou.engine.interpolation.ThrowingExceptionMissingValueHandler
which throws an exception in case of a miss (actually it replaces deprecated configuration property EngineConfigurationKey#NO_VALUE_INDICATES_PROBLEM
).
4.8. KeySplitter
org.trimou.engine.interpolation.KeySplitter
is responsible for splitting a variable key. org.trimou.engine.interpolation.DotKeySplitter
which follows the dot notation is used by default. org.trimou.engine.interpolation.BracketDotKeySplitter
enables to use bracket notation and literals in variable keys. E.g. {{messages["my.message.key"]}}
. You can set your own splitter through the MustacheEngineBuilder.setKeySplitter()
method.
4.9. ComputingCacheFactory
org.trimou.engine.cache.ComputingCache
is a simple abstraction for thread-safe computing (lazy loading) cache. It’s used in some internal components (e.g. ReflectionResolver
) and may also be used in custom components too. org.trimou.engine.cache.ComputingCacheFactory
component is responsible for creating new instances of ComputingCache
. The default computing cache implementation is backed by com.google.common.cache.LoadingCache
.
4.10. IdentifierGenerator
org.trimou.engine.id.IdentifierGenerator
is used to generate identifiers for various components and use-cases (e.g. Mustache
, MustacheRenderingEvent
and one-off lambda names). There are some restrictions on the uniqueness of the generated id - see also the javadoc.
5. Extensions
5.1. CDI
<dependency>
<groupId>org.trimou</groupId>
<artifactId>trimou-extension-cdi</artifactId>
<version>${version.trimou}</version>
</dependency>
5.1.1. CDIBeanResolver
Tries to resolve a CDI bean with the given name (i.e. annotated with @Named
or with a @Named
stereotype).
5.1.2. Rendering context
The rendering scope is active during each rendering of a template, i.e. during Mustache.render()
invocation - there is exactly one bean instance per rendering which is destroyed after the rendering is finished. This could be useful in SE environments where usually only @ApplicationScoped
and @Dependent
built-in scopes are available. You can annotate your bean with org.trimou.cdi.context.RenderingScoped
to declare the rendering scope.
5.2. Servlets
At the moment only Servlet 3.x API is supported.
<dependency>
<groupId>org.trimou</groupId>
<artifactId>trimou-extension-servlet</artifactId>
<version>${version.trimou}</version>
</dependency>
5.2.1. ServletContextTemplateLocator
Locates the template anywhere in the web app. The root path must begin with a /
and is interpreted as relative to the current context root, or relative to the /META-INF/resources
directory of a JAR file inside the web application’s /WEB-INF/lib
directory.
MustacheEngineBuilder
.newBuilder()
.addTemplateLocator(new ServletContextTemplateLocator(10, "/WEB-INF/templates"))
.build();
5.2.2. HttpServletRequestResolver
Resolves a key of value request to HttpServletRequestWrapper
. Why the wrapper? Well, we just don’t think it’s the right thing to call the request object directly.
5.2.3. RequestLocaleSupport
Obtains the current locate from the current servlet request.
5.3. PrettyTime
<dependency>
<groupId>org.trimou</groupId>
<artifactId>trimou-extension-prettytime</artifactId>
<version>${version.trimou}</version>
</dependency>
5.3.1. PrettyTimeResolver
This resolver allows you to use PrettyTime date-formatting in your templates.
Note
|
The PrettyTimeResolver is automatically loaded if you place the extension jar on the classpath. |
MustacheEngine engine = MustacheEngineBuilder
.newBuilder()
.build();
Mustache mustache = engine.compileMustache("prettyTime","{{now.prettyTime}}");
String output = mustache.render(ImmutableMap.<String, Object> of("now", new Date()));
// Renders something similar:
// moments from now
5.3.2. PrettyTimeHelper
Developers are encouraged to use this helper instead of PrettyTimeResolver
to avoid the negative performance impact during interpolation. However, PrettyTimeResolver
is registered automatically through PrettyTimeConfigurationExtension
. So don’t forget to disable the resolver by means of org.trimou.prettytime.resolver.PrettyTimeResolver.ENABLED_KEY
, e.g. use org.trimou.prettytime.resolver.PrettyTimeResolver.enabled=false
in your properties file.
5.4. Minify
Minify extension allows you to minify your HTML and XML templates (or any other type of content if you provide your own org.trimou.minify.Minifier
implementation). Trimou integrates small and efficient HtmlCompressor library. There are two ways to minify the templates. It’s possible to register a special listener to minify templates before parsing/compilation or use a special lambda to minify some parts of the template contents.
Tip
|
org.trimou.minify.Minify helper methods are useful to create the default listeners and lambdas (i.e. if you don’t require some extra configuration).
|
Note
|
From the performance point of view: both listener and lambda decrease the size of the rendered template. However listeners may also improve the rendering performance (template is minified only once - before the compilation). Whereas lambdas will likely make rendering performance worse (part of the template is minified every time the lambda is invoked). |
<dependency>
<groupId>org.trimou</groupId>
<artifactId>trimou-extension-minify</artifactId>
<version>${version.trimou}</version>
</dependency>
5.4.1. MinifyListener
MustacheEngine engine = MustacheEngineBuilder
.newBuilder()
.addMustacheListener(Minify.htmlListener())
.build();
Mustache mustache = engine.compileMustache("minify_html","<html><body> <!-- My comment -->{{foo}} </body></html>");
String output = mustache.render(ImmutableMap.<String, Object> of("foo", "FOO"));
// Renders:
// <html><body> FOO </body></html>
Tip
|
It’s also possible to customize the underlying com.googlecode.htmlcompressor.compressor.HtmlCompressor instance - see also our MinifyListenerTest.
|
5.4.2. MinifyLambda
MustacheEngine engine = MustacheEngineBuilder
.newBuilder()
.build();
Mustache mustache = engine.compileMustache("minify_html_lambda","<html><body><!-- Remains -->{{#mini}}<!-- Will be removed --> FOO {{/mini}}</body></html>");
String output = mustache.render(ImmutableMap.<String, Object> of("mini", Minify.htmlLambda()));
// Renders:
// <html><body><!-- Remains --> FOO </body></html>
5.4.3. Minifier interface
You can also implement your own minifier and leverage existing infrastructure:
MustacheEngine engine = MustacheEngineBuilder
.newBuilder()
.addMustacheListener(Minify.customListener(new AbstractMinifier() {
@Override
public Reader minify(String mustacheName, Reader mustacheContents) {
return mustacheName.endsWith("html") ? mySuperMinification(Reader mustacheContents) : mustacheContents;
}
}))).build();
5.5. Gson
Gson extension brings some basic support for JSON format by means of google-gson APIs.
<dependency>
<groupId>org.trimou</groupId>
<artifactId>trimou-extension-gson</artifactId>
<version>${version.trimou}</version>
</dependency>
5.5.1. JsonElementResolver
org.trimou.gson.resolver.JsonElementResolver
makes it easier to work with com.google.gson.JsonElement
instances. It is automatically loaded if you place the extension jar on the classpath.
-
It allows you to access
JsonObject
properties via dot notation-
e.g. if foo is an instance of
JsonObject
thenfoo.bar
is translated tofoo.get("bar")
-
-
JsonArray
elements can be accessed via index-
e.g. if foo is an instance of
JsonArray
thenfoo.1
is translated tofoo.get(1)
-
JsonNull
is resolved as a null
and JsonPrimitive
is automatically unwrapped. E.g. foo.bar
in JsonObject
example is translated to foo.get("bar").getAsNumber()
if bar is an instance of a java.lang.Number
. This can be disabled - see JsonElementResolver
javadoc.
However unwrapping only works if JsonElementResolver
is involved! So for example if you iterate over ["Jim", true, 5]
, a special keyword unwrapThis
must be used so that the primitives are unwrapped:
{{#jsonArray}}{{unwrapThis}}{{/jsonArray}}
Example code
{
"firstName": "Jan",
"lastName": "Novy",
"age": 30,
"address": {
"street": "Nova",
"city": "Prague",
"state": "CZ",
"postalCode": "11000"
},
"phoneNumbers": [
{
"type": "home",
"number": "+42002012345"
},
{
"type": "mobile",
"number": "+420728000111"
}
]
}
Last name: {{lastName}} Street: {{address.street}} Phone numbers: {{#phoneNumbers}}{{number}}{{#iterHasNext}}, {{/iterHasNext}}{{/phoneNumbers}} Type of the first phone number: {{phoneNumbers.0.type}} Type of the second phone number: {{phoneNumbers.1.type}}
// Load the test data
JsonElement jsonElement = new JsonParser().parse(...);
// JsonElementResolver is loaded automatically
MustacheEngine engine = MustacheEngineBuilder
.newBuilder()
.build();
Mustache mustache = engine.getMustache("json_test.mustache");
String output = mustache.render(jsonElement);
Last name: Novy Street: Nova Phone numbers: +42002012345, +420728000111 Type of the first phone number: home Type of the second phone number: mobile
5.6. Spring MVC integration
This extension provides a basic Spring MVC integration.
<dependency>
<groupId>org.trimou</groupId>
<artifactId>trimou-extension-spring-mvc</artifactId>
<version>${version.trimou}</version>
</dependency>
5.7. Dropwizard
This extension provides a basic Dropwizard integration.
<dependency>
<groupId>org.trimou</groupId>
<artifactId>trimou-extension-dropwizard</artifactId>
<version>${version.trimou}</version>
</dependency>
5.7.1. TrimouViewRenderer
org.trimou.dropwizard.views.TrimouViewRenderer
is a io.dropwizard.views.ViewRenderer
implementation backed by Trimou. There’s a simple builder for convenience: org.trimou.dropwizard.views.TrimouViewRenderer.Builder
.
5.8. JDK 8
Trimou requires Java 7. However, there are some interesting parts in JDK8. Therefore we have a special extension for JDK8 users.
5.8.1. MapBackedComputingCacheFactory
A computing cache factory producing computing cache implementations backed by java.util.concurrent.ConcurrentHashMap
. This implementation is a bit faster than the default one using com.google.common.cache.LoadingCache
. On the other hand it does not support automatic timeout eviction and listeners. Moreover its size-based eviction is not so effective.
5.8.2. TimeFormatHelper
An alternative to DateTimeFormatHelper
which makes use of java.time package in JDK 8 (JSR-310). It supports new temporal types and should also be less resource-intensive.