ResourceBundles backed with a database in Java

I recently wanted to use a database for internationalization rather than use properties files or ListResourceBundle within a CDI application, but hooking everything together was not as easy as I thought it would be.

I started by creating my own DatabaseResourceBundle implementation that would open a connection to the database and load the appropriate strings.

class DatabaseResourceBundle extends ResourceBundle {
  @Inject
  private EntityManager _entityManager;

  private Map<String, String> _values = new HashMap<String, String>();

  @PostConstruct
  public void postContstruct() {
    //Load the resources for this bundle
    TypedQuery<ResourceEntity> query = _entityManager.createQuery(...);
    List<ResourceEntity> resources = query.getResultsList();
    for(ResourceEntity resource : resources) {
      _values.put(resource.getKey(), resource.getValue());
    }
  }

  @Override
  protected Object handleGetObject(String key) {
    return _values.get(key);
  }

  @Override
  public Enumeration<String> getKeys() {
    return Iterators.asEnumeration(_values.keySet().iterator());
  }
}

Problem 1 – DatabaseResourceBundle did not get injected.

ResourceBundles are loaded through ResourceBundle.getBundle(String basename) mechanism, this first performs a lookup to see if there is a class of the appropriate name and instatiates it, or if it cannot find such a class looks for properties files with the same name. The CDI container was not getting a chance to inject the EntityManager, and therefore I couldn’t access the database.

Problem 2 – DatabaseResourceBundle can only serve up the base bunde.

Internally bundles form a hierachy of languages, countries and variations. This is configured by class name. So for instance:

  1. DatabaseResourceBundle
  2. DatabaseResourceBundle_en
  3. DatabaseResourceBundle_en_GB

Trying to get everything working through a single object is really working against the how resource bundles work and is likely to cause issues later down the line.

Solution 1 – Use the ResourceBundle.Control mechanism.

In theory this is just what I need. In the javadocs it even gives an example of loading XML bundles. However, it just isn’t practical. It was only introduced in Java 1.6 and I have yet to find any libraries that support this mechanism, in my case JSF.

Solution 2 – Use BeanManagerLocator and make DatabaseResourceBundle abstract

Because there is only supposed to be one CDI container per application it is actually several mechanisms to obtain a reference to the BeanManager to allow injection. The one that I used was the Seam Solder BeanManagerLocator.

abstract class AbstractDatabaseResourceBundle extends ResourceBundle {
  @Inject
  private EntityManager _entityManager;

  private Map<String, String> _values = new HashMap<String, String>();

  public DatabaseResourceBundle() {
    //Inject this object
    BeanManager beanManager = locator.getBeanManager();
    BeanManagerUtils.injectNonContextualInstance(beanManager, this);

    //Obtain the bundle configuration
    String[] split = getClass().getName().split("_");
    String baseName = split[0];
    String language = split.length > 1 ? split[1] : null;
    String country = split.length > 2 ? split[2] : null;
    String variant = split.length > 3 ? split[3] : null;

    //Load the resources for this bundle
    TypedQuery<ResourceEntity> query = _entityManager.createQuery(...);
    query.setParameter("baseName", baseName);
    query.setParameter("language", language);
    query.setParameter("country", country);
    query.setParameter("variant", variant);
    List<ResourceEntity> resources = query.getResultsList();
    for(ResourceEntity resource : resources) {
      _values.put(resource.getKey(), resource.getValue());
    }
  }

  @Override
  protected Object handleGetObject(String key) {
    return _values.get(key);
  }

  @Override
  public Enumeration<String> getKeys() {
    return Iterators.asEnumeration(_values.keySet().iterator());
  }
}

So this enables me to define the following classes:

  1. MyAppResourceBundle extends DatabaseResourceBundle
  2. MyAppResourceBundle_en extends DatabaseResourceBundle
  3. MyAppResourceBundle_en_GB extends DatabaseResourceBundle

The only downside to this approach is that for each application you have to pre-emtively create classes for all the languages, countries and varients that you want to support, but realistically that won’t change very often and can be combined with a new release.

Post to Twitter

CDI for Guice users

I am a Guice user who also uses CDI, I like both, but starting firmly in the Guice camp left me confused about how to effectivly use CDI.

