Skip to content

Commit

Permalink
HV-1748 Improve localization support
Browse files Browse the repository at this point in the history
- Make the default locale configurable
- Always initialize the default locale for the predefined scope VF
  • Loading branch information
gsmet committed Jan 15, 2020
1 parent 1b17b69 commit a9c46f4
Show file tree
Hide file tree
Showing 9 changed files with 252 additions and 56 deletions.
1 change: 1 addition & 0 deletions engine/pom.xml
Expand Up @@ -22,6 +22,7 @@

<properties>
<hibernate-validator-parent.path>..</hibernate-validator-parent.path>
<surefire.jvm.args.additional>-Duser.language=en</surefire.jvm.args.additional>
</properties>

<distributionManagement>
Expand Down
Expand Up @@ -7,6 +7,7 @@
package org.hibernate.validator;

import java.time.Duration;
import java.util.Locale;
import java.util.Set;

import javax.validation.Configuration;
Expand Down Expand Up @@ -360,4 +361,14 @@ public interface BaseHibernateValidatorConfiguration<S extends BaseHibernateVali
*/
@Incubating
S propertyNodeNameProvider(PropertyNodeNameProvider propertyNodeNameProvider);

/**
* Allows setting the default locale used to interpolate the constraint violation messages.
* <p>
* If not set, defaults to the system locale obtained via {@link Locale#getDefault()}.
*
* @since 6.1.1
*/
@Incubating
S defaultLocale(Locale defaultLocale);
}
Expand Up @@ -113,8 +113,8 @@ public abstract class AbstractConfigurationImpl<T extends BaseHibernateValidator
private Object constraintValidatorPayload;
private GetterPropertySelectionStrategy getterPropertySelectionStrategy;

// locales to initialize eagerly
private Set<Locale> localesToInitialize = Collections.emptySet();
// the default locale
private Locale defaultLocale = Locale.getDefault();

