Object wrappers
Page Contents
The object wrapper is an object that implements the freemarker.template.ObjectWrapper interface. It's purpose is to implement a mapping between Java objects (like String-s, Map-s, List-s, instances of your application specific classes, etc.) and FTL'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 Configuration as its object_wrapper setting (or with Configuration.setObjectWrapper).
FTL's type system is technically represented by the TemplateModel sub-interfaces that were introduced earlier (TemplateScalarModel, TemplateHashMode, TemplateSequenceModel, etc). To map a Java object to FTL's type system, object wrapper's TemplateModel wrap(java.lang.Object obj) method will be called.
Sometimes FreeMarker needs to reverse this mapping, in which case the ObjectWrapper's Object unwrap(TemplateModel) method is called (or some other variation of that, but see the API documentation for such details). This last operation is in ObjectWrapperAndUnwrapper, the subinterface of ObjectWrapper. Most real world object wrappers will implement ObjectWrapperAndUnwrapper.
Here's how wrapping Java objects that contain other objects (like a Map, a List, an array, or an object with some JavaBean properties) usually work. Let's say, an object wrapper wraps an Object[] array into some implementation of the TemplateSquenceModel interface. When FreeMarker needs an item from that FTL sequence, it will call TemplateSquenceModel.get(int index). The return type of this method is TemplateModel, that is, the TemplateSquenceModel implementation not only have to get the Object from the given index of the array, it's also responsible for wrapping that value before returning it. To solve that, a typical TemplateSquenceModel implementation will store the ObjectWrapper that has cerated it, and then invoke that ObjectWrapper to wrap the contained value. The same logic stands for TemplateHashModel or for any other TemplateModel that's a container for further TemplateModel-s. Hence, usually, no mater how deep the value hierarchy is, all values will be wrapped by the same single ObjectWrapper. (To create TemplateModel implementations that follow this idiom, you can use the freemarker.template.WrappingTemplateModel as base class.)
The data-model itself (the root variable) is a TemplateHashModel. The root object that you specify to Template.process will be wrapped with the object wrapper specified in the object_wrapper configuration setting, which must yield a TemplateHashModel. From then on, the wrapping of the contained values follow the logic described earlier (i.e., the container is responsible for wrapping its children).
Well behaving object wrappers bypass objects that already implement TemplateModel as is. So if you put an object into the data-model that already implements TemplateModel (or you return as such object from a Java method that'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 freemarker.template.SimpleHash as the data-model root (rather than a Map), by filling it with SimpleHash's put method (that's important, so it won't have to copy an existing Map that you have already filled). This speeds up top-level data-model variable access.
The default object wrapper
The default of the object_wrapper Configuration setting is a freemarker.template.DefaultObjectWrapper singleton. Unless you have very special requirements, it's recommended to use this object wrapper, or an instance of a DefaultObjectWrapper subclass of yours.
It recognizes most basic Java types, like String, Number, Boolean, Date, List (and in general all kind of java.util.Collection-s), arrays, Map, etc., and wraps them into the naturally matching TemplateModel interfaces. It will also wrap W3C DOM nodes with freemarker.ext.dom.NodeModel, so you can conveniently traverse XML as described in its own chapter). For Jython objects, it will delegate to freemarker.ext.jython.JythonWrapper. For all other objects, it will invoke BeansWrapper.wrap (the super class's method), which will expose the JavaBean properties of the objects as hash items (like myObj.foo in FTL will call getFoo() behind the scenes), and will also expose the public methods (JavaBean actions) of the object (like myObj.bar(1, 2) in FTL will call a method). (For more information about BeansWrapper, see its own section.)
Some further details that's worth mentioning about DefaultObjectWrapper:
-
You shouldn't use its constructor usually, instead create it using a DefaultObjectWrapperBuilder. This allows FreeMarker to use singletons.
-
DefaultObjectWrapper has an incompatibleImprovements property, that's highly recommended to set to 2.3.22 or higher (see the API documentation for the effects). How to set it:
-
If you have set the incompatible_improvements setting of the Configuration to 2.3.22 or higher, and you didn't set the object_wrapper setting (so it had remained on its default value), then you have to do nothing, as it already uses a DefaultObjectWrapper singleton with the equivalent incompatibleImprovements property value.
-
Otherwise you have to set the incompatibleImprovements independently of the Configuration. Depending on how you create/set the ObjectWrapper, it can be done like this (let's assume you want incompatibleImprovements 2.3.22):
-
If you are using the builder API:
... = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_22).build()
-
Or, if you are using the constructor:
... = new DefaultObjectWrapper(Configuration.VERSION_2_3_22)
-
Or, if you are using the object_wrapper property (*.properties file or java.util.Properties object):
object_wrapper=DefaultObjectWrapper(2.3.21)
-
Or, if you are configuring the object_wrapper through a FreemarkerServlet with an init-param in web.xml:
<init-param> <param-name>object_wrapper</param-name> <param-value>DefaultObjectWrapper(2.3.21)</param-value> </init-param>
-
-
-
In new or properly test-covered projects it's also recommended to set the forceLegacyNonListCollections property to false. If you are using .properties or FreemarkerServlet init-params or such, that will look like DefaultObjectWrapper(2.3.22, forceLegacyNonListCollections=false), while with the Java API you call setForceLegacyNonListCollections(false) on the DefaultObjectWrapperBuilder object before calling build().
-
The most common way of customizing DefaultObjectWrapper is overriding its handleUnknownType method.
Custom object wrapping example
Let's say you have an application-specific class like this:
| |||
You want templates to see this as a sequence of length 2, so that you can do things like someTupple[1], <#list someTupple ...>, or someTupple?size. For that you need to create a TemplateSequenceModel implementation that adapts a Tupple to the TempateSequenceMoldel interface:
| |||
Regarding the classes and interfaces:
-
TemplateSequenceModel: This is why the template will see this as a sequence
-
WrappingTemplateModel: Just a convenience class, used for TemplateModel-s that do object wrapping themselves. That's normally only needed for objects that contain other objects. See the wrap(...) calls above.
-
AdapterTemplateModel: Indicates that this template model adapts an already existing object to a TemplateModel interface, thus unwrapping should give back that original object.
Lastly, we tell FreeMarker to wrap Tupple-s with the TuppleAdapter (alternatively, you could wrap them manually before passing them to FreeMarker). For that, first we create a custom object wrapper:
| |||
and then where you configure FreeMarker (about configuring, see here...) we plug our object wrapper in:
| |||
or if you are configuring FreeMarker with java.util.Properties instead (and let's say it's also a .properties file):
| |||