How to set Locale in Bean Validation
Asked Answered
F

2

6

By default Bean Validation gets Locale based on Locale.getDefault(), which is common to whole JVM.

How to change BeanValidation's Locale for current EJB method call?

I'm using JavaEE7 and want to get benefits from integration of JPA and Bean Validation, i.e. automatic triggering validation on insert/update/delete events, and as far as possible avoid of writing everything manually.

EDIT

After all, I'm just returning non-interpolated messages from EJB:

public class DoNothingMessageInterpolator implements MessageInterpolator {
    @Override
    public String interpolate(String message, Context context) {
        return message;
    }
    @Override
    public String interpolate(String message, Context context, Locale locale) {
        return message;
    }
}

and later interpolating them in Web tier:

try{
    //invoke ejb
}catch( EJBException ejbex ){
    if( ejbex.getCause() instanceof ConstraintViolationException ){
        ConstraintViolationException cve = (ConstraintViolationException) ejbex.getCause();
        WebUtils.printConstraintViolationMessages("NewConferenceForm:", context, cve, new Locale(languageCtrl.getLocaleCode()) );
        return null;
    }else throw ejbex;
}catch( Exception e ){
        context.addMessage(null, new FacesMessage( FacesMessage.SEVERITY_ERROR, "Oops.", ""));
        return null;
}


public class WebUtils {

    public static void printConstraintViolationMessages(
        String formPrependId, 
        FacesContext context, 
        ConstraintViolationException cve,
        Locale locale )
    {
        Iterator<ConstraintViolation<?>> iter = cve.getConstraintViolations().iterator();
        while( iter.hasNext() ){
            final ConstraintViolation<?> cv = iter.next();

            javax.validation.MessageInterpolator.Context c = new javax.validation.MessageInterpolator.Context()
            {
                @Override public <T> T unwrap(Class<T> type) {
                    try {
                        return type.newInstance();
                    } catch (InstantiationException ex) {
                        Logger.getLogger(ConferencesCtrl.class.getName()).log(Level.SEVERE, null, ex);
                    } catch (IllegalAccessException ex) {
                        Logger.getLogger(ConferencesCtrl.class.getName()).log(Level.SEVERE, null, ex);
                    }
                    return null;
                }
                @Override
                public ConstraintDescriptor<?> getConstraintDescriptor() {
                    return cv.getConstraintDescriptor();
                }
                @Override
                public Object getValidatedValue() {
                    return cv.getInvalidValue();
                }
            };

            ResourceBundleMessageInterpolator rbmi = new ResourceBundleMessageInterpolator();
            String interpolatedMsg = rbmi.interpolate(cv.getMessage(), c, locale );

            //TODO: check if clientId exists
            context.addMessage( formPrependId+cv.getPropertyPath().toString(), new FacesMessage( interpolatedMsg ) );
        }
    }

}
Freon answered 15/4, 2014 at 13:31 Comment(0)
C
3

I guess it comes down to what you actually mean with

How to change BeanValidation's Locale for current EJB method call?

Assuming for example that each call is made by a given user and this user has an associated Locale, you would need a custom MessageInterpolator. You would configure your custom implementation via validation.xml (see example in online docs).

Implementation wise you can let the heavy lifting be done by delegation. Your custom message interpolator could instantiate the default Hibernate Validator ResourceBundleMessageInterpolator and delegate the interpolation calls to it, once the Locale is determined. The latter can be achieved by a ThreadLocaL. The EJB method would set the users Local in a ThreadLocal and your custom message interpolator would pick it up from there.

Caren answered 16/4, 2014 at 9:45 Comment(1)
Ok, I'm accepting the answer, it looks like I have different problem with passing user's Locale to EJB.Freon
F
2

Analog to @Hardy's answer you can also store the Local in the resource SessionContext data map for this purpose and retrieve it in your MessageInterpolator implementation.

In your remote bean methods you can pass the client locale as an argument and set it on method entry. A possible setup could look like this:

Locale retrieval interface

interface ILocale
{
    public Locale getLocale();
}

LocaleMessageInterpolator

Extend your default MessageInterpolator. Had to use interface to obtain locale to make it usable outside of EJB application.

public class LocaleMessageInterpolator extends ResourceBundleMessageInterpolator
{
    private final ILocale iLocale;

    public LocaleMessageInterpolator(final ILocale iLocale)
    {
        this.iLocale = iLocale;
    }

    @Override
    public String interpolate(final String messageTemplate, final Context context)
    {
        final Locale locale = this.iLocale == null ? null : this.iLocale.getLocale();

        if (locale == null)
            return super.interpolate(messageTemplate, context);
        else
            return this.interpolate(messageTemplate, context, locale);
    }
}

Application scoped bean

Register the new MessageInterpolator in your validator factory. If all other Beans inject AppBean the constant could be private and a setClientLocale() method could be provided.

@Startup
@Singleton
public class AppBean
{
    public static final String CONTEXT_CLIENT_LOCALE_KEY =  "CLIENT_LOCALE_KEY";

    @Resource
    private SessionContext ctx;

    @PostConstruct
    public void init()
    {
        // retrieve client locale from context using anyonymous implementation
        final ILocale ilocale = () -> {
            if (AppBean.this.ctx.getContextData().containsKey(AppBean.CONTEXT_CLIENT_LOCALE_KEY))
                return (Locale) AppBean.this.ctx.getContextData()
                .get(AppBean.CONTEXT_CLIENT_LOCALE_KEY);
            return null;
        };

        // create client locale aware message interpolator
        final LocaleMessageInterpolator localeMessageInterpolator= new LocaleMessageInterpolator(ilocale);

        // configurate validator factory
        ValidatorFactory validatorFactory = Validation.byDefaultProvider().configure().messageInterpolator(localeMessageInterpolator).buildValidatorFactory();

        // register validator factory

configuration.getProperties().put("javax.persistence.validation.factory", validatorFactory); }

Remote Bean

Save current client locale in SessionContext.

@Stateless(mappedName = "MyBean")
@Remote(MyBeanRemote.class)
public class MyBean
{
    @Resource
    private SessionContext ctx;

    @Override
    public void create(Locale locale, Foo foo)
    {
        this.ctx.getContextData().put(AppBean.CONTEXT_CLIENT_LOCALE_KEY, locale);
        // persist new Foo
        // thrown validation exceptions are localized
    }
}
Foreworn answered 21/6, 2017 at 21:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.