protected AbstractConfigurationImpl(BootstrapState state) {
this();
Expand Down Expand Up @@ -325,6 +325,14 @@ public T getterPropertySelectionStrategy(GetterPropertySelectionStrategy getterP
return thisAsT();
}

@Override
public T defaultLocale(Locale defaultLocale) {
Contracts.assertNotNull( defaultLocale, MESSAGES.parameterMustNotBeNull( "defaultLocale" ) );

this.defaultLocale = defaultLocale;
return thisAsT();
}

public boolean isAllowParallelMethodsDefineParameterConstraints() {
return this.methodValidationConfigurationBuilder.isAllowParallelMethodsDefineParameterConstraints();
}
Expand Down Expand Up @@ -518,7 +526,8 @@ public ClassLoader getExternalClassLoader() {
@Override
public final MessageInterpolator getDefaultMessageInterpolator() {
if ( defaultMessageInterpolator == null ) {
defaultMessageInterpolator = new ResourceBundleMessageInterpolator( getDefaultResourceBundleLocator(), localesToInitialize );
defaultMessageInterpolator = new ResourceBundleMessageInterpolator( getDefaultResourceBundleLocator(), getAllLocalesToInitialize(),
defaultLocale );
}

return defaultMessageInterpolator;
Expand All @@ -538,7 +547,7 @@ public final ConstraintValidatorFactory getDefaultConstraintValidatorFactory() {
public final ResourceBundleLocator getDefaultResourceBundleLocator() {
if ( defaultResourceBundleLocator == null ) {
defaultResourceBundleLocator = new PlatformResourceBundleLocator(
ResourceBundleMessageInterpolator.USER_VALIDATION_MESSAGES, localesToInitialize );
ResourceBundleMessageInterpolator.USER_VALIDATION_MESSAGES, getAllLocalesToInitialize() );
}

return defaultResourceBundleLocator;
Expand Down Expand Up @@ -688,12 +697,12 @@ private MessageInterpolator getDefaultMessageInterpolatorConfiguredWithClassLoad
if ( externalClassLoader != null ) {
PlatformResourceBundleLocator userResourceBundleLocator = new PlatformResourceBundleLocator(
ResourceBundleMessageInterpolator.USER_VALIDATION_MESSAGES,
localesToInitialize,
getAllLocalesToInitialize(),
externalClassLoader
);
PlatformResourceBundleLocator contributorResourceBundleLocator = new PlatformResourceBundleLocator(
ResourceBundleMessageInterpolator.CONTRIBUTOR_VALIDATION_MESSAGES,
localesToInitialize,
getAllLocalesToInitialize(),
externalClassLoader,
true
);
Expand All @@ -707,7 +716,8 @@ private MessageInterpolator getDefaultMessageInterpolatorConfiguredWithClassLoad
return new ResourceBundleMessageInterpolator(
userResourceBundleLocator,
contributorResourceBundleLocator,
localesToInitialize
getAllLocalesToInitialize(),
defaultLocale
);
}
finally {
Expand All @@ -719,8 +729,13 @@ private MessageInterpolator getDefaultMessageInterpolatorConfiguredWithClassLoad
}
}

protected void setLocalesToInitialize(Set<Locale> localesToInitialize) {
this.localesToInitialize = localesToInitialize;
protected final Locale getDefaultLocale() {
return defaultLocale;
}

protected Set<Locale> getAllLocalesToInitialize() {
// By default, we return an empty set meaning that we will dynamically initialize the locales.
return Collections.emptySet();
}

@SuppressWarnings("unchecked")
Expand Down
Expand Up @@ -6,6 +6,9 @@
*/
package org.hibernate.validator.internal.engine;

import static org.hibernate.validator.internal.util.logging.Messages.MESSAGES;

import java.util.Collections;
import java.util.Locale;
import java.util.Set;

Expand All @@ -15,6 +18,7 @@

import org.hibernate.validator.PredefinedScopeHibernateValidatorConfiguration;
import org.hibernate.validator.internal.util.CollectionHelper;
import org.hibernate.validator.internal.util.Contracts;
import org.hibernate.validator.metadata.BeanMetaDataClassNormalizer;

/**
Expand All @@ -27,6 +31,13 @@ public class PredefinedScopeConfigurationImpl extends AbstractConfigurationImpl<

private BeanMetaDataClassNormalizer beanMetaDataClassNormalizer;

/**
* Locales to initialize eagerly.
* <p>
* We will always include the default locale in the final list.
*/
private Set<Locale> localesToInitialize = Collections.emptySet();

public PredefinedScopeConfigurationImpl(BootstrapState state) {
super( state );
}
Expand All @@ -46,8 +57,9 @@ public Set<Class<?>> getBeanClassesToInitialize() {
}

@Override
public PredefinedScopeHibernateValidatorConfiguration initializeLocales(Set<Locale> locales) {
setLocalesToInitialize( CollectionHelper.toImmutableSet( locales ) );
public PredefinedScopeHibernateValidatorConfiguration initializeLocales(Set<Locale> localesToInitialize) {
Contracts.assertNotNull( localesToInitialize, MESSAGES.parameterMustNotBeNull( "localesToInitialize" ) );
this.localesToInitialize = localesToInitialize;
return thisAsT();
}

Expand All @@ -60,4 +72,16 @@ public PredefinedScopeHibernateValidatorConfiguration beanMetaDataClassNormalize
public BeanMetaDataClassNormalizer getBeanMetaDataClassNormalizer() {
return beanMetaDataClassNormalizer;
}

@Override
protected Set<Locale> getAllLocalesToInitialize() {
if ( localesToInitialize.isEmpty() ) {
return Collections.singleton( getDefaultLocale() );
}

Set<Locale> allLocales = CollectionHelper.newHashSet( localesToInitialize.size() + 1 );
allLocales.addAll( localesToInitialize );
allLocales.add( getDefaultLocale() );
return Collections.unmodifiableSet( allLocales );
}
}
Expand Up @@ -7,6 +7,7 @@
package org.hibernate.validator.messageinterpolation;

import static org.hibernate.validator.internal.util.ConcurrentReferenceHashMap.ReferenceType.SOFT;
import static org.hibernate.validator.internal.util.logging.Messages.MESSAGES;

import java.lang.invoke.MethodHandles;
import java.util.Collections;
Expand All @@ -20,15 +21,16 @@
import java.util.regex.Pattern;

import javax.validation.MessageInterpolator;
import javax.validation.ValidationException;

import org.hibernate.validator.Incubating;
import org.hibernate.validator.internal.engine.messageinterpolation.InterpolationTermType;
import org.hibernate.validator.internal.engine.messageinterpolation.LocalizedMessage;
import org.hibernate.validator.internal.engine.messageinterpolation.parser.MessageDescriptorFormatException;
import org.hibernate.validator.internal.engine.messageinterpolation.parser.Token;
import org.hibernate.validator.internal.engine.messageinterpolation.parser.TokenCollector;
import org.hibernate.validator.internal.engine.messageinterpolation.parser.TokenIterator;
import org.hibernate.validator.internal.util.ConcurrentReferenceHashMap;
import org.hibernate.validator.internal.util.Contracts;
import org.hibernate.validator.internal.util.logging.Log;
import org.hibernate.validator.internal.util.logging.LoggerFactory;
import org.hibernate.validator.resourceloading.PlatformResourceBundleLocator;
Expand Down Expand Up @@ -131,7 +133,7 @@ public abstract class AbstractMessageInterpolator implements MessageInterpolator
* {@code MessageInterpolator} using the default resource bundle locators.
*/
public AbstractMessageInterpolator() {
this( Collections.emptySet() );
this( Collections.emptySet(), Locale.getDefault() );
}

/**
Expand All @@ -140,7 +142,7 @@ public AbstractMessageInterpolator() {
* @param userResourceBundleLocator {@code ResourceBundleLocator} used to load user provided resource bundle
*/
public AbstractMessageInterpolator(ResourceBundleLocator userResourceBundleLocator) {
this( userResourceBundleLocator, Collections.emptySet() );
this( userResourceBundleLocator, Collections.emptySet(), Locale.getDefault() );
}

/**
Expand All @@ -152,7 +154,7 @@ public AbstractMessageInterpolator(ResourceBundleLocator userResourceBundleLocat
*/
public AbstractMessageInterpolator(ResourceBundleLocator userResourceBundleLocator,
ResourceBundleLocator contributorResourceBundleLocator) {
this( userResourceBundleLocator, contributorResourceBundleLocator, Collections.emptySet() );
this( userResourceBundleLocator, contributorResourceBundleLocator, Collections.emptySet(), Locale.getDefault() );
}

/**
Expand All @@ -166,61 +168,74 @@ public AbstractMessageInterpolator(ResourceBundleLocator userResourceBundleLocat
public AbstractMessageInterpolator(ResourceBundleLocator userResourceBundleLocator,
ResourceBundleLocator contributorResourceBundleLocator,
boolean cacheMessages) {
this( userResourceBundleLocator, contributorResourceBundleLocator, Collections.emptySet(), cacheMessages );
this( userResourceBundleLocator, contributorResourceBundleLocator, Collections.emptySet(), Locale.getDefault(), cacheMessages );
}

/**
* {@code MessageInterpolator} using the default resource bundle locators.
*
* @param localesToInitialize The set of locales to initialize at bootstrap.
* @param localesToInitialize the set of locales to initialize at bootstrap
* @param defaultLocale the default locale
*
* @since 6.1
* @since 6.1.1
*/
public AbstractMessageInterpolator(Set<Locale> localesToInitialize) {
this( null, localesToInitialize );
@Incubating
public AbstractMessageInterpolator(Set<Locale> localesToInitialize, Locale defaultLocale) {
this( null, localesToInitialize, defaultLocale );
}

/**
* {@code MessageInterpolator} taking a resource bundle locator.
*
* @param userResourceBundleLocator {@code ResourceBundleLocator} used to load user provided resource bundle
* @param localesToInitialize The set of locales to initialize at bootstrap.
* @param localesToInitialize the set of locales to initialize at bootstrap
* @param defaultLocale the default locale
*
* @since 6.1
* @since 6.1.1
*/
public AbstractMessageInterpolator(ResourceBundleLocator userResourceBundleLocator, Set<Locale> localesToInitialize) {
this( userResourceBundleLocator, null, localesToInitialize );
@Incubating
public AbstractMessageInterpolator(ResourceBundleLocator userResourceBundleLocator, Set<Locale> localesToInitialize, Locale defaultLocale) {
this( userResourceBundleLocator, null, localesToInitialize, defaultLocale );
}

/**
* {@code MessageInterpolator} taking two resource bundle locators.
*
* @param userResourceBundleLocator {@code ResourceBundleLocator} used to load user provided resource bundle
* @param contributorResourceBundleLocator {@code ResourceBundleLocator} used to load resource bundle of constraint contributor
* @param localesToInitialize The set of locales to initialize at bootstrap.
* @param localesToInitialize the set of locales to initialize at bootstrap
* @param defaultLocale the default locale
*
* @since 6.1
* @since 6.1.1
*/
@Incubating
public AbstractMessageInterpolator(ResourceBundleLocator userResourceBundleLocator,
ResourceBundleLocator contributorResourceBundleLocator, Set<Locale> localesToInitialize) {
this( userResourceBundleLocator, contributorResourceBundleLocator, localesToInitialize, true );
ResourceBundleLocator contributorResourceBundleLocator, Set<Locale> localesToInitialize,
Locale defaultLocale) {
this( userResourceBundleLocator, contributorResourceBundleLocator, localesToInitialize, defaultLocale, true );
}

/**
* {@code MessageInterpolator} taking two resource bundle locators.
*
* @param userResourceBundleLocator {@code ResourceBundleLocator} used to load user provided resource bundle
* @param contributorResourceBundleLocator {@code ResourceBundleLocator} used to load resource bundle of constraint contributor
* @param localesToInitialize The set of locales to initialize at bootstrap.
* @param cacheMessages Whether resolved messages should be cached or not.
* @param localesToInitialize the set of locales to initialize at bootstrap
* @param defaultLocale the default locale
* @param cacheMessages whether resolved messages should be cached or not
*
* @since 6.1
* @since 6.1.1
*/
@Incubating
public AbstractMessageInterpolator(ResourceBundleLocator userResourceBundleLocator,
ResourceBundleLocator contributorResourceBundleLocator,
Set<Locale> localesToInitialize,
Locale defaultLocale,
boolean cacheMessages) {
defaultLocale = Locale.getDefault();
Contracts.assertNotNull( localesToInitialize, MESSAGES.parameterMustNotBeNull( "localesToInitialize" ) );
Contracts.assertNotNull( defaultLocale, MESSAGES.parameterMustNotBeNull( "defaultLocale" ) );

this.defaultLocale = defaultLocale;

if ( userResourceBundleLocator == null ) {
this.userResourceBundleLocator = new PlatformResourceBundleLocator( USER_VALIDATION_MESSAGES, localesToInitialize );
Expand Down Expand Up @@ -297,7 +312,7 @@ public String interpolate(String message, Context context, Locale locale) {
try {
interpolatedMessage = interpolateMessage( message, context, locale );
}
catch (ValidationException e) {
catch (MessageDescriptorFormatException e) {
LOG.warn( e.getMessage() );
}
return interpolatedMessage;
Expand Down
Expand Up @@ -11,6 +11,7 @@
import java.util.Locale;
import java.util.Set;

import org.hibernate.validator.Incubating;
import org.hibernate.validator.internal.engine.messageinterpolation.InterpolationTerm;
import org.hibernate.validator.internal.engine.messageinterpolation.ParameterTermResolver;
import org.hibernate.validator.internal.util.logging.Log;
Expand All @@ -28,11 +29,15 @@ public class ParameterMessageInterpolator extends AbstractMessageInterpolator {
private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() );

public ParameterMessageInterpolator() {
this( Collections.emptySet() );
this( Collections.emptySet(), Locale.getDefault() );
}

public ParameterMessageInterpolator(Set<Locale> localesToInitialize) {
super( localesToInitialize );
/**
* @since 6.1.1
*/
@Incubating
public ParameterMessageInterpolator(Set<Locale> localesToInitialize, Locale defaultLocale) {
super( localesToInitialize, defaultLocale );
}

@Override
Expand Down

0 comments on commit a9c46f4

Please sign in to comment.