问题
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 ) );
}
}
}
回答1:
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.
回答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
}
}
来源:https://stackoverflow.com/questions/23085247/how-to-set-locale-in-bean-validation