Bean wrapper - FreeMarker Manual

- Home
- Manual
- Java API

- Contribute
- Report a Bug
- Download

Manual
Previous Next

# Bean wrapper

Note:

Using `BeansWrapper` directly is not recommended anymore. Use its subclass, `DefaultObjectWrapper` instead, though by ensuring that its `incompatibleImprovements` property is at least 2.3.22. `DefaultObjectWrapper` gives cleaner data-model (less confusing multi-type FTL values) and is usually faster. See more about DefaultObjectWrapper here...

The `freemarker.ext.beans.BeansWrapper` is an object wrapper that was originally added to FreeMarker so arbitrary POJO-s (Plain Old Java Objects) can be wrapped into `TemplateModel` interfaces. Since then it has became the normal way of doing things, and in fact the `DefaultObjectWrapper` itself is a `BeansWrapper` extension. So everything described here goes for the `DefaultObjectWrapper` too, except that the `DefaultObjectWrapper` will wrap `String`, `Number`, `Date`, `array`, `Collection` (like `List`), `Map`, `Boolean` and `Iterator` objects with the `freemarker.template.SimpleXxx` classes, and W3C DOM nodes with `freemarker.ext.dom.NodeModel` (more about wrapped W3C DOM...), so for those the above described rules doesn't apply.

You will want to use `BeansWrapper` instead of `DefaultObjectWrapper` when any of these stands:

- The `Collection`-s and `Map`-s of the model should be allowed to be modified during template execution. (`DefaultObjectWrapper` prevents that, since it creates a copy of the collections when they are wrapped, and the copies will be read-only.)

- If the identity of the `array`, `Collection` and `Map` objects must be kept when they are passed to a wrapped object's method in the template. That is, if those methods must get exactly the same object that was earlier wrapped.

- If the Java API of the earlier listed classes (`String`, `Map`, `List` ...etc) should be visible for the templates. Even with `BeansWrapper`, they are not visible by default, but it can be achieved by setting the exposure level (see later). Note that this is usually a bad practice; try to use the built-ins (like `foo?size`, `foo?upper_case`, `foo?replace('_', '-')` ...etc) instead of the Java API.

Below is a summary of the `TemplateModel`-s that the `BeansWrapper` creates. Let's assume that the object is called `obj` before wrapping, and `model` after wrapping for the sake of the following discussion.

## TemplateHashModel functionality

Every object will be wrapped into a `TemplateHashModel` that will expose JavaBeans properties and methods of the object. This way, you can use `model.foo` in the template to invoke `obj.getFoo()` or `obj.isFoo()` methods. (Note that public fields are not visible directly; you must write a getter method for them.) Public methods are also retrievable through the hash model as template method models, therefore you can use the `model.doBar()` to invoke `object.doBar()`. More on this on discussion of method model functionality.

If the requested key can not be mapped to a bean property or method, the framework will attempt to locate the so-called "generic get method", that is a method with signature public `any-return-type get(String)` or public `any-return-type get(Object)` and invoke that method with the requested key. Note that this allows convenient access to mappings in a `java.util.Map` and similar classes - as long as the keys of the map are `String`s and some property or method name does not shadow the mapping. (There is a solution to avoid shadowing, read on.) Also note that the models for `java.util.ResourceBundle` objects use the `getObject(String)` as the generic get method.

If you call `setExposeFields(true)` on a `BeansWrapper` instance, it will also expose public, non-static fields of classes as hash keys and values. I.e. if `foo` is a public, non-static field of the class `Bar`, and `bar` is a template variable wrapping an instance of `Bar`, then `bar.foo` expression will evaluate as the value of the field `foo` of the `bar` object. The public fields in all superclasses of the class are also exposed.

## A word on security