The problem was that although Guice and CDI are both dependency injection frameworks, they require a slightly different mindset which means you have to organise your projects differently. Don't expect your Guice application to work in CDI without modification, and moving from CDI to Guice is likely to be painful. 

The Guice way

With Guice you are in complete control over how your application is configured, although defaulting is possible, if you need to specify an explicit implementation you can do so via a module in your bootstrap class.

Guice configuration uses a fluent configuration API to achive application specific configuration. This is great if you do a lot of refactoring as the configuration gets refactored along with the rest of your code.

For example, here we have an application that is creating a starship:

Although TranswarpDrive and WarpDrive are available implementations of engines, we are specifying that we want to use WarpDrive.

Advantages

  • Great refactoring support.
  • It's easy to see how injection is configured.

The CDI way

With CDI you must let the container take control over what is being injected. If you've been using Guice this will seem unnatural, like letting a plane fly itself, but it the idea is that CDI components should automatically configure themselves as much as possible.

This is fine, except that when converting a Guice application to CDI for the first time you will probably encounter something like:

The fundemental difference between CDI and Guice is that everything on the classpath is a candidate for injection, and the CDI container will tell you if there is an ambiguous injection. In this case if we ask for an Engine the CDI container can't tell which implementation to use.

There are a couple of ways to remedy this situation:

Using alternatives

If you have several implementations of a class then you can annotate them with the @Alternative annotation. Any class annotated with @Alternative can be specified in META-INF/benas.xml, if it is then it will override other implementations on the classpath.

In our example, if WarpDrive was annotated with @Alternative then it would be OK for TranswarpDrive to be on the classpath.

Using Maven

If you are prepared to split your implementations into different maven artifacts then you can use Maven to select the implementation that you want to use. This is more applicable to library developers, you can leave it to the client to select what implementation they want to use by including it as a dependency in the pom.

In our example, if Engine, WarpDrive and TranswarpDrive are all in different artifacts, then using the pom we can select to just have the WarpDrive on the classpath. The CDI container doesn't know anything about TranswarpDrive.

CDI sounds like a step backwards, but it isn't

At this point I was wondering why on earth I should be using CDI instead of Guice, the use of XML for alternatives instead of the Guice fluent config feels very wrong. However after working with CDI for a while I realised that it wasn't a problem:

  • CDI aware components configure themselves – If a component is CDI aware then you generally just include them on the classpath and start injecting the services you need. No module required.
  • Alternatives should hardly ever be used  Instead if the different implementations are required at runtime then use @Instance.
  • CDI extensions are first class citizens – With CDI an extension can hook directly into the container injection mechanism, with Guice you only get the opportunity to inject after guice has had first dibs.
  • Lifecycle support – Lack of lifecycle support in Guice lifecycle support is sometimes problematic for applications that require a clean shutdown.
  • It's the standard – Like it or not, CDI is the default dependency injection that'll be bundled in with modern application servers, at some point you'll find it easier to just write the code to put food on the table rather than worrying about what library is doing dependency injection.

Don’t get me wrong, I am still a big fan of Guice, and and there are still cases where Guice wins on simplicity and performance, just think of CDI as another tool in the toolbox.

CDI Unit testing needs some Guice

There is one fly in the ointment when using CDI. During unit testing you usually want to replace an implementation with an alternative implementation for testing or mock. Using Guice I would create an injector for my unit tests that had the appropriate configuration. However, simply firing up the JavaSE Weld implementation won't work because:

  • Everything on the classpath gets included in the container, of you have some code that listens to the startup event then this can slow down running the test.
  • There is no opportunity to reconfigure what is being injected without modifying you main application code.

Instead you need to be able to configure what goes into CDI container for the unit test in a similar way that you would with Guice. Take a look at these projects to see how it's done:

Post to Twitter

CDI-Unit 0.9.6 released!

Added support for specifying alternatives using annotations.

I still need to fill out all the javadocs and get the everything reviewed before releaseing 1.0.0. In particular I am unhappy with the naming of the annotations: SupportClasses, TestAlternative and TestAlternatives as they are not sufficiently capture their purpose. It is likely that these names will change in the next release.

Post to Twitter