Storing your messages in a database

Posted by    |      

Seam has great i8ln - it provides a built in locale selector which it uses to provide localized message bundles. You can load different message bundles for different pages.

Seam's built in message bundle uses properties files to define the resources - but what if you want to store your messages in a database? You might want to allow an admin to edit the properties through a web admin panel for example. Here we will cover the basic entities and wiring needed - you will probably want to add in some CRUD views for the resources.

Entities

We create two entites, first a resource bundle entity, which defines the bundle name, and the locale which it is for:

@Entity
public class ResourceBundle {
        
   @Id @GeneratedValue
   private Integer id;

   private String name;

   private ResourceLocale resourceLocale;
        
   @OneToMany(mappedBy="resourceBundle")
   ¨¨private List<Resource> resources;
}

And the embedded locale:

@Embeddable
public class ResourceLocale {
        
        @Column(length=2)
        @Length(max=2)
        private String language;
        
        @Column(length=2)
        @Length(max=2)
        private String country;
        
        @Column(length=2)
        @Length(max=2)
        private String variant;

}

And the resource itself, consisting of a key and a value:

@Entity
@NamedQueries({
   @NamedQuery(
      name="keys", 
      query="select r.key from Resource r where 
         r.resourceBundle.name = :bundleName and
         r.resourceBundle.resourceLocale.language = :language and 
         r.resourceBundle.resourceLocale.country = :country and
         r.resourceBundle.resourceLocale.variant = :variant"),
   @NamedQuery(
      name="value", 
      query="select r.value from Resource r where 
         r.resourceBundle.name = :bundleName and 
         r.resourceBundle.resourceLocale.language = :language and 
         r.resourceBundle.resourceLocale.country = :country and 
         r.resourceBundle.resourceLocale.variant = :variant and r.key = :key")
})
public class Resource 
{
   @Id @GeneratedValue
   private Integer id;

   @Column(name="_key")
   private String key;

   @Column(name="_value")
   private String value;
   
   @ManyToOne
   private ResourceBundle resourceBundle;
}

Wiring

We need to make Seam use our new resource loading scheme:

@Name("org.jboss.seam.core.resourceLoader")
@BypassInterceptors
public class DatabaseResourceLoader extends ResourceLoader {
   
   // Redefine how we load a resource bundle
   public ResourceBundle loadBundle(final String bundleName) {
      return new ResourceBundle() {

         public Enumeration<String> getKeys() {
            Locale locale = org.jboss.seam.core.Locale.instance();
            EntityManager entityManager = (EntityManager) Component.getInstance("entityManager");
            List resources = entityManager.createNamedQuery("keys")
               .setParameter("bundleName", bundleName)
               .setParameter("language", locale.getLanguage())
               .setParameter("country", locale.getCountry())
               .setParameter("variant", locale.getVariant())
               .getResultList();
            return Collections.enumeration(resources);
         }

         protected Object handleGetObject(String key) {
            Locale locale = org.jboss.seam.core.Locale.instance();
            EntityManager entityManager = (EntityManager) Component.getInstance("entityManager");
            try {
               return entityManager.createNamedQuery("value")
                  .setParameter("bundleName", bundleName)
                  .setParameter("language", locale.getLanguage())
                  .setParameter("country", locale.getCountry())
                  .setParameter("variant", locale.getVariant())
                  .setParameter("key", key)
                  .getSingleResult();
            } catch (NoResultException e) {
               return null;
            }
         }
      };
   }
}

Java 6

This approach will work on Java 5 - but I believe Java 6 provides enhanced ResourceBundle support (but as there is no Java 6 for the Mac I didn't ;-) ). You might want to investigate this.

Caching

We've not used any caching here - every request for a key will result in a database lookup. You could use a Java Map to cache resources as they are loaded (taking care to flush the cash when the locale changes - use Seam's event bus to observe org.jboss.seam.localeSelected), or perhaps you could rely on the Hibernate second level cache... Up to you!


Back to top