By default, you will not be able to access several methods that are considered not safe for templating. For instance, you can't use synchronization methods (`wait`, `notify`, `notifyAll`), thread and thread group management methods (`stop`, `suspend`, `resume`, `setDaemon`, `setPriority`), reflection (`Field` `setXxx` methods, `Method.invoke`, `Constructor.newInstance`, `Class.newInstance`, `Class.getClassLoader` etc.) and various dangerous methods in `System` and `Runtime` classes (`exec`, `exit`, `halt`, `load`, etc.). The `BeansWrapper` has several security levels (called "levels of method exposure"), and the default called `EXPOSE_SAFE` is probably suited for most applications. There is a no-safeguard level called `EXPOSE_ALL` that allows you to call even the above unsafe methods, and a strict level `EXPOSE_PROPERTIES_ONLY` that will expose only bean property getters. Finally, there is a level named `EXPOSE_NOTHING` that will expose no properties and no methods. The only data you will be able to access through hash model interface in this case are items in maps and resource bundles, as well as objects returned from a call to generic `get(Object)` or `get(String)` methods - provided the affected objects have such method.

## TemplateScalarModel functionality

Models for `java.lang.String` objects will implement `TemplateScalarModel` whose `getAsString()` method simply delegates to `toString()`. Note that wrapping `String` objects into Bean wrappers provides much more functionality than just them being scalars: because of the hash interface described above, the models that wrap `String`s also provide access to all `String` methods (`indexOf`, `substring`, etc.), though most of them has native FreeMarker equivalent which are better to use (`s?index_of(n)`, `s[start..<end]`, etc).

## TemplateNumberModel functionality

Model wrappers for objects that are instances of `java.lang.Number` implement `TemplateNumberModel` whose `getAsNumber()` method returns the wrapped number object. Note that wrapping `Number` objects into Bean wrappers provides much more functionality than just them being number models: because of the hash interface described above, the models that wrap `Number`s also provide access to all their methods.

## TemplateCollectionModel functionality

Model wrappers for native Java arrays and all classes that implement `java.util.Collection` will implement `TemplateCollectionModel` and thus gain the additional capability of being usable through `list` directive.

## TemplateSequenceModel functionality

Model wrappers for native Java arrays and all classes that implement `java.util.List` will implement `TemplateSequenceModel` and thus their elements will be accessible by index using the `model[i]` syntax. You can also query the length of the array or the size of the list using the `model?size` built-in.

Also, every method that takes a single parameter that is assignable through reflective method invocation from a `java.lang.Integer` (these are `int`, `long`, `float`, `double`, `java.lang.Object`, `java.lang.Number`, and `java.lang.Integer`) also implements this interface. What this means is that you have a convenient way for accessing the so-called indexed bean properties: `model.foo[i]` will translate into `obj.getFoo(i)`.

## TemplateMethodModel functionality

All methods of an object are represented as `TemplateMethodModelEx` objects accessible using a hash key with method name on their object's model. When you call a method using `model.method(arg1, arg2, ...)` the arguments are passed to the method as template models. The method will first try to unwrap them - see below for details about unwrapping. These unwrapped arguments are then used for the actual method call. In case the method is overloaded, the most specific method will be selected using the same rules that are used by the Java compiler to select a method from several overloaded methods. In case that no method signature matches the passed parameters, or that no method can be chosen without ambiguity, a `TemplateModelException` is thrown.

Methods of return type `void` return `TemplateModel.NOTHING`, so they can be safely called with the `${obj.method(args)}` syntax.

Models for instances of `java.util.Map` also implement `TemplateMethodModelEx` as a means for invoking their `get()` method. As it was discussed previously, you can use the hash functionality to access the "get" method as well, but it has several drawbacks: it's slower because first property and method names are checked for the key; keys that conflict with property and method names will be shadowed by them; finally you can use `String` keys only with that approach. In contrast, invoking `model(key)` translates to `model.get(key)` directly: it's faster because there's no property and method name lookup; it is subject to no shadowing; and finally it works for non-String keys since the argument is unwrapped just as with ordinary method calls. In effect, `model(key)` on a `Map` is equal to `model.get(key)`, only shorter to write.

