CDI-Unit 2.0.6 released

CDI-Unit 2.0.6 has been released with a couple of small changes:

  • Only classes that are CDI enabled (have a beans.xml) will be automatically followed when working out the deployment.
  • Sterotypes can now be added in @ActivatedAlternatives.

Post to Twitter

CDI-Unit 2.0.5 released

It’s nice to see people filing bugs, because that means I can fix them!

This release fixes an issue when testing non web based apps. All @Test classes should have automatically be placed in application scope, but due to a typo this wasn’t happening.

Also fixed is the discovery algorithm used to decide what is in the deployment. For my larger projects this has resulted in a noticeable speedup.

New release should be in maven central shortly.

Post to Twitter

CDI-Unit deployed to maven central

So after a long break and a little prompting I finally did the work required to get CDI-Unit deployed to maven central. I hate referencing external repositories, so you probably do as well.

It was a little tricky, but not nearly as hard as I thought. Information that came in handy:

The only issue I has was that my credentials didn’t seem to work when uploading artifacts, so after a lot of checking and rechecking my config I eventually reset my password to something else and it all started working.

The artefact hasn’t appeared yet, but it is in Sonatype-Releases, so it should be there in a few hours.

Please note that the group ID has changed so you’ll need to change your dependency to:

<dependency>
  <groupId>org.jglue.cdi-unit</groupId>
  <artifactId>cdi-unit</artifactId>
  <version>2.0.4</version>
  <scope>test</scope>
</dependency>

Post to Twitter

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