/*
* Hibernate Validator, declare and validate application constraints
*
* License: Apache License, Version 2.0
* See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>.
*/
package org.hibernate.validator.test.internal.engine.messageinterpolation;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.ResourceBundle;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import javax.validation.MessageInterpolator;
import javax.validation.constraints.Max;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.hibernate.validator.internal.engine.MessageInterpolatorContext;
import org.hibernate.validator.internal.metadata.core.ConstraintHelper;
import org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl;
import org.hibernate.validator.internal.util.annotationfactory.AnnotationDescriptor;
import org.hibernate.validator.internal.util.annotationfactory.AnnotationFactory;
import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator;
import org.hibernate.validator.spi.resourceloading.ResourceBundleLocator;
import org.hibernate.validator.testutil.TestForIssue;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import static org.testng.Assert.assertEquals;
/**
* Tests for message interpolation.
*
* @author Hardy Ferentschik
*/
public class ResourceBundleMessageInterpolatorTest {
private ResourceBundleMessageInterpolator interpolator;
private NotNull notNull;
private ConstraintDescriptorImpl<NotNull> notNullDescriptor;
private Size size;
private ConstraintDescriptorImpl<Size> sizeDescriptor;
@BeforeTest
public void setUp() {
// Create some annotations for testing using AnnotationProxies
AnnotationDescriptor<NotNull> descriptor = new AnnotationDescriptor<NotNull>( NotNull.class );
notNull = AnnotationFactory.create( descriptor );
notNullDescriptor = new ConstraintDescriptorImpl<NotNull>(
new ConstraintHelper(),
null,
notNull,
java.lang.annotation.ElementType.FIELD
);
AnnotationDescriptor<Size> sizeAnnotationDescriptor = new AnnotationDescriptor<Size>( Size.class );
size = AnnotationFactory.create( sizeAnnotationDescriptor );
sizeDescriptor = new ConstraintDescriptorImpl<Size>(
new ConstraintHelper(),
null,
size,
java.lang.annotation.ElementType.FIELD
);
}
@Test
public void testSuccessfulInterpolation() {
interpolator = new ResourceBundleMessageInterpolator(
new TestResourceBundleLocator()
);
MessageInterpolator.Context context = createMessageInterpolatorContext( notNullDescriptor );
String expected = "message interpolation successful";
String actual = interpolator.interpolate( "{simple.key}", context );
assertEquals( actual, expected, "Wrong substitution" );
expected = "message interpolation successful message interpolation successful";
actual = interpolator.interpolate( "{simple.key} {simple.key}", context );
assertEquals( actual, expected, "Wrong substitution" );
expected = "The message interpolation successful completed";
actual = interpolator.interpolate( "The {simple.key} completed", context );
assertEquals( actual, expected, "Wrong substitution" );
expected = "{{simple.key}}";
actual = interpolator.interpolate( "{{simple.key}}", context );
assertEquals( actual, expected, "Wrong substitution" );
}
@Test
public void testMessageLiterals() {
interpolator = new ResourceBundleMessageInterpolator(
new TestResourceBundleLocator()
);
MessageInterpolatorContext messageInterpolatorContext = createMessageInterpolatorContext( notNullDescriptor );
String expected = "{";
String actual = interpolator.interpolate( "\\{", messageInterpolatorContext );
assertEquals( actual, expected, "Wrong substitution" );
expected = "}";
actual = interpolator.interpolate( "\\}", messageInterpolatorContext );
assertEquals( actual, expected, "Wrong substitution" );
expected = "\\";
actual = interpolator.interpolate( "\\", messageInterpolatorContext );
assertEquals( actual, expected, "Wrong substitution" );
}
@Test
public void testUnSuccessfulInterpolation() {
interpolator = new ResourceBundleMessageInterpolator(
new TestResourceBundleLocator()
);
MessageInterpolatorContext messageInterpolatorContext = createMessageInterpolatorContext( notNullDescriptor );
String expected = "foo"; // missing {}
String actual = interpolator.interpolate( "foo", messageInterpolatorContext );
assertEquals( actual, expected, "Wrong substitution" );
expected = "#{foo {}";
actual = interpolator.interpolate( "#{foo {}", messageInterpolatorContext );
assertEquals( actual, expected, "Wrong substitution" );
}
@Test
public void testUnknownTokenInterpolation() {
interpolator = new ResourceBundleMessageInterpolator(
new TestResourceBundleLocator()
);
MessageInterpolatorContext messageInterpolatorContext = createMessageInterpolatorContext( notNullDescriptor );
String expected = "{bar}"; // unknown token {}
String actual = interpolator.interpolate( "{bar}", messageInterpolatorContext );
assertEquals( actual, expected, "Wrong substitution" );
}
@Test
public void testKeyWithDashes() {
interpolator = new ResourceBundleMessageInterpolator(
new TestResourceBundleLocator()
);
MessageInterpolatorContext messageInterpolatorContext = createMessageInterpolatorContext( notNullDescriptor );
String expected = "message interpolation successful"; // unknown token {}
String actual = interpolator.interpolate( "{key-with-dashes}", messageInterpolatorContext );
assertEquals( actual, expected, "Wrong substitution" );
}
@Test
public void testKeyWithSpaces() {
interpolator = new ResourceBundleMessageInterpolator(
new TestResourceBundleLocator()
);
MessageInterpolatorContext messageInterpolatorContext = createMessageInterpolatorContext( notNullDescriptor );
String expected = "message interpolation successful"; // unknown token {}
String actual = interpolator.interpolate( "{key with spaces}", messageInterpolatorContext );
assertEquals( actual, expected, "Wrong substitution" );
}
@Test
public void testDefaultInterpolation() {
interpolator = new ResourceBundleMessageInterpolator(
new TestResourceBundleLocator()
);
MessageInterpolatorContext messageInterpolatorContext = createMessageInterpolatorContext( notNullDescriptor );
String expected = "may not be null";
String actual = interpolator.interpolate( notNull.message(), messageInterpolatorContext );
assertEquals( actual, expected, "Wrong substitution" );
expected = "size must be between 0 and 2147483647"; // unknown token {}
messageInterpolatorContext = createMessageInterpolatorContext( sizeDescriptor );
actual = interpolator.interpolate( size.message(), messageInterpolatorContext );
assertEquals( actual, expected, "Wrong substitution" );
}
@Test
public void testMessageInterpolationWithLocale() {
interpolator = new ResourceBundleMessageInterpolator();
MessageInterpolatorContext messageInterpolatorContext = createMessageInterpolatorContext( notNullDescriptor );
String expected = "darf nicht null sein";
String actual = interpolator.interpolate( notNull.message(), messageInterpolatorContext, Locale.GERMAN );
assertEquals( actual, expected, "Wrong substitution" );
}
@Test
public void testUserResourceBundle() {
interpolator = new ResourceBundleMessageInterpolator();
MessageInterpolatorContext messageInterpolatorContext = createMessageInterpolatorContext( notNullDescriptor );
String expected = "no puede ser null";
String actual = interpolator.interpolate(
notNull.message(),
messageInterpolatorContext,
new Locale( "es", "ES" )
);
assertEquals( actual, expected, "Wrong substitution" );
}
@Test
@TestForIssue(jiraKey = "HV-102")
public void testRecursiveMessageInterpolation() {
AnnotationDescriptor<Max> descriptor = new AnnotationDescriptor<Max>( Max.class );
descriptor.setValue( "message", "{replace.in.user.bundle1}" );
descriptor.setValue( "value", 10L );
Max max = AnnotationFactory.create( descriptor );
ConstraintDescriptorImpl<Max> constraintDescriptor = new ConstraintDescriptorImpl<Max>(
new ConstraintHelper(),
null,
max,
java.lang.annotation.ElementType.FIELD
);
interpolator = new ResourceBundleMessageInterpolator(
new TestResourceBundleLocator()
);
MessageInterpolator.Context messageInterpolatorContext = createMessageInterpolatorContext( constraintDescriptor );
String expected = "{replace.in.default.bundle2}";
String actual = interpolator.interpolate( max.message(), messageInterpolatorContext );
assertEquals(
actual, expected, "Within default bundle replacement parameter evaluation should not be recursive!"
);
}
@Test
@TestForIssue(jiraKey = "HV-182")
public void testCorrectMessageInterpolationIfParameterCannotBeReplaced() {
AnnotationDescriptor<Max> descriptor = new AnnotationDescriptor<Max>( Max.class );
String message = "Message should stay unchanged since {fubar} is not replaceable";
descriptor.setValue( "message", message );
descriptor.setValue( "value", 10L );
Max max = AnnotationFactory.create( descriptor );
ConstraintDescriptorImpl<Max> constraintDescriptor = new ConstraintDescriptorImpl<Max>(
new ConstraintHelper(),
null,
max,
java.lang.annotation.ElementType.FIELD
);
interpolator = new ResourceBundleMessageInterpolator(
new TestResourceBundleLocator()
);
MessageInterpolator.Context messageInterpolatorContext = createMessageInterpolatorContext( constraintDescriptor );
String actual = interpolator.interpolate( max.message(), messageInterpolatorContext );
assertEquals(
actual, message, "The message should not have changed."
);
}
@Test(expectedExceptions = RuntimeException.class,
expectedExceptionsMessageRegExp = "ReadOnceOnlyResourceBundle can only be accessed once!")
@TestForIssue(jiraKey = "HV-330")
public void testResourceBundleGetsAccessedMultipleTimesWhenCachingIsDisabled() {
runInterpolation( false );
}
@Test
@TestForIssue(jiraKey = "HV-330")
public void testResourceBundleGetsAccessedOnlyOnceWhenCachingIsEnabled() {
runInterpolation( true );
}
private MessageInterpolatorContext createMessageInterpolatorContext(ConstraintDescriptorImpl<?> descriptor) {
return new MessageInterpolatorContext(
descriptor,
null,
null,
Collections.<String, Object>emptyMap()
);
}
private void runInterpolation(boolean cachingEnabled) {
ReadOnceOnlyResourceBundle testBundle = new ReadOnceOnlyResourceBundle();
interpolator = new ResourceBundleMessageInterpolator(
new TestResourceBundleLocator( testBundle ), cachingEnabled
);
MessageInterpolator.Context messageInterpolatorContext = createMessageInterpolatorContext( notNullDescriptor );
// the ReadOnceOnlyResourceBundle will throw an exception in case the bundle is read more than once!
for ( int i = 0; i < 3; i++ ) {
String expected = "fixed";
String actual = interpolator.interpolate( "{hv-330}", messageInterpolatorContext );
assertEquals( actual, expected, "Wrong interpolation" );
}
}
/**
* A dummy locator always returning a {@link org.hibernate.validator.test.internal.engine.messageinterpolation.ResourceBundleMessageInterpolatorTest.ReadOnceOnlyResourceBundle}.
*/
private static class TestResourceBundleLocator implements ResourceBundleLocator {
private final ResourceBundle resourceBundle;
public TestResourceBundleLocator() {
this( new TestResourceBundle() );
}
public TestResourceBundleLocator(ResourceBundle bundle) {
resourceBundle = bundle;
}
@Override
public ResourceBundle getResourceBundle(Locale locale) {
return resourceBundle;
}
}
/**
* A dummy resource bundle which can be passed to the constructor of ResourceBundleMessageInterpolator to replace
* the user specified resource bundle.
*/
private static class TestResourceBundle extends ResourceBundle implements Enumeration<String> {
private final Map<String, String> testResources;
Iterator<String> iter;
public TestResourceBundle() {
testResources = new HashMap<String, String>();
// add some test messages
testResources.put( "simple.key", "message interpolation successful" );
testResources.put( "key-with-dashes", "message interpolation successful" );
testResources.put( "key with spaces", "message interpolation successful" );
testResources.put( "replace.in.user.bundle1", "{replace.in.user.bundle2}" );
testResources.put( "replace.in.user.bundle2", "{replace.in.default.bundle1}" );
iter = testResources.keySet().iterator();
}
@Override
public Object handleGetObject(String key) {
return testResources.get( key );
}
@Override
public Enumeration<String> getKeys() {
return this;
}
@Override
public boolean hasMoreElements() {
return iter.hasNext();
}
@Override
public String nextElement() {
if ( hasMoreElements() ) {
return iter.next();
}
else {
throw new NoSuchElementException();
}
}
}
/**
* A dummy resource bundle which can be passed to the constructor of ResourceBundleMessageInterpolator to replace
* the user specified resource bundle.
*/
private static class ReadOnceOnlyResourceBundle extends ResourceBundle {
private AtomicInteger counter;
private final String key = "hv-330";
private final String value = "fixed";
public ReadOnceOnlyResourceBundle() {
counter = new AtomicInteger( 1 );
}
@Override
public Object handleGetObject(String key) {
if ( counter.decrementAndGet() < 0 ) {
throw new RuntimeException( "ReadOnceOnlyResourceBundle can only be accessed once!" );
}
if ( this.key.equals( key ) ) {
return value;
}
else {
throw new RuntimeException( "Unexpected key: " + key );
}
}
@Override
public Enumeration<String> getKeys() {
return new Enumeration<String>() {
AtomicBoolean hasMoreElements = new AtomicBoolean( Boolean.TRUE );
@Override
public boolean hasMoreElements() {
return hasMoreElements.getAndSet( Boolean.FALSE );
}
@Override
public String nextElement() {
return value;
}
};
}
}
}