Models for `java.util.ResourceBundle` also implement `TemplateMethodModelEx` as a convenient way of resource access and message formatting. A single-argument call to a bundle will retrieve the resource with the name that corresponds to the `toString()` value of the unwrapped argument. A multiple-argument call to a bundle will also retrieve the resource with the name that corresponds to the `toString()` value of the unwrapped argument, but it will use it as a format pattern and pass it to `java.text.MessageFormat` using the unwrapped values of second and later arguments as formatting parameters. `MessageFormat` objects will be initialized with the locale of the bundle that originated them.

## Unwrapping rules

When invoking a Java method from a template, its arguments need to be converted from template models back to Java objects. Assuming that the target type (the declared type of the method's formal parameter) is denoted as `T`, the following rules are tried in the following order:

- If the model is the null model for this wrapper, Java `null` is returned.

- If the model implements `AdapterTemplateModel`, the result of `model.getAdaptedObject(T)` is returned if it is instance of `T` or it is a number and can be converted to `T` using numeric coercion as described three bullets lower. All models created by the BeansWrapper are themselves AdapterTemplateModel implementations, so unwrapping a model that was created by BeansWrapper for an underlying Java object always yields the original Java object.

- If the model implements the deprecated `WrapperTemplateModel`, the result of `model.getWrappedObject()` is returned if it is instance of `T` or it is a number and can be converted to `T` using numeric coercion as described two bullets lower.

- If `T` is `java.lang.String`, then if model implements `TemplateScalarModel` its string value is returned. Note that if the model doesn't implement TemplateScalarModel we don't attempt to automatically convert the model to string using String.valueOf(model). You'll have to use the ?string built-in explicitly to pass non-scalars as strings.

- If `T` is a primitive numeric type or `java.lang.Number` is assignable from `T`, and model implements `TemplateNumberModel`, then its numeric value is returned if it is instance of `T` or its boxing type (if `T` is primitive type). Otherwise, if `T` is a built-in Java numeric type (primitive type or a standard subclass of `java.lang.Number`, including `BigInteger` and `BigDecimal`) a new object of class `T` or its boxing type is created with the number model's appropriately coerced value.

- If `T` is `boolean` or `java.lang.Boolean`, and model implements `TemplateBooleanModel`, then its boolean value is returned.

- If `T` is `java.util.Map` and the model implements `TemplateHashModel`, then a special Map representation of the hash model is returned.

- If `T` is `java.util.List` and the model implements `TemplateSequenceModel`, then a special List representation of the sequence model is returned.

- If `T` is `java.util.Set` and the model implements `TemplateCollectionModel`, then a special Set representation of the collection model is returned.

- If `T` is `java.util.Collection` or `java.lang.Iterable` and the model implements either `TemplateCollectionModel` or `TemplateSequenceModel`, then a special Set or List representation of the collection or sequence model (respectively) is returned.

- If `T` is a Java array type and the model implements `TemplateSequenceModel`, then a new array of the specified type is created and its elements unwrapped into the array recursively using the array's component type as `T`.

- If `T` is `char` or `java.lang.Character`, and model implements `TemplateScalarModel`, and its string representation contains exactly one character, then a `java.lang.Character` with that value is retured.

- If `java.util.Date` is assignable from `T`, and model implements `TemplateDateModel`, and its date value is instance of `T`, then its date value is returned.

- If model is a number model, and its numeric value is instance of `T`, the numeric value is returned. You can have a custom subclass of java.lang.Number that implements a custom interface, and T might be that interface. (*)

- If model is a date model, and its date value is instance of `T`, the date value is returned. Similar consideration as for (*)

- If model is a scalar model, and `T` is assignable from `java.lang.String`, the string value is returned. This covers cases when T is java.lang.Object, java.lang.Comparable, and java.io.Serializable (**)

- If model is a boolean model, and `T` is assignable from `java.lang.Boolean`, the boolean value is returned. Same as (**)

- If model is a hash model, and `T` is assignable from `freemarker.ext.beans.HashAdapter`, a hash adapter is returned. Same as (**)

- If model is a sequence model, and `T` is assignable from `freemarker.ext.beans.SequenceAdapter`, a sequence adapter is returned. Same as (**)

- If model is a collection model, and `T` is assignable from `freemarker.ext.beans.SetAdapter`, a set adapter for the collection is returned. Same as (**)

- If the model is instance of `T`, the model itself is returned. This covers the case where the method explicitly declared a FreeMarker-specific model interface, as well as allows returning directive, method and transform models when java.lang.Object is requested.

- An exception signifying no conversion is possible is thrown.

## Accessing static methods

The `TemplateHashModel` returned from `BeansWrapper.getStaticModels()` can be used to create hash models for accessing static methods and fields of an arbitrary class.

    BeansWrapper wrapper = BeansWrapper.getDefaultInstance();
    TemplateHashModel staticModels = wrapper.getStaticModels();
    TemplateHashModel fileStatics =
        (TemplateHashModel) staticModels.get("java.io.File");

And you will get a template hash model that exposes all static methods and static fields (both final and non-final) of the `java.lang.System` class as hash keys. Suppose that you put the previous model in your root model:

    root.put("File", fileStatics);

From now on, you can use `${File.SEPARATOR}` to insert the file separator character into your template, or you can even list all roots of your file system by:

    <#list File.listRoots() as fileSystemRoot>...</#list>

Of course, you must be aware of the potential security issues this model brings.

You can even give the template authors complete freedom over which classes' static methods they use by placing the static models hash into your template root model with

    root.put("statics", BeansWrapper.getDefaultInstance().getStaticModels());

This object exposes just about any class' static methods if it's used as a hash with class name as the key. You can then use expression like `${statics["java.lang.System"].currentTimeMillis()}` in your template. Note, however that this has even more security implications, as someone could even invoke `System.exit()` using this model if the method exposure level is weakened to `EXPOSE_ALL`.

Note that in above examples, we always use the default `BeansWrapper` instance. This is a convenient static wrapper instance that you can use in most cases. You are also free to create your own `BeansWrapper` instances and use them instead especially when you want to modify some of its characteristics (like model caching, security level, or the null model representation).

## Accessing enums

The `TemplateHashModel` returned from `BeansWrapper.getEnumModels()` can be used to create hash models for accessing values of enums on JRE 1.5 or later. (An attempt to invoke this method on an earlier JRE will result in an `UnsupportedOperationException`.)

    BeansWrapper wrapper = BeansWrapper.getDefaultInstance();
    TemplateHashModel enumModels = wrapper.getEnumModels();
    TemplateHashModel roundingModeEnums =
        (TemplateHashModel) enumModels.get("java.math.RoundingMode");

And you will get a template hash model that exposes all enum values of the `java.math.RoundingMode` class as hash keys. Suppose that you put the previous model in your root model:

    root.put("RoundingMode", roundingModeEnums);

From now on, you can use `RoundingMode.UP` as an expression to reference the `UP` enum value in your template.

You can even give the template authors complete freedom over which enum classes they use by placing the enum models hash into your template root model with

    root.put("enums", BeansWrapper.getDefaultInstance().getEnumModels());

This object exposes any enum class if it's used as a hash with class name as the key. You can then use expression like `${enums["java.math.RoundingMode"].UP}` in your template.

The exposed enum values can be used as scalars (they'll delegate to their `toString()` method), and can be used in equality and inequality comparisons as well.

Note that in above examples, we always use the default `BeansWrapper` instance. This is a convenient static wrapper instance that you can use in most cases. You are also free to create your own `BeansWrapper` instances and use them instead especially when you want to modify some of its characteristics (like model caching, security level, or the null model representation).

Previous Next