From 66ee1cb043c2e3908df71c051f2e0590505e9f31 Mon Sep 17 00:00:00 2001 From: b84330808 Date: Wed, 28 Oct 2026 00:30:52 +0900 Subject: [PATCH 1/2] Use Locale.ROOT when deriving config property names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AbstractConfig turns getter and setter names into property names with String.toLowerCase()/toUpperCase(), which follow the JVM default locale. Under a Turkish default locale "I" lowercases to a dotless "ı", so every property whose name starts with "i" was mangled: - appendParameters() wrote "ıothreads=8" instead of "iothreads=8" for ProtocolConfig#setIothreads (same for init, injvm, interface, ...). - refresh() looked up "dubbo.protocol.ıothreads", so dubbo.protocol.iothreads from system properties, dubbo.properties or the environment was silently ignored. Use Locale.ROOT in these helpers, as other parts of Dubbo already do. Co-Authored-By: Claude Opus 5.5 --- .../apache/dubbo/config/AbstractConfig.java | 15 ++++---- .../dubbo/config/AbstractConfigTest.java | 34 +++++++++++++++++++ 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractConfig.java index 35da43e059ee..98b59ba473d7 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractConfig.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractConfig.java @@ -59,6 +59,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -272,26 +273,27 @@ private static void appendParameters0( protected static String extractPropertyName(String setter) { String propertyName = setter.substring("set".length()); - propertyName = propertyName.substring(0, 1).toLowerCase() + propertyName.substring(1); + propertyName = propertyName.substring(0, 1).toLowerCase(Locale.ROOT) + propertyName.substring(1); return propertyName; } private static String calculatePropertyToGetter(String name) { - return "get" + name.substring(0, 1).toUpperCase() + name.substring(1); + return "get" + name.substring(0, 1).toUpperCase(Locale.ROOT) + name.substring(1); } private static String calculatePropertyToSetter(String name) { - return "set" + name.substring(0, 1).toUpperCase() + name.substring(1); + return "set" + name.substring(0, 1).toUpperCase(Locale.ROOT) + name.substring(1); } private static String calculatePropertyFromGetter(String name) { int i = name.startsWith("get") ? 3 : 2; - return StringUtils.camelToSplitName(name.substring(i, i + 1).toLowerCase() + name.substring(i + 1), "."); + return StringUtils.camelToSplitName( + name.substring(i, i + 1).toLowerCase(Locale.ROOT) + name.substring(i + 1), "."); } private static String calculateAttributeFromGetter(String getter) { int i = getter.startsWith("get") ? 3 : 2; - return getter.substring(i, i + 1).toLowerCase() + getter.substring(i + 1); + return getter.substring(i, i + 1).toLowerCase(Locale.ROOT) + getter.substring(i + 1); } private static void invokeSetParameters(Class c, Object o, Map map) { @@ -979,7 +981,8 @@ private void invokeSetParameters(Map values, Object obj) { private boolean isIgnoredAttribute(Class clazz, String propertyName) { Method getter = null; - String capitalizePropertyName = propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1); + String capitalizePropertyName = + propertyName.substring(0, 1).toUpperCase(Locale.ROOT) + propertyName.substring(1); try { getter = clazz.getMethod("get" + capitalizePropertyName); } catch (NoSuchMethodException e) { diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/AbstractConfigTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/AbstractConfigTest.java index db8944b51592..c1b98ddf0f15 100644 --- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/AbstractConfigTest.java +++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/AbstractConfigTest.java @@ -35,6 +35,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Properties; @@ -572,6 +573,39 @@ void testRefreshParametersWithOverrideConfigMode() { } } + // In the Turkish locale "I".toLowerCase() is the dotless "ı", which turned + // the property behind getIothreads()/setIothreads() into "ıothreads". + @Test + void testAppendParametersUnderTurkishLocale() { + Locale defaultLocale = Locale.getDefault(); + Locale.setDefault(Locale.forLanguageTag("tr-TR")); + try { + ProtocolConfig protocolConfig = new ProtocolConfig(); + protocolConfig.setIothreads(8); + + Map parameters = new HashMap<>(); + AbstractConfig.appendParameters(parameters, protocolConfig); + Assertions.assertEquals("8", parameters.get("iothreads"), parameters.toString()); + } finally { + Locale.setDefault(defaultLocale); + } + } + + @Test + void testRefreshUnderTurkishLocale() { + Locale defaultLocale = Locale.getDefault(); + Locale.setDefault(Locale.forLanguageTag("tr-TR")); + try { + SysProps.setProperty("dubbo.protocol.iothreads", "16"); + ProtocolConfig protocolConfig = new ProtocolConfig(); + protocolConfig.refresh(); + Assertions.assertEquals(16, protocolConfig.getIothreads()); + } finally { + Locale.setDefault(defaultLocale); + ApplicationModel.defaultModel().modelEnvironment().destroy(); + } + } + @Test void testOnlyPrefixedKeyTakeEffect() { try { From b8a2927dbc0119df255ad4069dcd5baf4aed4f96 Mon Sep 17 00:00:00 2001 From: b84330808 Date: Wed, 28 Oct 2026 17:15:13 +0900 Subject: [PATCH 2/2] Use Locale.ROOT for the remaining identifier case conversions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per review: the same Turkish-locale problem as in AbstractConfig also broke config mode parsing (STRİCT), protocol names (TRİPLE), the StringUtils key helpers and DubboBeanDefinitionParser. Bean property, setter and extension names in ReflectUtils, MethodUtils, PojoUtils, JavaBeanSerializeUtil, ExtensionLoader and EnvironmentAdapter are fixed the same way. Each fixed path has a tr-TR test. Co-Authored-By: Claude Opus 5.5 --- .../beanutil/JavaBeanSerializeUtil.java | 3 +- .../common/extension/ExtensionLoader.java | 7 +-- .../infra/support/EnvironmentAdapter.java | 3 +- .../dubbo/common/utils/MethodUtils.java | 3 +- .../apache/dubbo/common/utils/PojoUtils.java | 9 ++-- .../dubbo/common/utils/ReflectUtils.java | 7 +-- .../dubbo/common/utils/StringUtils.java | 15 +++--- .../config/context/AbstractConfigManager.java | 3 +- .../beanutil/JavaBeanSerializeUtilTest.java | 18 +++++++ .../dubbo/common/utils/MethodUtilsTest.java | 16 ++++++ .../dubbo/common/utils/PojoUtilsTest.java | 24 +++++++++ .../dubbo/common/utils/ReflectUtilsTest.java | 19 +++++++ .../dubbo/common/utils/StringUtilsTest.java | 17 ++++++ .../config/context/ConfigManagerTest.java | 18 +++++++ .../apache/dubbo/config/ServiceConfig.java | 7 +-- .../dubbo/config/ServiceConfigTest.java | 19 +++++++ .../schema/DubboBeanDefinitionParser.java | 5 +- .../schema/DubboBeanDefinitionParserTest.java | 52 +++++++++++++++++++ 18 files changed, 220 insertions(+), 25 deletions(-) create mode 100644 dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/schema/DubboBeanDefinitionParserTest.java diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/beanutil/JavaBeanSerializeUtil.java b/dubbo-common/src/main/java/org/apache/dubbo/common/beanutil/JavaBeanSerializeUtil.java index 0149cc9ee7c3..2ff1165811f0 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/beanutil/JavaBeanSerializeUtil.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/beanutil/JavaBeanSerializeUtil.java @@ -30,6 +30,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.IdentityHashMap; +import java.util.Locale; import java.util.Map; public final class JavaBeanSerializeUtil { @@ -306,7 +307,7 @@ private static void deserializeInternal( } private static Method getSetterMethod(Class cls, String property, Class valueCls) { - String name = "set" + property.substring(0, 1).toUpperCase() + property.substring(1); + String name = "set" + property.substring(0, 1).toUpperCase(Locale.ROOT) + property.substring(1); Method method = null; try { method = cls.getMethod(name, valueCls); diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/extension/ExtensionLoader.java b/dubbo-common/src/main/java/org/apache/dubbo/common/extension/ExtensionLoader.java index f95a9f19abf2..0b5925e9c734 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/extension/ExtensionLoader.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/extension/ExtensionLoader.java @@ -66,6 +66,7 @@ import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Properties; @@ -750,7 +751,7 @@ private IllegalStateException findException(String name) { int i = 1; for (Map.Entry entry : exceptions.entrySet()) { - if (entry.getKey().toLowerCase().startsWith(name.toLowerCase())) { + if (entry.getKey().toLowerCase(Locale.ROOT).startsWith(name.toLowerCase(Locale.ROOT))) { if (i == 1) { buf.append(", possible causes: "); } @@ -922,7 +923,7 @@ private void initExtension(T instance) { */ private String getSetterProperty(Method method) { return method.getName().length() > 3 - ? method.getName().substring(3, 4).toLowerCase() + ? method.getName().substring(3, 4).toLowerCase(Locale.ROOT) + method.getName().substring(4) : ""; } @@ -1428,7 +1429,7 @@ private String findAnnotationName(Class clazz) { if (name.endsWith(type.getSimpleName())) { name = name.substring(0, name.length() - type.getSimpleName().length()); } - return name.toLowerCase(); + return name.toLowerCase(Locale.ROOT); } @SuppressWarnings("unchecked") diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/infra/support/EnvironmentAdapter.java b/dubbo-common/src/main/java/org/apache/dubbo/common/infra/support/EnvironmentAdapter.java index 0ef472af614b..7e74016b5ec2 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/infra/support/EnvironmentAdapter.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/infra/support/EnvironmentAdapter.java @@ -24,6 +24,7 @@ import org.apache.dubbo.rpc.model.ScopeModelAware; import java.util.HashMap; +import java.util.Locale; import java.util.Map; import static org.apache.dubbo.common.constants.CommonConstants.COMMA_SPLIT_PATTERN; @@ -70,7 +71,7 @@ public Map getExtraAttributes(Map params) { String value = ConfigurationUtils.getProperty(applicationModel, key); if (value != null) { // since 3.2 - parameters.put(key.toLowerCase(), value); + parameters.put(key.toLowerCase(Locale.ROOT), value); // upper-case key kept for compatibility parameters.put(key, value); } diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/MethodUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/MethodUtils.java index a29d93f1eb4b..7d1de538d946 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/MethodUtils.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/MethodUtils.java @@ -27,6 +27,7 @@ import java.util.Arrays; import java.util.LinkedList; import java.util.List; +import java.util.Locale; import java.util.Objects; import java.util.function.Predicate; @@ -429,7 +430,7 @@ static String extractFieldName(Method method) { } if (StringUtils.isNotEmpty(fieldName)) { - fieldName = fieldName.substring(0, 1).toLowerCase() + fieldName.substring(1); + fieldName = fieldName.substring(0, 1).toLowerCase(Locale.ROOT) + fieldName.substring(1); } return fieldName; diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/PojoUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/PojoUtils.java index e861c3f2987d..1e191459a978 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/PojoUtils.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/PojoUtils.java @@ -48,6 +48,7 @@ import java.util.IdentityHashMap; import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -267,11 +268,11 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl String methodName = method.getName(); Object value = null; if (methodName.length() > 3 && methodName.startsWith("get")) { - value = map.get(methodName.substring(3, 4).toLowerCase() + methodName.substring(4)); + value = map.get(methodName.substring(3, 4).toLowerCase(Locale.ROOT) + methodName.substring(4)); } else if (methodName.length() > 2 && methodName.startsWith("is")) { - value = map.get(methodName.substring(2, 3).toLowerCase() + methodName.substring(3)); + value = map.get(methodName.substring(2, 3).toLowerCase(Locale.ROOT) + methodName.substring(3)); } else { - value = map.get(methodName.substring(0, 1).toLowerCase() + methodName.substring(1)); + value = map.get(methodName.substring(0, 1).toLowerCase(Locale.ROOT) + methodName.substring(1)); } if (value instanceof Map && !Map.class.isAssignableFrom(method.getReturnType())) { value = realize0(value, method.getReturnType(), null, new IdentityHashMap<>()); @@ -733,7 +734,7 @@ private static Object getDefaultValue(Class parameterType) { } private static Method getSetterMethod(Class cls, String property, Class valueCls) { - String name = "set" + property.substring(0, 1).toUpperCase() + property.substring(1); + String name = "set" + property.substring(0, 1).toUpperCase(Locale.ROOT) + property.substring(1); Method method = NAME_METHODS_CACHE.get(cls.getName() + "." + name + "(" + valueCls.getName() + ")"); if (method == null) { try { diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ReflectUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ReflectUtils.java index 3ab2a9d34284..9962c9c37a44 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ReflectUtils.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ReflectUtils.java @@ -39,6 +39,7 @@ import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -1102,11 +1103,11 @@ public static boolean isBeanPropertyReadMethod(Method method) { public static String getPropertyNameFromBeanReadMethod(Method method) { if (isBeanPropertyReadMethod(method)) { if (method.getName().startsWith("get")) { - return method.getName().substring(3, 4).toLowerCase() + return method.getName().substring(3, 4).toLowerCase(Locale.ROOT) + method.getName().substring(4); } if (method.getName().startsWith("is")) { - return method.getName().substring(2, 3).toLowerCase() + return method.getName().substring(2, 3).toLowerCase(Locale.ROOT) + method.getName().substring(3); } } @@ -1125,7 +1126,7 @@ public static boolean isBeanPropertyWriteMethod(Method method) { public static String getPropertyNameFromBeanWriteMethod(Method method) { if (isBeanPropertyWriteMethod(method)) { - return method.getName().substring(3, 4).toLowerCase() + return method.getName().substring(3, 4).toLowerCase(Locale.ROOT) + method.getName().substring(4); } return null; diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/StringUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/StringUtils.java index d140ed4454ef..08c7e5c9ccf7 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/StringUtils.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/StringUtils.java @@ -28,6 +28,7 @@ import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.TreeMap; @@ -944,7 +945,7 @@ public static String camelToSplitName(String camelName, String split) { if (!isWord(camelName)) { // convert Ab-Cd-Ef to ab-cd-ef if (isSplitCase(camelName, split.charAt(0))) { - return camelName.toLowerCase(); + return camelName.toLowerCase(Locale.ROOT); } // not camel case return camelName; @@ -968,7 +969,7 @@ public static String camelToSplitName(String camelName, String split) { buf.append(ch); } } - return buf == null ? camelName.toLowerCase() : buf.toString().toLowerCase(); + return buf == null ? camelName.toLowerCase(Locale.ROOT) : buf.toString().toLowerCase(Locale.ROOT); } private static boolean isSplitCase(String str, char separator) { @@ -1002,7 +1003,7 @@ private static boolean isWord(char ch) { * @return */ public static String snakeToSplitName(String snakeName, String split) { - String lowerCase = snakeName.toLowerCase(); + String lowerCase = snakeName.toLowerCase(Locale.ROOT); if (isSnakeCase(snakeName)) { return replace(lowerCase, "_", split); } @@ -1010,7 +1011,9 @@ public static String snakeToSplitName(String snakeName, String split) { } protected static boolean isSnakeCase(String str) { - return str.contains("_") || str.equals(str.toLowerCase()) || str.equals(str.toUpperCase()); + return str.contains("_") + || str.equals(str.toLowerCase(Locale.ROOT)) + || str.equals(str.toUpperCase(Locale.ROOT)); } /** @@ -1053,11 +1056,11 @@ public static String trim(String str) { } public static String toURLKey(String key) { - return key.toLowerCase().replaceAll(SEPARATOR_REGEX, HIDE_KEY_PREFIX); + return key.toLowerCase(Locale.ROOT).replaceAll(SEPARATOR_REGEX, HIDE_KEY_PREFIX); } public static String toOSStyleKey(String key) { - key = key.toUpperCase().replaceAll(DOT_REGEX, UNDERLINE_SEPARATOR); + key = key.toUpperCase(Locale.ROOT).replaceAll(DOT_REGEX, UNDERLINE_SEPARATOR); if (!key.startsWith("DUBBO_")) { key = "DUBBO_" + key; } diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/context/AbstractConfigManager.java b/dubbo-common/src/main/java/org/apache/dubbo/config/context/AbstractConfigManager.java index 8c2acd809205..d23104524b1b 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/config/context/AbstractConfigManager.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/config/context/AbstractConfigManager.java @@ -50,6 +50,7 @@ import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -121,7 +122,7 @@ public void initialize() throws IllegalStateException { String configModeStr = (String) configuration.getProperty(ConfigKeys.DUBBO_CONFIG_MODE); try { if (StringUtils.hasText(configModeStr)) { - this.configMode = ConfigMode.valueOf(configModeStr.toUpperCase()); + this.configMode = ConfigMode.valueOf(configModeStr.toUpperCase(Locale.ROOT)); } } catch (Exception e) { String msg = "Illegal '" + ConfigKeys.DUBBO_CONFIG_MODE + "' config value [" + configModeStr diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/beanutil/JavaBeanSerializeUtilTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/beanutil/JavaBeanSerializeUtilTest.java index 602d7603fd05..7e4f89596e63 100644 --- a/dubbo-common/src/test/java/org/apache/dubbo/common/beanutil/JavaBeanSerializeUtilTest.java +++ b/dubbo-common/src/test/java/org/apache/dubbo/common/beanutil/JavaBeanSerializeUtilTest.java @@ -28,6 +28,7 @@ import java.util.Collection; import java.util.Date; import java.util.HashMap; +import java.util.Locale; import java.util.Map; import java.util.UUID; @@ -56,6 +57,23 @@ void testSerialize_Primitive_NUll() { Assertions.assertNull(descriptor); } + // In the Turkish locale "i".toUpperCase() is the dotted "İ", so "setİnfoProfile" was looked up. + @Test + void testDeserializeBeanUnderTurkishLocale() { + PersonInfo personInfo = new PersonInfo(); + personInfo.setName("dubbo"); + JavaBeanDescriptor descriptor = new JavaBeanDescriptor(BigPerson.class.getName(), JavaBeanDescriptor.TYPE_BEAN); + descriptor.setProperty("infoProfile", personInfo); + Locale defaultLocale = Locale.getDefault(); + Locale.setDefault(Locale.forLanguageTag("tr-TR")); + try { + BigPerson person = (BigPerson) JavaBeanSerializeUtil.deserialize(descriptor); + Assertions.assertSame(personInfo, person.getInfoProfile()); + } finally { + Locale.setDefault(defaultLocale); + } + } + @Test void testDeserialize_Primitive() { JavaBeanDescriptor descriptor = new JavaBeanDescriptor(long.class.getName(), JavaBeanDescriptor.TYPE_PRIMITIVE); diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/utils/MethodUtilsTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/utils/MethodUtilsTest.java index 702306d3ae18..c2eb24c2b508 100644 --- a/dubbo-common/src/test/java/org/apache/dubbo/common/utils/MethodUtilsTest.java +++ b/dubbo-common/src/test/java/org/apache/dubbo/common/utils/MethodUtilsTest.java @@ -16,7 +16,10 @@ */ package org.apache.dubbo.common.utils; +import org.apache.dubbo.rpc.model.person.BigPerson; + import java.lang.reflect.Method; +import java.util.Locale; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -118,6 +121,19 @@ void testExtractFieldName() throws Exception { Assertions.assertEquals("fieldName3", MethodUtils.extractFieldName(m7)); } + // In the Turkish locale "I".toLowerCase() is the dotless "ı". + @Test + void testExtractFieldNameUnderTurkishLocale() throws Exception { + Locale defaultLocale = Locale.getDefault(); + Locale.setDefault(Locale.forLanguageTag("tr-TR")); + try { + Method getter = BigPerson.class.getMethod("getInfoProfile"); + Assertions.assertEquals("infoProfile", MethodUtils.extractFieldName(getter)); + } finally { + Locale.setDefault(defaultLocale); + } + } + public class MethodFieldTestClazz { public String is() { return ""; diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/utils/PojoUtilsTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/utils/PojoUtilsTest.java index 5cb48a25a77c..c17c7739c745 100644 --- a/dubbo-common/src/test/java/org/apache/dubbo/common/utils/PojoUtilsTest.java +++ b/dubbo-common/src/test/java/org/apache/dubbo/common/utils/PojoUtilsTest.java @@ -45,6 +45,7 @@ import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.UUID; @@ -296,6 +297,23 @@ void testMapToInterface() throws Exception { assertTrue(message.isUrgent()); } + // In the Turkish locale "I".toLowerCase() is the dotless "ı", so getId() looked up "ıd" in the map. + @Test + void testMapToInterfaceUnderTurkishLocale() { + Map map = new HashMap<>(); + map.put("id", "T-1"); + map.put("important", true); + Locale defaultLocale = Locale.getDefault(); + Locale.setDefault(Locale.forLanguageTag("tr-TR")); + try { + Ticket ticket = (Ticket) PojoUtils.realize(map, Ticket.class); + assertThat(ticket.getId(), equalTo("T-1")); + assertTrue(ticket.isImportant()); + } finally { + Locale.setDefault(defaultLocale); + } + } + @Test void testJsonObjectToMap() throws Exception { Method method = PojoUtilsTest.class.getMethod("setMap", Map.class); @@ -1286,4 +1304,10 @@ interface Message { boolean isUrgent(); } + + interface Ticket { + String getId(); + + boolean isImportant(); + } } diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/utils/ReflectUtilsTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/utils/ReflectUtilsTest.java index c987b81c30bb..20983cbb401a 100644 --- a/dubbo-common/src/test/java/org/apache/dubbo/common/utils/ReflectUtilsTest.java +++ b/dubbo-common/src/test/java/org/apache/dubbo/common/utils/ReflectUtilsTest.java @@ -16,6 +16,9 @@ */ package org.apache.dubbo.common.utils; +import org.apache.dubbo.rpc.model.person.BigPerson; +import org.apache.dubbo.rpc.model.person.PersonInfo; + import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; @@ -24,6 +27,7 @@ import java.util.Collection; import java.util.Date; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; @@ -257,6 +261,21 @@ void testGetPropertyNameFromBeanWriteMethod() throws Exception { assertEquals("property", ReflectUtils.getPropertyNameFromBeanWriteMethod(method)); } + // In the Turkish locale "I".toLowerCase() is the dotless "ı". + @Test + void testGetPropertyNameFromBeanMethodUnderTurkishLocale() throws Exception { + Locale defaultLocale = Locale.getDefault(); + Locale.setDefault(Locale.forLanguageTag("tr-TR")); + try { + Method getter = BigPerson.class.getMethod("getInfoProfile"); + assertEquals("infoProfile", ReflectUtils.getPropertyNameFromBeanReadMethod(getter)); + Method setter = BigPerson.class.getMethod("setInfoProfile", PersonInfo.class); + assertEquals("infoProfile", ReflectUtils.getPropertyNameFromBeanWriteMethod(setter)); + } finally { + Locale.setDefault(defaultLocale); + } + } + @Test void testIsPublicInstanceField() throws Exception { Field field = EmptyClass.class.getDeclaredField("set"); diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/utils/StringUtilsTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/utils/StringUtilsTest.java index b8250019dea5..7082b83c59d1 100644 --- a/dubbo-common/src/test/java/org/apache/dubbo/common/utils/StringUtilsTest.java +++ b/dubbo-common/src/test/java/org/apache/dubbo/common/utils/StringUtilsTest.java @@ -21,6 +21,7 @@ import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; @@ -408,6 +409,22 @@ void testToOSStyleKey() { assertEquals("DUBBO_TAG1", StringUtils.toOSStyleKey("tag1")); } + // In the Turkish locale "I".toLowerCase() is the dotless "ı" and "i".toUpperCase() is the dotted "İ". + @Test + void testSplitNamesAndKeysUnderTurkishLocale() { + Locale defaultLocale = Locale.getDefault(); + Locale.setDefault(Locale.forLanguageTag("tr-TR")); + try { + assertEquals("io-threads", StringUtils.camelToSplitName("IO-THREADS", "-")); + assertEquals("io-threads", StringUtils.snakeToSplitName("IO_THREADS", "-")); + assertEquals("iothreads", StringUtils.convertToSplitName("IOTHREADS", "-")); + assertEquals("dubbo.protocol.iothreads", StringUtils.toURLKey("DUBBO_PROTOCOL_IOTHREADS")); + assertEquals("DUBBO_PROTOCOL_IOTHREADS", StringUtils.toOSStyleKey("dubbo.protocol.iothreads")); + } finally { + Locale.setDefault(defaultLocale); + } + } + @Test void testParseParameters() { String legalStr = "[{key1:value1},{key2:value2}]"; diff --git a/dubbo-common/src/test/java/org/apache/dubbo/config/context/ConfigManagerTest.java b/dubbo-common/src/test/java/org/apache/dubbo/config/context/ConfigManagerTest.java index 74121b9b4923..97360e833eab 100644 --- a/dubbo-common/src/test/java/org/apache/dubbo/config/context/ConfigManagerTest.java +++ b/dubbo-common/src/test/java/org/apache/dubbo/config/context/ConfigManagerTest.java @@ -31,6 +31,7 @@ import org.apache.dubbo.rpc.model.FrameworkModel; import java.util.Collection; +import java.util.Locale; import java.util.Optional; import org.junit.jupiter.api.Assertions; @@ -376,6 +377,23 @@ void testConfigMode() { } } + // In the Turkish locale "override".toUpperCase() is "OVERRİDE", which is not a ConfigMode constant. + @Test + void testConfigModeUnderTurkishLocale() { + Locale defaultLocale = Locale.getDefault(); + Locale.setDefault(Locale.forLanguageTag("tr-TR")); + try { + System.setProperty(DUBBO_CONFIG_MODE, "override"); + ApplicationModel.reset(); + ConfigManager configManager = ApplicationModel.defaultModel().getApplicationConfigManager(); + Assertions.assertEquals(ConfigMode.OVERRIDE, configManager.getConfigMode()); + } finally { + Locale.setDefault(defaultLocale); + System.clearProperty(DUBBO_CONFIG_MODE); + ApplicationModel.reset(); + } + } + @Test void testGetConfigByIdOrName() { RegistryConfig registryConfig = new RegistryConfig(); diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ServiceConfig.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ServiceConfig.java index 262d84e09154..b7eb0eb52311 100644 --- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ServiceConfig.java +++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ServiceConfig.java @@ -65,6 +65,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.TreeSet; @@ -1170,7 +1171,7 @@ private static Integer parsePort(String configPort) { } private static String getValueFromConfig(ProtocolConfig protocolConfig, String key) { - String protocolPrefix = protocolConfig.getName().toUpperCase() + "_"; + String protocolPrefix = protocolConfig.getName().toUpperCase(Locale.ROOT) + "_"; String value = ConfigUtils.getSystemProperty(protocolPrefix + key); if (StringUtils.isEmpty(value)) { value = ConfigUtils.getSystemProperty(key); @@ -1179,12 +1180,12 @@ private static String getValueFromConfig(ProtocolConfig protocolConfig, String k } private static Integer getRandomPort(String protocol) { - protocol = protocol.toLowerCase(); + protocol = protocol.toLowerCase(Locale.ROOT); return RANDOM_PORT_MAP.getOrDefault(protocol, Integer.MIN_VALUE); } private static void putRandomPort(String protocol, Integer port) { - protocol = protocol.toLowerCase(); + protocol = protocol.toLowerCase(Locale.ROOT); if (!RANDOM_PORT_MAP.containsKey(protocol)) { RANDOM_PORT_MAP.put(protocol, port); logger.warn( diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/ServiceConfigTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/ServiceConfigTest.java index a285c8c073f3..c081c5cfeb67 100644 --- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/ServiceConfigTest.java +++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/ServiceConfigTest.java @@ -37,7 +37,9 @@ import org.apache.dubbo.rpc.model.FrameworkModel; import org.apache.dubbo.rpc.service.GenericService; +import java.lang.reflect.Method; import java.util.Collections; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.CountDownLatch; @@ -62,6 +64,7 @@ import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER; import static org.apache.dubbo.common.constants.CommonConstants.SHUTDOWN_WAIT_KEY; import static org.apache.dubbo.common.constants.CommonConstants.SIDE_KEY; +import static org.apache.dubbo.config.Constants.DUBBO_PORT_TO_BIND; import static org.apache.dubbo.config.Constants.SHUTDOWN_TIMEOUT_KEY; import static org.apache.dubbo.remoting.Constants.BIND_IP_KEY; import static org.apache.dubbo.remoting.Constants.BIND_PORT_KEY; @@ -766,6 +769,22 @@ public Set removeCachedMapping(String serviceKey) { scheduledExecutorService.shutdown(); } + // In the Turkish locale "tri".toUpperCase() is "TRİ", so TRI_DUBBO_PORT_TO_BIND was never looked up. + @Test + void testGetValueFromConfigUnderTurkishLocale() throws Exception { + Method getValueFromConfig = + ServiceConfig.class.getDeclaredMethod("getValueFromConfig", ProtocolConfig.class, String.class); + getValueFromConfig.setAccessible(true); + Locale defaultLocale = Locale.getDefault(); + Locale.setDefault(Locale.forLanguageTag("tr-TR")); + try { + SysProps.setProperty("TRI_" + DUBBO_PORT_TO_BIND, "50051"); + assertEquals("50051", getValueFromConfig.invoke(null, new ProtocolConfig("tri"), DUBBO_PORT_TO_BIND)); + } finally { + Locale.setDefault(defaultLocale); + } + } + @Test void testToString() { ServiceConfig serviceConfig = new ServiceConfig<>(); diff --git a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/schema/DubboBeanDefinitionParser.java b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/schema/DubboBeanDefinitionParser.java index dbdfc369ab33..60dd385cb509 100644 --- a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/schema/DubboBeanDefinitionParser.java +++ b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/schema/DubboBeanDefinitionParser.java @@ -46,6 +46,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.regex.Pattern; @@ -330,7 +331,7 @@ private static void assignProperties(Object obj, Element ele, ParserContext pars Method[] methods = obj.getClass().getMethods(); for (Method method : methods) { if (MethodUtils.isSetter(method)) { - String beanProperty = method.getName().substring(3, 4).toLowerCase() + String beanProperty = method.getName().substring(3, 4).toLowerCase(Locale.ROOT) + method.getName().substring(4); String property = StringUtils.camelToSplitName(beanProperty, "-"); String value = resolveAttribute(ele, property, parserContext); @@ -395,7 +396,7 @@ private static void getPropertyMap(Class beanClass, Map beanPr && Modifier.isPublic(setter.getModifiers()) && setter.getParameterTypes().length == 1) { Class type = setter.getParameterTypes()[0]; - String beanProperty = name.substring(3, 4).toLowerCase() + name.substring(4); + String beanProperty = name.substring(3, 4).toLowerCase(Locale.ROOT) + name.substring(4); // check the setter/getter whether match Method getter = null; try { diff --git a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/schema/DubboBeanDefinitionParserTest.java b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/schema/DubboBeanDefinitionParserTest.java new file mode 100644 index 000000000000..022fe1521868 --- /dev/null +++ b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/schema/DubboBeanDefinitionParserTest.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.config.spring.schema; + +import org.apache.dubbo.config.ProtocolConfig; + +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class DubboBeanDefinitionParserTest { + + // In the Turkish locale "I".toLowerCase() is the dotless "ı", which turned the + // property behind setIothreads() into "ıothreads", so the iothreads attribute was ignored. + @Test + void testGetPropertyMapUnderTurkishLocale() throws Exception { + Method getPropertyMap = + DubboBeanDefinitionParser.class.getDeclaredMethod("getPropertyMap", Class.class, Map.class); + getPropertyMap.setAccessible(true); + Locale defaultLocale = Locale.getDefault(); + Locale.setDefault(Locale.forLanguageTag("tr-TR")); + try { + Map properties = new HashMap<>(); + getPropertyMap.invoke(null, ProtocolConfig.class, properties); + assertEquals( + Integer.class, + properties.get("iothreads"), + properties.keySet().toString()); + } finally { + Locale.setDefault(defaultLocale); + } + } +}