7
votes

I have a properties file for localization:

foo=Bar
title=Widget Application

This is tied in as a resource-bundle in the faces-config:

<resource-bundle>
    <base-name>com.example.messages.messages</base-name>
    <var>msgs</var>
</resource-bundle>

I can access this just fine in the facelets view using EL:

<title>#{msgs.title}</title>

However, if there are things like SQLExceptions, I need to be able to write messages from the managed bean. This is all working also:

FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, "There was an error saving this widget.", null);
FacesContext.getCurrentInstance().addMessage(null, message);

Here is the issue: I want to have those messages come from the properties file so that they, too, can be changed based on the locale. Is there an easy way to access the properties file using injection?

5

5 Answers

17
votes

I asked a quite related question on SO: How to inject a non-serializable class (like java.util.ResourceBundle) with Weld

And inside the Seam Forum: http://seamframework.org/Community/HowToCreateAnInjectableResourcebundleWithWeld

To summarize: I realized an injectable ResourceBundle with 3 Producers. First you need a FacesContextProducer. I took the one from the Seam 3 Alpha sources.

public class FacesContextProducer {
   @Produces @RequestScoped
   public FacesContext getFacesContext() {
      FacesContext ctx = FacesContext.getCurrentInstance();
      if (ctx == null)
         throw new ContextNotActiveException("FacesContext is not active");
      return ctx;
   }
}

Then you need a LocaleProducer, which uses the FacesContextProducer. I also took it from Seam 3 Alpha.

public class FacesLocaleResolver {
   @Inject
   FacesContext facesContext;

   public boolean isActive() {
      return (facesContext != null) && (facesContext.getCurrentPhaseId() != null);
   }

   @Produces @Faces
   public Locale getLocale() {
      if (facesContext.getViewRoot() != null) 
         return facesContext.getViewRoot().getLocale();
      else
         return facesContext.getApplication().getViewHandler().calculateLocale(facesContext);
   }
}

Now you have everything to create a ResourceBundleProducer, which can look like this:

public class ResourceBundleProducer {
  @Inject       
  public Locale locale;

  @Inject       
  public FacesContext facesContext;

  @Produces
  public ResourceBundle getResourceBundle() {
   return ResourceBundle.getBundle("/messages", facesContext.getViewRoot().getLocale() );
  }
}

Now you can @Inject the ResourceBundle into your beans. Pay attention that it has to be injected into a transient attribute, otherwise you'll get an exception complaining that ResourceBundle is not serializable.

@Named
public class MyBean {
  @Inject
  private transient ResourceBundle bundle;

  public void testMethod() {
    bundle.getString("SPECIFIC_BUNDLE_KEY");
  }
}
2
votes

It's easier to use e.g. the message module of MyFaces CODI!

2
votes

You can do this with JSF alone.

Start by defining a managed property on your backing bean. In the JSF configuration, you can set the managed property's value to an EL expression that references your resource bundle.

I've done something like the following using Tomcat 6. The only caveat is that you can't access this value from your backing bean's constructor, since JSF will not yet have initialized it. Use @PostConstruct on an initialization method if the value is needed early in the bean's lifecycle.

<managed-bean>
  ...
  <managed-property>
    <property-name>messages</property-name>
    <property-class>java.util.ResourceBundle</property-class>
    <value>#{msgs}</value>
  </managed-property>
  ...
</managed-bean>

<application>
  ...
  <resource-bundle>
    <base-name>com.example.messages.messages</base-name>
    <var>msgs</var>
  </resource-bundle>
  ...
</application>

This has the advantage of making your backing bean methods less dependent on the presentation technology, so it should be easier to test. It also decouples your code from details like the name given to the bundle.

Some testing using Mojarra 2.0.4-b09 does show a small inconsistency when a user changes locale mid-session. In-page EL expressions use the new locale but the backing bean isn't given the new ResourceBundle reference. To make it consistent you could use the bean property value in EL expressions, such as using #{backingBean.messages.greeting} in place of #{msgs.greeting}. Then page EL and the backing bean would always use the locale that was active when the session began. If users had to switch locales mid-session and get the new messages, you could try making a request-scoped bean and give it references to both the session bean and resource bundle.

0
votes

Here's an example on how to do this: http://www.laliluna.de/articles/javaserver-faces-message-resource-bundle-tutorial.html

You want to have a look at the ResourceBundle.getBundle() part.

Greetings, Lars

0
votes

This is an old question but I'm adding another way to do this. I was looking for something else and ran across this. The methods here all seemed convoluted for something that doesn't strike me as all the that difficult. Playing with glitter because it's pretty if you ask me.

Given a file:

/com/full/package/path/to/messages/errormessages.properties

Inside the file:

SOME_ERROR_STRING=Your App Just Cratered

I create a "getBundle()" method since I like to catch the runtime and add a meaningful message so I will understand where it is coming from. Not hard and can help if you get the whim to play with the properties files for some reason and don't update everything correctly. I sometimes make this private as it is sometimes a helper method within a class (for me). This keeps the try catch clutter out of the meaningful code.

Using the full path to the file allows you to put it somewhere other than the default location/directory if you have other ideas on organization.

public/private ResourceBundle getMessageResourceBundle(){
    String messageBundle = "com.full.package.path.to.messages.errormessages";
    ResourceBundle bundle = null;
    try{
        bundle = ResourceBundle.getBundle(messageBundle);
    }catch(MissingResourceException ex){
        Logger.getLogger(this.getClass().getName()).log(Level.SEVERE,
                    "Unable to find message bundle in XYZ Class", ex);
        throw ex;
    }

}

public void doSomethingWithBundle(){

    ResourceBundle bundle = getMessageResourceBundle();
    String someString = bundle.getString("SOME_ERROR_STRING");
    ...
}