blob: bef441d56afc7d900f17e8ce8fa27599c10cb2d7 [file] [log] [blame]
<!doctype html>
<!-- Generated by FreeMarker/Docgen from DocBook -->
<html lang="en" class="page-type-section">
<head prefix="og: http://ogp.me/ns#">
<meta charset="utf-8">
<title>Object wrappers - Apache FreeMarker Manual</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="format-detection" content="telephone=no">
<meta property="og:site_name" content="Apache FreeMarker Manual">
<meta property="og:title" content="Object wrappers">
<meta property="og:locale" content="en_US">
<meta property="og:url" content="https://freemarker.apache.org/docs/pgui_datamodel_objectWrapper.html">
<link rel="canonical" href="https://freemarker.apache.org/docs/pgui_datamodel_objectWrapper.html">
<link rel="icon" href="favicon.png" type="image/png">
<link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/css?family=Roboto:500,700,400,300|Droid+Sans+Mono">
<link rel="stylesheet" type="text/css" href="docgen-resources/docgen.min.css?1707770044859">
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/cookie-bar/cookiebar-latest.min.js"></script>
</head>
<body itemscope itemtype="https://schema.org/Code">
<meta itemprop="url" content="https://freemarker.apache.org/docs/">
<meta itemprop="name" content="Apache FreeMarker Manual">
<!--[if lte IE 9]>
<div class="oldBrowserWarning" style="display: block">
Unsupported web browser - Use a modern browser to view this website!
</div>
<![endif]--> <div class="oldBrowserWarning">
Unsupported web browser - Use a modern browser to view this website!
</div>
<div class="header-top-bg"><div class="site-width header-top"><div id="hamburger-menu" role="button"></div> <div class="logo">
<a href="https://freemarker.apache.org" role="banner"><img itemprop="image" src="logo.png" alt="FreeMarker"></a> </div>
<ul class="tabs"><li><a href="https://freemarker.apache.org/">Home</a></li><li class="current"><a href="index.html">Manual</a></li><li><a class="external" href="api/index.html">Java API</a></li></ul><ul class="secondary-tabs"><li><a class="tab icon-heart" href="https://freemarker.apache.org/contribute.html" title="Contribute"><span>Contribute</span></a></li><li><a class="tab icon-bug" href="https://issues.apache.org/jira/projects/FREEMARKER" title="Report a Bug"><span>Report a Bug</span></a></li><li><a class="tab icon-download" href="https://freemarker.apache.org/freemarkerdownload.html" title="Download"><span>Download</span></a></li></ul></div></div><div class="header-bottom-bg"><div class="site-width search-row"><a href="index.html" class="navigation-header">Manual</a><div class="navigation-header"></div><form method="get" class="search-form" action="search-results.html"><fieldset><legend class="sr-only">Search form</legend><label for="search-field" class="sr-only">Search query</label><input id="search-field" name="q" type="search" class="search-input" placeholder="Search" spellcheck="false" autocorrect="off" autocomplete="off"><button type="submit" class="search-btn"><span class="sr-only">Search</span></button></fieldset></form></div><div class="site-width breadcrumb-row"> <div class="breadcrumbs">
<ul class="breadcrumb" itemscope itemtype="http://schema.org/BreadcrumbList"><li class="step-0" itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem"><a class="label" itemprop="item" href="index.html"><span itemprop="name">Apache FreeMarker Manual</span></a></li><li class="step-1" itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem"><a class="label" itemprop="item" href="pgui.html"><span itemprop="name">Programmer&#39;s Guide</span></a></li><li class="step-2" itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem"><a class="label" itemprop="item" href="pgui_datamodel.html"><span itemprop="name">The Data Model</span></a></li><li class="step-3" itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem"><a class="label" itemprop="item" href="pgui_datamodel_objectWrapper.html"><span itemprop="name">Object wrappers</span></a></li></ul> </div>
<div class="bookmarks" title="Bookmarks"><span class="sr-only">Bookmarks:</span><ul><li><a href="alphaidx.html">Alpha. index</a></li><li><a href="gloss.html">Glossary</a></li><li><a href="dgui_template_exp.html#exp_cheatsheet">Expressions</a></li><li><a href="ref_builtins_alphaidx.html">?builtins</a></li><li><a href="ref_directive_alphaidx.html">#directives</a></li><li><a href="ref_specvar.html">.spec_vars</a></li><li><a href="app_faq.html">FAQ</a></li></ul></div></div></div> <div class="main-content site-width">
<div class="content-wrapper">
<div id="table-of-contents-wrapper" class="col-left">
<script>var breadcrumb = ["Apache FreeMarker Manual","Programmer\'s Guide","The Data Model","Object wrappers"];</script>
<script src="toc.js?1707770044859"></script>
<script src="docgen-resources/main.min.js?1707770044859"></script>
</div>
<div class="col-right"><div class="page-content"><div class="page-title"><div class="pagers top"><a class="paging-arrow previous" href="pgui_datamodel_node.html"><span>Previous</span></a><a class="paging-arrow next" href="pgui_config.html"><span>Next</span></a></div><div class="title-wrapper">
<h1 class="content-header header-section1" id="pgui_datamodel_objectWrapper" itemprop="headline">Object wrappers</h1>
</div></div><div class="page-menu">
<div class="page-menu-title">Page Contents</div>
<ul><li><a class="page-menu-link" href="#pgui_datamodel_defaultObjectWrapper" data-menu-target="pgui_datamodel_defaultObjectWrapper">The default object wrapper</a></li><li><a class="page-menu-link" href="#pgui_datamodel_customObjectWrappingExample" data-menu-target="pgui_datamodel_customObjectWrappingExample">Custom object wrapping example</a></li></ul> </div><p>The object wrapper is an object that implements the
<code class="inline-code">freemarker.template.ObjectWrapper</code> interface. It&#39;s
purpose is to implement a mapping between Java objects (like
<code class="inline-code">String</code>-s, <code class="inline-code">Map</code>-s,
<code class="inline-code">List</code>-s, instances of your application specific
classes, etc.) and FTL&#39;s type system. With other words, it specifies
how the templates will see the Java objects of the data-model
(including the return value of Java methods called from the template).
The object wrapper is plugged into the
<code class="inline-code">Configuration</code> as its
<code class="inline-code">object_wrapper</code> setting (or with
<code class="inline-code">Configuration.setObjectWrapper</code>).</p><p>FTL&#39;s type system is technically represented by the
<code class="inline-code">TemplateModel</code> sub-interfaces that were introduced
earlier (<code class="inline-code">TemplateScalarModel</code>,
<code class="inline-code">TemplateHashModel</code>,
<code class="inline-code">TemplateSequenceModel</code>, etc). To map a Java object
to FTL&#39;s type system, object wrapper&#39;s <code class="inline-code">TemplateModel
wrap(java.lang.Object obj)</code> method will be called.</p><p>Sometimes FreeMarker needs to reverse this mapping, in which
case the <code class="inline-code">ObjectWrapper</code>&#39;s <code class="inline-code">Object
unwrap(TemplateModel)</code> method is called (or some other
variation of that, but see the API documentation for such details).
This last operation is in
<code class="inline-code">ObjectWrapperAndUnwrapper</code>, the subinterface of
<code class="inline-code">ObjectWrapper</code>. Most real world object wrappers will
implement <code class="inline-code">ObjectWrapperAndUnwrapper</code>.</p><p>Here&#39;s how wrapping Java objects that contain other objects
(like a <code class="inline-code">Map</code>, a <code class="inline-code">List</code>, an array,
or an object with some JavaBean properties) usually work. Let&#39;s say,
an object wrapper wraps an <code class="inline-code">Object[]</code> array into some
implementation of the <code class="inline-code">TemplateSquenceModel</code>
interface. When FreeMarker needs an item from that FTL sequence, it
will call <code class="inline-code">TemplateSquenceModel.get(int index)</code>. The
return type of this method is <code class="inline-code">TemplateModel</code>, that
is, the <code class="inline-code">TemplateSquenceModel</code> implementation not
only have to get the <code class="inline-code">Object</code> from the given index of
the array, it&#39;s also responsible for wrapping that value before
returning it. To solve that, a typical
<code class="inline-code">TemplateSquenceModel</code> implementation will store the
<code class="inline-code">ObjectWrapper</code> that has cerated it, and then invoke
that <code class="inline-code">ObjectWrapper</code> to wrap the contained value. The
same logic stands for <code class="inline-code">TemplateHashModel</code> or for any
other <code class="inline-code">TemplateModel</code> that&#39;s a container for further
<code class="inline-code">TemplateModel</code>-s. Hence, usually, no mater how deep
the value hierarchy is, all values will be wrapped by the same single
<code class="inline-code">ObjectWrapper</code>. (To create
<code class="inline-code">TemplateModel</code> implementations that follow this
idiom, you can use the
<code class="inline-code">freemarker.template.WrappingTemplateModel</code> as base
class.)</p><p>The data-model itself (the root variable) is a
<code class="inline-code">TemplateHashModel</code>. The root object that you specify
to <code class="inline-code">Template.process</code> will be wrapped with the object
wrapper specified in the <code class="inline-code">object_wrapper</code>
configuration setting, which must yield a
<code class="inline-code">TemplateHashModel</code>. From then on, the wrapping of
the contained values follow the logic described earlier (i.e., the
container is responsible for wrapping its children).</p><p>Well behaving object wrappers bypass objects that already
implement <code class="inline-code">TemplateModel</code> as is. So if you put an
object into the data-model that already implements
<code class="inline-code">TemplateModel</code> (or you return as such object from a
Java method that&#39;s called from the template, etc.), then you can avoid
actual object wrapping. You do this usually when you are creating a
value specifically to be accessed from a template. Thus, you avoid
much of the object wrapping performance overhead, also you can control
exactly what will the template see (not depending on the mapping
strategy of the current object wrapper). A frequent application of
this trick is using a
<code class="inline-code">freemarker.template.SimpleHash</code> as the data-model
root (rather than a <code class="inline-code">Map</code>), by filling it with
<code class="inline-code">SimpleHash</code>&#39;s <code class="inline-code">put</code> method (that&#39;s
important, so it won&#39;t have to copy an existing <code class="inline-code">Map</code>
that you have already filled). This speeds up top-level data-model
variable access.</p>
<h2 class="content-header header-section2" id="pgui_datamodel_defaultObjectWrapper">The default object wrapper</h2>
<p>The default of the <code class="inline-code">object_wrapper</code>
<code class="inline-code">Configuration</code> setting is a
<code class="inline-code">freemarker.template.DefaultObjectWrapper</code>
singleton. Unless you have very special requirements, it&#39;s
recommended to use this object wrapper, or an instance of a
<code class="inline-code">DefaultObjectWrapper</code> subclass of yours.</p>
<p>It recognizes most basic Java types, like
<code class="inline-code">String</code>, <code class="inline-code">Number</code>,
<code class="inline-code">Boolean</code>, <code class="inline-code">Date</code>,
<code class="inline-code">List</code> (and in general all kind of
<code class="inline-code">java.util.Collection</code>-s), arrays,
<code class="inline-code">Map</code>, etc., and wraps them into the naturally
matching <code class="inline-code">TemplateModel</code> interfaces. It will also
wrap W3C DOM nodes with
<code class="inline-code">freemarker.ext.dom.NodeModel</code>, so you can
conveniently traverse XML as <a href="xgui.html">described in its
own chapter</a>). For Jython objects, it will delegate to
<code class="inline-code">freemarker.ext.jython.JythonWrapper</code>. For all
other objects, it will invoke <code class="inline-code">BeansWrapper.wrap</code>
(the super class&#39;s method), which will expose the JavaBean
properties of the objects as hash items (like
<code class="inline-code">myObj.foo</code> in FTL will call
<code class="inline-code">getFoo()</code> behind the scenes), and will also expose
the public methods (JavaBean actions) of the object (like
<code class="inline-code">myObj.bar(1, 2)</code> in FTL will call a method). (For
more information about BeansWrapper, <a href="pgui_misc_beanwrapper.html">see its own section</a>.)</p>
<p>Some further details that&#39;s worth mentioning about
<code class="inline-code">DefaultObjectWrapper</code>:</p>
<ul>
<li>
<p>You shouldn&#39;t use its constructor usually, instead create
it using a <code class="inline-code">DefaultObjectWrapperBuilder</code>. This
allows FreeMarker to use singletons.</p>
</li>
<li>
<p><a name="topic.defaultObjectWrapperIcI"></a><code class="inline-code">DefaultObjectWrapper</code> has an
<code class="inline-code">incompatibleImprovements</code> property, that&#39;s
highly recommended to set it to a high value (see the <a href="https://freemarker.apache.org/docs/api/freemarker/template/DefaultObjectWrapper.html#DefaultObjectWrapper-freemarker.template.Version-">API
documentation</a> for the effects). How to set it:</p>
<ul>
<li>
<p>If you have set the
<code class="inline-code">incompatible_improvements</code> setting
<em>of the <code class="inline-code">Configuration</code></em>
to 2.3.22 or higher, and you didn&#39;t set the
<code class="inline-code">object_wrapper</code> setting (so it had
remained on its default value), then you have to do nothing,
as it already uses a <code class="inline-code">DefaultObjectWrapper</code>
singleton with the equivalent
<code class="inline-code">incompatibleImprovements</code> property
value.</p>
</li>
<li>
<p><a name="topic.setDefaultObjectWrapperIcIIndividually"></a>Otherwise you have to set the
<code class="inline-code">incompatibleImprovements</code> independently of
the <code class="inline-code">Configuration</code>. Depending on how you
create/set the <code class="inline-code">ObjectWrapper</code>, it can be
done like this:</p>
<ul>
<li>
<p>If you are using the builder API:</p>
<div class="code-block role-unspecified">
<pre class="code-block-body">... = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_27).build()</pre> </div>
</li>
<li>
<p>Or, if you are using the constructor:</p>
<div class="code-block role-unspecified">
<pre class="code-block-body">... = new DefaultObjectWrapper(Configuration.VERSION_2_3_27)</pre> </div>
</li>
<li>
<p>Or, if you are using the
<code class="inline-code">object_wrapper</code> property
(<code class="inline-code">*.properties</code> file or
<code class="inline-code">java.util.Properties</code> object):</p>
<div class="code-block role-unspecified">
<pre class="code-block-body">object_wrapper=DefaultObjectWrapper(2.3.27)</pre> </div>
</li>
<li>
<p>Or, if you are configuring the
<code class="inline-code">object_wrapper</code> through a
<code class="inline-code">FreemarkerServlet</code> with an
<code class="inline-code">init-param</code> in
<code class="inline-code">web.xml</code>:</p>
<div class="code-block role-unspecified">
<pre class="code-block-body">&lt;init-param&gt;
&lt;param-name&gt;object_wrapper&lt;/param-name&gt;
&lt;param-value&gt;DefaultObjectWrapper(2.3.27)&lt;/param-value&gt;
&lt;/init-param&gt;</pre> </div>
</li>
</ul>
</li>
</ul>
</li>
<li>
<p>In new or properly test-covered projects it&#39;s also
recommended to set the
<code class="inline-code">forceLegacyNonListCollections</code> property to
<code class="inline-code">false</code>. If you are using
<code class="inline-code">.properties</code> or
<code class="inline-code">FreemarkerServlet</code> init-params or such, that
will look like <code class="inline-code">DefaultObjectWrapper(2.3.22,
forceLegacyNonListCollections=false)</code>, while with the
Java API you call
<code class="inline-code">setForceLegacyNonListCollections(false)</code> on
the <code class="inline-code">DefaultObjectWrapperBuilder</code> object before
calling <code class="inline-code">build()</code>.</p>
</li>
<li>
<p>The most common way of customizing
<code class="inline-code">DefaultObjectWrapper</code> is overriding its
<code class="inline-code">handleUnknownType</code> method.</p>
</li>
</ul>
<h2 class="content-header header-section2" id="pgui_datamodel_customObjectWrappingExample">Custom object wrapping example</h2>
<p>Let&#39;s say you have an application-specific class like
this:</p>
<div class="code-block role-unspecified">
<pre class="code-block-body">package com.example.myapp;
public class Tupple&lt;E1, E2&gt; {
public Tupple(E1 e1, E2 e2) { ... }
public E1 getE1() { ... }
public E2 getE2() { ... }
}</pre> </div>
<p>You want templates to see this as a sequence of length 2, so
that you can do things like <code class="inline-code">someTupple[1]</code>,
<code class="inline-code">&lt;#list someTupple
<em class="code-color">...</em>&gt;</code>, or
<code class="inline-code">someTupple?size</code>. For that you need to create a
<code class="inline-code">TemplateSequenceModel</code> implementation that adapts
a <code class="inline-code">Tupple</code> to the
<code class="inline-code">TempateSequenceMoldel</code> interface:</p>
<div class="code-block role-unspecified">
<pre class="code-block-body">package com.example.myapp.freemarker;
...
public class TuppleAdapter extends WrappingTemplateModel implements TemplateSequenceModel,
AdapterTemplateModel {
private final Tupple&lt;?, ?&gt; tupple;
public TuppleAdapter(Tupple&lt;?, ?&gt; tupple, ObjectWrapper ow) {
super(ow); // coming from WrappingTemplateModel
this.tupple = tupple;
}
@Override // coming from TemplateSequenceModel
public int size() throws TemplateModelException {
return 2;
}
@Override // coming from TemplateSequenceModel
public TemplateModel get(int index) throws TemplateModelException {
switch (index) {
case 0: return wrap(tupple.getE1());
case 1: return wrap(tupple.getE2());
default: return null;
}
}
@Override // coming from AdapterTemplateModel
public Object getAdaptedObject(Class hint) {
return tupple;
}
}</pre> </div>
<p>Regarding the classes and interfaces:</p>
<ul>
<li>
<p><code class="inline-code">TemplateSequenceModel</code>: This is why the
template will see this as a sequence</p>
</li>
<li>
<p><code class="inline-code">WrappingTemplateModel</code>: Just a
convenience class, used for <code class="inline-code">TemplateModel</code>-s
that do object wrapping themselves. That&#39;s normally only needed
for objects that contain other objects. See the
<code class="inline-code">wrap(<em class="code-color">...</em>)</code> calls
above.</p>
</li>
<li>
<p><code class="inline-code">AdapterTemplateModel</code>: Indicates that
this template model adapts an already existing object to a
<code class="inline-code">TemplateModel</code> interface, thus unwrapping
should give back that original object.</p>
</li>
</ul>
<p>Lastly, we tell FreeMarker to wrap <code class="inline-code">Tupple</code>-s
with the <code class="inline-code">TuppleAdapter</code> (alternatively, you could
wrap them manually before passing them to FreeMarker). For that,
first we create a custom object wrapper:</p>
<div class="code-block role-unspecified">
<pre class="code-block-body">package com.example.myapp.freemarker;
...
public class MyAppObjectWrapper extends DefaultObjectWrapper {
public MyAppObjectWrapper(Version incompatibleImprovements) {
super(incompatibleImprovements);
}
@Override
protected TemplateModel handleUnknownType(final Object obj) throws TemplateModelException {
if (obj instanceof Tupple) {
return new TuppleAdapter((Tupple&lt;?, ?&gt;) obj, this);
}
return super.handleUnknownType(obj);
}
}</pre> </div>
<p>and then where you configure FreeMarker (<a href="pgui_config.html">about configuring, see here...</a>) we plug
our object wrapper in:</p>
<div class="code-block role-unspecified">
<pre class="code-block-body">// Where you initialize the cfg *singleton* (happens just once in the application life-cycle):
cfg = new Configuration(Configuration.VERSION_2_3_27);
...
cfg.setObjectWrapper(new MyAppObjectWrapper(cfg.getIncompatibleImprovements()));</pre> </div>
<p>or if you are configuring FreeMarker with
<code class="inline-code">java.util.Properties</code> instead (and let&#39;s say it&#39;s
also a <code class="inline-code">.properties</code> file):</p>
<div class="code-block role-unspecified">
<pre class="code-block-body">object_wrapper=com.example.myapp.freemarker.MyAppObjectWrapper(2.3.27)</pre> </div>
<div class="bottom-pagers-wrapper"><div class="pagers bottom"><a class="paging-arrow previous" href="pgui_datamodel_node.html"><span>Previous</span></a><a class="paging-arrow next" href="pgui_config.html"><span>Next</span></a></div></div></div></div> </div>
</div>
<div class="site-footer"><div class="site-width"><div class="footer-top"><div class="col-left sitemap"><div class="column"><h3 class="column-header">Overview</h3><ul><li><a href="https://freemarker.apache.org/">What is FreeMarker?</a></li><li><a href="https://freemarker.apache.org/freemarkerdownload.html">Download</a></li><li><a href="app_versions.html">Version history</a></li><li><a href="app_faq.html">FAQ</a></li><li><a itemprop="license" href="app_license.html">License</a></li><li><a href="https://privacy.apache.org/policies/privacy-policy-public.html">Privacy policy</a></li></ul></div><div class="column"><h3 class="column-header">Often used / Reference</h3><ul><li><a href="https://try.freemarker.apache.org/">Try template online</a></li><li><a href="dgui_template_exp.html#exp_cheatsheet">Expressions cheatsheet</a></li><li><a href="ref_directive_alphaidx.html">#directives</a></li><li><a href="ref_builtins_alphaidx.html">?built_ins</a></li><li><a href="ref_specvar.html">.special_vars</a></li><li><a href="api/freemarker/core/Configurable.html#setSetting-java.lang.String-java.lang.String-">Configuration settings</a></li></ul></div><div class="column"><h3 class="column-header">Community</h3><ul><li><a href="https://github.com/apache/freemarker">Github project page</a></li><li><a href="https://issues.apache.org/jira/projects/FREEMARKER">Report a bug</a></li><li><a href="https://freemarker.apache.org/report-security-vulnerabilities.html">Report security vulnerability</a></li><li><a href="https://stackoverflow.com/questions/ask?tags=freemarker">Get help on StackOverflow</a></li><li><a href="https://twitter.com/freemarker">Announcements on Twitter</a></li><li><a href="https://freemarker.apache.org/mailing-lists.html">Discuss on mailing lists</a></li></ul></div></div><div class="col-right"><ul class="social-icons"><li><a class="github" href="https://github.com/apache/freemarker">Github</a></li><li><a class="twitter" href="https://twitter.com/freemarker">Twitter</a></li><li><a class="stack-overflow" href="https://stackoverflow.com/questions/ask?tags=freemarker">Stack Overflow</a></li></ul><a class="xxe" href="http://www.xmlmind.com/xmleditor/" rel="nofollow" title="Edited with XMLMind XML Editor"><span>Edited with XMLMind XML Editor</span></a></div></div><div class="footer-bottom"> <p class="last-generated">
Last generated:
<time itemprop="dateModified" datetime="2024-02-12T20:34:04Z" title="Monday, February 12, 2024 at 8:34:04 PM Greenwich Mean Time">2024-02-12 20:34:04 GMT</time>, for Freemarker 2.3.32 </p>
<p class="copyright">
© <span itemprop="copyrightYear">1999</span>–2024
<a itemtype="http://schema.org/Organization" itemprop="copyrightHolder" href="https://apache.org/">The Apache Software Foundation</a>. Apache FreeMarker, FreeMarker, Apache Incubator, Apache, the Apache FreeMarker logo are trademarks of The Apache Software Foundation. All other marks mentioned may be trademarks or registered trademarks of their respective owners. </p>
</div></div></div></body>
</html>