Object wrappers - FreeMarker Manual

- Home
- Manual
- Java API

- Contribute
- Report a Bug
- Download

Manual
Previous Next

# Object wrappers

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:

    package com.example.myapp;

    public class Tupple<E1, E2> {
        public Tupple(E1 e1, E2 e2) { ... }
        public E1 getE1() { ... }
        public E2 getE2() { ... }
    }

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:

    package com.example.myapp.freemarker;

    ...

    public class TuppleAdapter extends WrappingTemplateModel implements TemplateSequenceModel,
            AdapterTemplateModel {

        private final Tupple<?, ?> tupple;

        public TuppleAdapter(Tupple<?, ?> 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;
        }

    }

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:

    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<?, ?>) obj, this);
            }

            return super.handleUnknownType(obj);
        }

    }

and then where you configure FreeMarker (about configuring, see here...) we plug our object wrapper in:

    // Where you initialize the cfg *singleton* (happens just once in the application life-cycle):
    cfg = new Configuration(Configuration.VERSION_2_3_22);
    ...
    cfg.setObjectWrapper(new MyAppObjectWrapper(cfg.getIncompatibleImprovements()));

or if you are configuring FreeMarker with `java.util.Properties` instead (and let's say it's also a `.properties` file):

    object_wrapper=com.example.myapp.freemarker.MyAppObjectWrapper(2.3.22)

Previous Next