Fork me on GitHub

Extending the platform: Resource bundle resolution

Snippetory is a simple to use and extensible text generation platform. Now we take a look on the extension mechanism. It is based on the service loading mechanism of the jar. Any extension available on class path, in jars, will be loaded whenever Snippetory is initialized.

This has the advantage, that the extensions are available in different environments simply by configuring the class path.

  • IDE tooling
  • An automatic verifier module for build tools
  • ...
Whenever an extension is used without packaging it can be easily loaded by loading the Configurer class.

Service Provider Interface

All the classes, relevant for extending the platform are placed in the package org.jproggy.snippetory.spi. These are the interfaces that have to be implemented for creating new formats, encoding and syntaxes as well as the Configurer interface.

The example

Since information from resource bundles can be resolved statically, it would be nice to resolve them while parsing the template. On the other hand a full build in support would require a complexity, that isn't that handy. However, specialization reduces the information to be denoted. Hence I decided to provide localization support as a how to instead of trying to build an all in one solution.

Hands on

First we need an VoidFormat class. That's a specialization of the Format It stores the value resolved from the resource bundle.

package org.jproggy.example.msg;

import java.util.Collections;
import java.util.Set;

import org.jproggy.snippetory.spi.*;

public class ResoureFormat extends SimpleFormat implements VoidFormat, FormatConfiguration {
  private final String resolved;

  public ResoureFormat(String resolved) {
    this.resolved =  resolved;
  }

  @Override
  public Object formatVoid(TemplateNode node) {
    return resolved;
  }

  @Override
  public boolean supports(Object value) {
    return false;
  }

  @Override
  public String format(TemplateNode node, Object value) {
    throw new UnsupportedOperationException();
  }
  
  @Override
  public void set(String name, Object value) {
  }

  @Override
  public void append(String name, Object value) {
  }

  @Override
  public Set<String> names() {
    return Collections.emptySet();
  }
  
}

However, to have a resolved value we need to resolve it. This is done within the FormatFactory.

package org.jproggy.example.msg;

import java.util.ResourceBundle;

import org.jproggy.snippetory.TemplateContext;
import org.jproggy.snippetory.spi.*;

public class ResoureFormatFab implements FormatFactory, Configurer {

  static
  {
    // the class is only loaded, so initialization goes to
    // static initializer.
    Format.REGISTRY.register("msg", new ResoureFormatFab());
  }

  @Override
  public FormatConfiguration create(String definition, TemplateContext ctx){
    // The resource is resolved in the factory. This mean if resolving 
    // would fail, the template could not be parsed. I.e. we have a 
    // strong fail fast behavior.
    ResourceBundle msg =  ResourceBundle.getBundle(
        "org.jproggy.example.msg.Messages", ctx.getLocale());
    return new ResoureFormat(msg.getString(definition));
  } 
}

Now we have to configure the service loader mechanism. This is a little more complicated to describe as it needs interaction with you packaging solution. In effect we to create a sub folder of the META-INF folder called services. This folder has to contain a file called org.jproggy.snippetory.spi.Configurer. I.e. META-INF/services/org.jproggy.snippetory.spi.Configurer
And this file has to contain a single line, that's simply the binary name of your Configurer class. The binary name differs from the qualified basically for inner classes. They're separated form their hosts name by '$' instead of a period.

com.example.tool.ResoureFormatFab

Now it's ready to use. We create a file called HalloWorldApp.html as a resource within our class path. (If you use Maven just put it in src/main/resources)

<html>
<head>
  <title>{v:x msg="page.title"}</title>
</head>
<body>
  {v:x msg="greeting"} {v:x msg="world"}
</body>
</html>

Of course we need a properties file. Maybe com/example/tool/Messages_de.properties:

page.title = Hallo Welt Anwendung greeting = Hallo world = Welt

With java code like this:

    .
    .
    .
  Repo.readResource("HalloWorldApp.html").locale(Locale.GERMANY)
      .parse().render(System.out);
    .
    .
    .

Bernd Ebertz

Head, Founder and chief technology evangelist of
jproggy.org
Java, and all Java-based marks are trademarks or registered trademarks of Oracle.
This site is not affiliated in any way with Oracle.