Tuesday, November 17, 2009

Freemarker ... powerful templating in Java

In most applications there is a point when you as developer need to create a template (for e-mail, notification, message, ...). Basically, the very simple template is just a string with some parameters like "Hello, ${name}" which have to be substituted at run-time. In many cases simple replace(...) is enough, but what if you need some logic inside template (expressions, flow control, function calls, ... )? For those who need powerful templates in Java projects I would like to recommend Freemarker - Java Template Engine Library.

Freemarker, in fact, has very good documentation. It's easy to start using it without any specific knowledge. In this post I would like to share a few issues which I found very useful. Let's start with very basic e-mail message template.
Hello ${user.fistName} ${user.lastName}!
Welcome to the world of templates!
In this template we assume that some object (bean) user with public properties firstName and lastName must be passed to template engine in order to build e-mail message. Let's save this template to message.ftl file (.ftl is default file extension for Freemarker templates). To process this template we need few simple steps:
Configuration cfg = new Configuration();

// Specify the data source where the template files come from.
cfg.setDirectoryForTemplateLoading( new File("/where/you/store/templates"));
// Specify how templates will see the data-model
cfg.setObjectWrapper( ObjectWrapper.DEFAULT_WRAPPER );

final Map< String, Object > context = new HashMap< String, Object >();
final User user = new User( "First Name", "Last Name" );
context.put( "user", user );

final Template t = cfg.getTemplate( "message.ftl");
final Environment env = t.createProcessingEnvironment( context, writer );
It's quite clear what the code does: create configuration, create context (simple map) and then process template (stored in file). When this code snippet finishes, writer will contain fully processed template. The tricky part here is such code:
// Specify how templates will see the data-model
cfg.setObjectWrapper( ObjectWrapper.DEFAULT_WRAPPER );
We will see what it means later but for know it's enough to say that it has influence on how Freemarker processes objects passed to template (via context).

We are done with e-mail message but what if you need to construct e-mail subject in template as well and return it back to callee? Freemarker allows to do that using environment. Let's modify the template a little bit:
<#assign subject="Congratulations ${user.fistName} ${user.lastName}!" />
Hello ${user.fistName} ${user.lastName}!
Welcome to the world of templates!
So in this template we create internal variable subject which we will use later in code. We need just a few additional steps:

String subject = "";
final TemplateModel subjectModel = env.getVariable( "subject" );
if( subjectModel instanceof TemplateScalarModel ) {
subject = ( ( TemplateScalarModel )subjectModel ).getAsString();
Next interesting question is about object properties / functions / static functions which you could use inside template. Basically, with ObjectWrapper.DEFAULT_WRAPPER you are free to use any object property which comply with Java Beans specification. In case you need, for example, calls like getClass(), you need ObjectWrapper.BEANS_WRAPPER.
cfg.setObjectWrapper( ObjectWrapper.BEANS_WRAPPER );  
Using static functions / properties could be done by means of static models. Let say you have a class with static my.package.StaticUtil. To use static members of this class inside the template we need to use code like:
final BeansWrapper wrapper = BeansWrapper.getDefaultInstance();
final TemplateHashModel staticModels = wrapper.getStaticModels();

final TemplateHashModel staticUtil =( TemplateHashModel )staticModels
.get( "my.package.StaticUtil" );

context.put( "StaticUtil", staticUtil );
Later in template you can use constructions like ${StaticUtil.someStaticFunction()} to access static members.

That's it for now. One thing is worthwhile to say is that Freemarker has good integration with Eclipse via JBoss Tools.