One of the most common “enterprise” uses for the Stringtree Templater is to generate data transfer formats from Java object models. I showed a few posts ago a little bit of how you can generate JSON or XML from an object model.
However, if you want to generate a fairly complex XML document with lots of different elements, the approach I described of using a separate template for each element name can get tedious pretty quick. A colleague asked me about this, and I told him I’d look for a smarter solution. To come up with this technique I had to dig fairly deep into the way Stringtree Templater works, but once it is out in the open, it’s easy to apply, and very re-usable. Here’s how it works:
Let’s imagine we want to generate the XML for a simple case - a FullName object with accessors getForename() and getLastname(). If we create such an object new FullName("Frank","Carver") we would expect the following XML:
<person> <forename>Frank</forename> <surname>Carver</surname> </person>
First, we need a template for each element. This is pretty simple, let’s call it element.tpl:
<${name}>${value}</${name}>
Now all we need to do is store context objects “name” and “value” with the correct contents and invoke the element template. We could do this using the assignment syntax for each element (e.g. ${name=’forename’}${value=this.forename}${*element}) but this is pretty clumsy, too, and only partly addresses the problem.
A better solution would be to use the ability of Stringtree Templater to call out to Java objects. There are plenty of options for this, but the one we will use is the same method Stringtree Templater uses to provide an intuitive syntax for accessing Map entries. When Stringtree Templater encounters an expression such as ${object.field} it first tries “JavaBean” accessors ( object.getField(), object.isField() ); if they are not available it tries the field name as a regular method (object.field()); if that fails too it tries a general “get” method ( object.get("field") ). Each of these methods are tried with and without an optional parameter referring to the enclosing context.
With all that in mind, we need a context tool which, when asked to “get” a field, places the field name in the context as “name”, and the field value in the context as “value”. Unfortunately, we have no obvious way of passing in the object which supplies these fields. The solution is to work with Stringtree Templater and to realise that in the great majority of cases the object being considered will be in the context as “this”.
The final part of the context tool “get” method is its return value. In this case I choose to return a boolean indicating whether the “this” object has such a field. That way, the return value of the template expression can be used to determine whether or not to generate optional elements in the resulting XML. One version of the context tool might look like:
import org.stringtree.Fetcher;
import org.stringtree.fetcher.BeanFetcher;
import org.stringtree.finder.StringKeeper;
public class BeanTool {
public boolean get(StringKeeper context, String name) {
Fetcher fetcher = new BeanFetcher(context.getObject("this"));
Object value = fetcher.getObject(name);
if (null != value) {
context.put("name", name);
context.put("value", value);
return true;
}
return false;
}
}
Note that this is just a regular Java class (a “POJO”), with no need to extend or implement any Stringtree classes. In this case it uses some Stringtree code to manipulate the template context, but tools for other purposes would likely not need that.
Next, a template to tie it all together, let’s call it person.tpl Not that this template assumes that our new tool has been placed into the context as “tool”.
<person>
${tool.forename?*element}
${tool.surname?*element}
</person>
Note the “?” indicating the conditional execution of the next section, and the “*” indicating inclusion of a template fragment.
Finally, a bit of Java (in this case, in the form of a JUnit test case) to show how it all fits together:
import junit.framework.TestCase;
import org.stringtree.Repository;
import org.stringtree.fetcher.MapFetcher;
import org.stringtree.template.EasyTemplater;
public class EasyTemplaterTest extends TestCase {
Repository templates;
EasyTemplater templater;
public void setUp() {
templates = new MapFetcher();
templater = new EasyTemplater(templates);
}
public void testXMLElementExpansionSequence() {
templater.put("frank", new FullName("Frank", "Carver"));
templater.put("bean", new BeanTool());
templates.put("tpl1", "${frank*person}");
templates.put("person", "<root>${bean.forename?*element}${bean.surname?*element}</root>");
templates.put("element", "<${name}>${value}</${name}>");
assertEquals("<root><forename>Frank</forename><surname>Carver</surname></root>",
templater.toString("tpl1"));
}
}
Have fun!
