From 86aeeb1e68abef9178b71045426c3c00e402c0e8 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Mon, 7 Feb 2022 10:47:39 +0100 Subject: [PATCH] Type-safe message bundles - add a missing validation for localized files - there was a missing validation that a localized file conflicts with the locale of the default message bundle interface - fixes #19617 --- .../deployment/MessageBundleProcessor.java | 22 +++++-- ...alizedBundleDefaultLocaleConflictTest.java | 55 +++++++++++++++++ .../LocalizedBundleLocaleConflictTest.java | 61 +++++++++++++++++++ ...LocalizedFileBundleLocaleConflictTest.java | 61 +++++++++++++++++++ ...ocalizedFileDefaultLocaleConflictTest.java | 51 ++++++++++++++++ .../io/quarkus/qute/i18n/MessageBundles.java | 7 ++- 6 files changed, 251 insertions(+), 6 deletions(-) create mode 100644 extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/LocalizedBundleDefaultLocaleConflictTest.java create mode 100644 extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/LocalizedBundleLocaleConflictTest.java create mode 100644 extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/LocalizedFileBundleLocaleConflictTest.java create mode 100644 extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/LocalizedFileDefaultLocaleConflictTest.java diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java index 68842ed04f045..a226da57c0229 100644 --- a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java +++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/MessageBundleProcessor.java @@ -156,11 +156,17 @@ List processBundles(BeanArchiveIndexBuildItem beanArchiv Map localeToInterface = new HashMap<>(); for (ClassInfo localizedInterface : localized) { String locale = localizedInterface.classAnnotation(Names.LOCALIZED).value().asString(); + if (defaultLocale.equals(locale)) { + throw new MessageBundleException( + String.format( + "Locale of [%s] conflicts with the locale [%s] of the default message bundle [%s]", + localizedInterface, locale, bundleClass)); + } ClassInfo previous = localeToInterface.put(locale, localizedInterface); - if (defaultLocale.equals(locale) || previous != null) { + if (previous != null) { throw new MessageBundleException(String.format( - "A localized message bundle interface [%s] already exists for locale %s: [%s]", - previous != null ? previous : bundleClass, locale, localizedInterface)); + "Cannot register [%s] - a localized message bundle interface exists for locale [%s]: %s", + localizedInterface, locale, previous)); } localizedInterfaces.add(localizedInterface.name()); } @@ -172,12 +178,18 @@ List processBundles(BeanArchiveIndexBuildItem beanArchiv if (fileName.startsWith(name)) { // msg_en.txt -> en String locale = fileName.substring(fileName.indexOf('_') + 1, fileName.indexOf('.')); + if (defaultLocale.equals(locale)) { + throw new MessageBundleException( + String.format( + "Locale of [%s] conflicts with the locale [%s] of the default message bundle [%s]", + fileName, locale, bundleClass)); + } ClassInfo localizedInterface = localeToInterface.get(locale); if (localizedInterface != null) { throw new MessageBundleException( String.format( - "A localized message bundle interface [%s] already exists for locale %s: [%s]", - localizedInterface, locale, fileName)); + "Cannot register [%s] - a localized message bundle interface exists for locale [%s]: %s", + fileName, locale, localizedInterface)); } localeToFile.put(locale, messageFile); } diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/LocalizedBundleDefaultLocaleConflictTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/LocalizedBundleDefaultLocaleConflictTest.java new file mode 100644 index 0000000000000..742a11ad7b3eb --- /dev/null +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/LocalizedBundleDefaultLocaleConflictTest.java @@ -0,0 +1,55 @@ +package io.quarkus.qute.deployment.i18n; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.qute.deployment.MessageBundleException; +import io.quarkus.qute.i18n.Localized; +import io.quarkus.qute.i18n.Message; +import io.quarkus.qute.i18n.MessageBundle; +import io.quarkus.runtime.util.ExceptionUtil; +import io.quarkus.test.QuarkusUnitTest; + +public class LocalizedBundleDefaultLocaleConflictTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot(root -> root + .addClasses(Messages.class, EnMessages.class)) + .overrideConfigKey("quarkus.default-locale", "en") + .assertException(t -> { + Throwable rootCause = ExceptionUtil.getRootCause(t); + if (rootCause instanceof MessageBundleException) { + assertEquals( + "Locale of [io.quarkus.qute.deployment.i18n.LocalizedBundleDefaultLocaleConflictTest$EnMessages] conflicts with the locale [en] of the default message bundle [io.quarkus.qute.deployment.i18n.LocalizedBundleDefaultLocaleConflictTest$Messages]", + rootCause.getMessage()); + } else { + fail("No message bundle exception thrown: " + t); + } + }); + + @Test + public void testValidation() { + fail(); + } + + @MessageBundle + public interface Messages { + + @Message("Ahoj svete!") + String helloWorld(); + + } + + @Localized("en") + public interface EnMessages extends Messages { + + @Message("Hello world!") + String helloWorld(); + + } + +} diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/LocalizedBundleLocaleConflictTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/LocalizedBundleLocaleConflictTest.java new file mode 100644 index 0000000000000..caa19fb303fd7 --- /dev/null +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/LocalizedBundleLocaleConflictTest.java @@ -0,0 +1,61 @@ +package io.quarkus.qute.deployment.i18n; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.qute.deployment.MessageBundleException; +import io.quarkus.qute.i18n.Localized; +import io.quarkus.qute.i18n.Message; +import io.quarkus.qute.i18n.MessageBundle; +import io.quarkus.runtime.util.ExceptionUtil; +import io.quarkus.test.QuarkusUnitTest; + +public class LocalizedBundleLocaleConflictTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot(root -> root + .addClasses(Messages.class, EnMessages.class, AnotherEnMessages.class)) + .overrideConfigKey("quarkus.default-locale", "cs") + .assertException(t -> { + Throwable rootCause = ExceptionUtil.getRootCause(t); + if (rootCause instanceof MessageBundleException) { + assertTrue(rootCause.getMessage().contains("a localized message bundle interface exists for locale [en]")); + } else { + fail("No message bundle exception thrown: " + t); + } + }); + + @Test + public void testValidation() { + fail(); + } + + @MessageBundle + public interface Messages { + + @Message("Ahoj svete!") + String helloWorld(); + + } + + @Localized("en") + public interface EnMessages extends Messages { + + @Message("Hello world!") + String helloWorld(); + + } + + @Localized("en") + public interface AnotherEnMessages extends Messages { + + @Message("Hello world!") + String helloWorld(); + + } + +} diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/LocalizedFileBundleLocaleConflictTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/LocalizedFileBundleLocaleConflictTest.java new file mode 100644 index 0000000000000..e49fff4798deb --- /dev/null +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/LocalizedFileBundleLocaleConflictTest.java @@ -0,0 +1,61 @@ +package io.quarkus.qute.deployment.i18n; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.qute.deployment.MessageBundleException; +import io.quarkus.qute.i18n.Localized; +import io.quarkus.qute.i18n.Message; +import io.quarkus.qute.i18n.MessageBundle; +import io.quarkus.runtime.util.ExceptionUtil; +import io.quarkus.test.QuarkusUnitTest; + +public class LocalizedFileBundleLocaleConflictTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(Messages.class, EnMessages.class) + // This localized file conflicts with the default locale + .addAsResource(new StringAsset( + "hello=Hello!"), + "messages/msg_en.properties")) + .overrideConfigKey("quarkus.default-locale", "cs") + .assertException(t -> { + Throwable rootCause = ExceptionUtil.getRootCause(t); + if (rootCause instanceof MessageBundleException) { + assertEquals( + "Cannot register [msg_en.properties] - a localized message bundle interface exists for locale [en]: io.quarkus.qute.deployment.i18n.LocalizedFileBundleLocaleConflictTest$EnMessages", + rootCause.getMessage()); + } else { + fail("No message bundle exception thrown: " + t); + } + + });; + + @Test + public void testValidation() { + fail(); + } + + @MessageBundle + public interface Messages { + + @Message("Ahoj svete!") + String helloWorld(); + + } + + @Localized("en") + public interface EnMessages extends Messages { + + @Message("Hello world!") + String helloWorld(); + + } + +} diff --git a/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/LocalizedFileDefaultLocaleConflictTest.java b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/LocalizedFileDefaultLocaleConflictTest.java new file mode 100644 index 0000000000000..b5012f059d2ff --- /dev/null +++ b/extensions/qute/deployment/src/test/java/io/quarkus/qute/deployment/i18n/LocalizedFileDefaultLocaleConflictTest.java @@ -0,0 +1,51 @@ +package io.quarkus.qute.deployment.i18n; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.qute.deployment.MessageBundleException; +import io.quarkus.qute.i18n.Message; +import io.quarkus.qute.i18n.MessageBundle; +import io.quarkus.runtime.util.ExceptionUtil; +import io.quarkus.test.QuarkusUnitTest; + +public class LocalizedFileDefaultLocaleConflictTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(Messages.class) + // This localized file conflicts with the default locale + .addAsResource(new StringAsset( + "hello=Hello!"), + "messages/msg_en.properties")) + .overrideConfigKey("quarkus.default-locale", "en") + .assertException(t -> { + Throwable rootCause = ExceptionUtil.getRootCause(t); + if (rootCause instanceof MessageBundleException) { + assertEquals( + "Locale of [msg_en.properties] conflicts with the locale [en] of the default message bundle [io.quarkus.qute.deployment.i18n.LocalizedFileDefaultLocaleConflictTest$Messages]", + rootCause.getMessage()); + } else { + fail("No message bundle exception thrown: " + t); + } + }); + + @Test + public void testValidation() { + fail(); + } + + @MessageBundle + public interface Messages { + + @Message("Hello world!") + String helloWorld(); + + } + +} diff --git a/extensions/qute/runtime/src/main/java/io/quarkus/qute/i18n/MessageBundles.java b/extensions/qute/runtime/src/main/java/io/quarkus/qute/i18n/MessageBundles.java index a46a841a343fa..11abb54d4da73 100644 --- a/extensions/qute/runtime/src/main/java/io/quarkus/qute/i18n/MessageBundles.java +++ b/extensions/qute/runtime/src/main/java/io/quarkus/qute/i18n/MessageBundles.java @@ -76,11 +76,16 @@ static void setupNamespaceResolvers(@Observes EngineBuilder builder, BundleConte continue; } Instance found = container.select(locEntry.getValue(), new Localized.Literal(locEntry.getKey())); - if (!found.isResolvable()) { + if (found.isUnsatisfied()) { throw new IllegalStateException( Qute.fmt("Bean not found for localized interface [{e.value}] and locale [{e.key}]") .data("e", locEntry).render()); } + if (found.isAmbiguous()) { + throw new IllegalStateException( + Qute.fmt("Multiple beans found for localized interface [{e.value}] and locale [{e.key}]") + .data("e", locEntry).render()); + } interfaces.put(locEntry.getKey(), (Resolver) found.get()); } final Resolver defaultResolver = resolver;