Implementing a Locale provider that works in JSF a

2019-05-28 19:23发布

问题:

I've been joyfully using omnifaces' Faces.getLocale() to aquire the locale used by the currently logged in user (which in turn gets this from a <f:view> definition). I really like the fallback approach from view to client to system default locale as it fits the requirements for locale selection in my application:

  1. If a user is logged in, use his language preference (obtained from the backend entity)
  2. If no user preference can be found, use the highest ranking language from the Accept-Languages HTTP header
  3. If no locale has been selected by now, use the system default.

Now I've started using JAX-RS (resteasy implementation) and find it quite difficult to write a service that will provide my backend code with the current user's locale.

I can't use Faces.getLocale(), since that requires a FacesContext which isn't present during JAX-RS request processing.

I can't use the @Context SecurityContext annotation in a @Provider (which would give me the user preferred locale) or @Context HttpHeaders (access to the client locale) since JAX-RS only injects those when it uses the provider itself, not when my backend code instantiates the class.

And I don't want to litter my method signatures with Locale parameters, since virtually everything requires a locale to be present.

To have a concrete example: I have a vcard generator that generates little NOTE fields depending on the user's preferred locale. I can both call the vcard generating method via JSF/EL:

<h:commandLink action="#{vcfGenerator.forPerson(person)}" 
    value="Go" target="_blank" />

And via a REST service:

@GET @Path('person/{id:[1-9][0-9]*}/vcard')
@Produces('text/vcard')
String exportVcard(@PathParam('id') Long personId, @Context HttpHeaders headers) {
    VcfGenerator exporter = Component.getInstance(VcfGenerator) as VcfGenerator

    Person person = entityManager.find(Person, personId)
    if (! person)
        return Response.noContent().build()

    def locale = headers.acceptableLanguages[0] ?: Locale.ROOT
    return exporter.generateVCF(person, locale).toString()
}

This works (VcfGenerator has a set of JSF-only methods that use Faces.getLocale()), but is a pain to maintain. So instead of passing the Locale object, I'd like to say:

Vcard generateVCF(Person person) {
    Locale activeLocale = LocaleProvider.instance().getContext(VcfGenerator.class)
    ResourceBundle bundle = ResourceBundle.getBundle("messages", activeLocale, new MyControl())
    // use bundle to construct the vcard
}

Has anyone done similar work and can share insights?

回答1:

I know this has been posted a while ago, but as it has not been marked as resolved, here is how I got a workaround working for this specific case:

  • First I got a custom ResourceBundle working, as @BalusC described here: http://balusc.blogspot.fr/2010/10/internationalization-in-jsf-with-utf-8.html
  • Then I updated the constructor in order to detect if a FacesContext is currently being in use, from this :

    public Text() {
        setParent(ResourceBundle.getBundle(BUNDLE_NAME, 
        FacesContext.getCurrentInstance().getViewRoot().getLocale(), UTF8_CONTROL));
    }
    
  • To This:

    public Text() {
        FacesContext ctx = FacesContext.getCurrentInstance();
        setParent(ResourceBundle.getBundle(BUNDLE_NAME, 
        ctx != null ? ctx.getViewRoot().getLocale() : Locale.ENGLISH, UTF8_CONTROL));
    }
    

This now works both in JSF and JAX-RS context.

Hope this help,