From 38a06755a1bc9155dd31ca6d9bf2759de4b60e90 Mon Sep 17 00:00:00 2001 From: emeroad Date: Tue, 20 Jan 2026 15:37:30 +0900 Subject: [PATCH] [#13319] KeyValueTokenizer --- .../jdbc/dameng/DamengJdbcUrlParser.java | 17 ++--- .../PluginPackageClassRequirementFilter.java | 9 +-- .../service/ChannelzSocketLookup.java | 10 +-- .../common/util/KeyValueTokenizerTest.java | 43 +++++++++++ .../common/util/KeyValueTokenizer.java | 71 +++++++++++++++++++ .../util/ExceptionTraceQueryParameter.java | 5 +- .../pinpoint/metric/common/util/TagUtils.java | 26 +++---- .../metric/common/util/TagUtilsTest.java | 8 +++ .../redis/kv/RedisKVPubChannelProvider.java | 11 +-- .../redis/kv/RedisKVSubChannelProvider.java | 11 +-- .../ServerMapHistogramController.java | 4 +- 11 files changed, 172 insertions(+), 43 deletions(-) create mode 100644 commons-server/src/test/java/com/navercorp/pinpoint/common/util/KeyValueTokenizerTest.java create mode 100644 commons/src/main/java/com/navercorp/pinpoint/common/util/KeyValueTokenizer.java diff --git a/agent-module/plugins/dameng-jdbc/src/main/java/com/navercorp/pinpoint/plugin/jdbc/dameng/DamengJdbcUrlParser.java b/agent-module/plugins/dameng-jdbc/src/main/java/com/navercorp/pinpoint/plugin/jdbc/dameng/DamengJdbcUrlParser.java index c5fcfc6d3f09..4202b1047445 100644 --- a/agent-module/plugins/dameng-jdbc/src/main/java/com/navercorp/pinpoint/plugin/jdbc/dameng/DamengJdbcUrlParser.java +++ b/agent-module/plugins/dameng-jdbc/src/main/java/com/navercorp/pinpoint/plugin/jdbc/dameng/DamengJdbcUrlParser.java @@ -24,6 +24,7 @@ import com.navercorp.pinpoint.bootstrap.plugin.jdbc.StringMaker; import com.navercorp.pinpoint.bootstrap.plugin.jdbc.UnKnownDatabaseInfo; import com.navercorp.pinpoint.common.trace.ServiceType; +import com.navercorp.pinpoint.common.util.KeyValueTokenizer; import com.navercorp.pinpoint.common.util.StringUtils; import java.util.Arrays; @@ -73,10 +74,10 @@ private DatabaseInfo parseURL(String url) { String host = DEFAULT_HOST; String port = DEFAULT_PORT; if (StringUtils.hasText(hostPort)) { - String[] hostPortInfo = hostPort.split(":", 2); - host = hostPortInfo[0]; - if (hostPortInfo.length > 1) { - port = hostPortInfo[1]; + KeyValueTokenizer.KeyValue hostPortInfo = KeyValueTokenizer.tokenize(hostPort, ":"); + host = hostPortInfo.getKey(); + if (!hostPortInfo.getValue().isEmpty()) { + port = hostPortInfo.getValue(); } } @@ -122,11 +123,11 @@ private Map parseQuery(String propString) { if (!StringUtils.hasText(v)) { continue; } - String[] kv = v.split("=", 2); - if (kv.length > 1) { - map.put(kv[0], kv[1]); + KeyValueTokenizer.KeyValue keyValue = KeyValueTokenizer.tokenize(v, "="); + if (keyValue != null) { + map.put(keyValue.getKey(), keyValue.getValue()); } else { - map.put(kv[0], StringUtils.EMPTY_STRING); + logger.info("Parse failed " + v) ; } } return map; diff --git a/agent-module/profiler/src/main/java/com/navercorp/pinpoint/profiler/plugin/PluginPackageClassRequirementFilter.java b/agent-module/profiler/src/main/java/com/navercorp/pinpoint/profiler/plugin/PluginPackageClassRequirementFilter.java index 4621222eb49c..9497322ac773 100644 --- a/agent-module/profiler/src/main/java/com/navercorp/pinpoint/profiler/plugin/PluginPackageClassRequirementFilter.java +++ b/agent-module/profiler/src/main/java/com/navercorp/pinpoint/profiler/plugin/PluginPackageClassRequirementFilter.java @@ -1,5 +1,6 @@ package com.navercorp.pinpoint.profiler.plugin; +import com.navercorp.pinpoint.common.util.KeyValueTokenizer; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -53,10 +54,10 @@ private boolean isLoadedClass(String classname, ClassLoader cl) { private void parseRequirementList(List packageRequirementList, List packageList, List requirementList) { for (String packageWithRequirement : packageRequirementList) { - String[] split = packageWithRequirement.split(":", 2); - if (split.length == 2) { - packageList.add(split[0]); - requirementList.add(split[1]); + KeyValueTokenizer.KeyValue keyValue = KeyValueTokenizer.tokenize(packageWithRequirement, ":"); + if (keyValue != null) { + packageList.add(keyValue.getKey()); + requirementList.add(keyValue.getValue()); } } } diff --git a/collector/src/main/java/com/navercorp/pinpoint/collector/grpc/channelz/service/ChannelzSocketLookup.java b/collector/src/main/java/com/navercorp/pinpoint/collector/grpc/channelz/service/ChannelzSocketLookup.java index c044bc37175d..ebb0976a6a48 100644 --- a/collector/src/main/java/com/navercorp/pinpoint/collector/grpc/channelz/service/ChannelzSocketLookup.java +++ b/collector/src/main/java/com/navercorp/pinpoint/collector/grpc/channelz/service/ChannelzSocketLookup.java @@ -16,6 +16,8 @@ package com.navercorp.pinpoint.collector.grpc.channelz.service; +import com.navercorp.pinpoint.common.util.KeyValueTokenizer; + import javax.annotation.Nullable; import java.util.Collection; @@ -48,16 +50,16 @@ private SocketEntry(String remoteAddr, Integer localPort, long socketId) { * @return entry of socket index */ public static SocketEntry compose(Object remote, Object local, long socketId) { - String remoteAddr = split(remote)[0]; - String localPort = split(local)[1]; + String remoteAddr = split(remote).getKey(); + String localPort = split(local).getValue(); return new SocketEntry(remoteAddr.substring(1), parse(localPort), socketId); } - private static String[] split(Object obj) { + private static KeyValueTokenizer.KeyValue split(Object obj) { if (obj == null) { return null; } - return obj.toString().split(":", 2); + return KeyValueTokenizer.tokenize(obj.toString(), ":"); } private static Integer parse(String str) { diff --git a/commons-server/src/test/java/com/navercorp/pinpoint/common/util/KeyValueTokenizerTest.java b/commons-server/src/test/java/com/navercorp/pinpoint/common/util/KeyValueTokenizerTest.java new file mode 100644 index 000000000000..c872660e5be1 --- /dev/null +++ b/commons-server/src/test/java/com/navercorp/pinpoint/common/util/KeyValueTokenizerTest.java @@ -0,0 +1,43 @@ +package com.navercorp.pinpoint.common.util; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class KeyValueTokenizerTest { + + @Test + void tokenize() { + KeyValueTokenizer.KeyValue keyValue = KeyValueTokenizer.tokenize("key=value", "="); + assertEquals("key", keyValue.getKey()); + assertEquals("value", keyValue.getValue()); + } + + @Test + void tokenize2() { + KeyValueTokenizer.KeyValue keyValue = KeyValueTokenizer.tokenize("key==value", "=="); + assertEquals("key", keyValue.getKey()); + assertEquals("value", keyValue.getValue()); + } + + @Test + void tokenize_emptyValue() { + KeyValueTokenizer.KeyValue keyValue = KeyValueTokenizer.tokenize("key=", "="); + assertEquals("key", keyValue.getKey()); + assertEquals("", keyValue.getValue()); + } + + @Test + void tokenize_emptyKeyValue() { + KeyValueTokenizer.KeyValue keyValue = KeyValueTokenizer.tokenize("=", "="); + assertEquals("", keyValue.getKey()); + assertEquals("", keyValue.getValue()); + } + + @Test + void tokenize_empty() { + KeyValueTokenizer.KeyValue keyValue = KeyValueTokenizer.tokenize("", "="); + Assertions.assertNull(keyValue); + } +} \ No newline at end of file diff --git a/commons/src/main/java/com/navercorp/pinpoint/common/util/KeyValueTokenizer.java b/commons/src/main/java/com/navercorp/pinpoint/common/util/KeyValueTokenizer.java new file mode 100644 index 000000000000..1d0733f482a0 --- /dev/null +++ b/commons/src/main/java/com/navercorp/pinpoint/common/util/KeyValueTokenizer.java @@ -0,0 +1,71 @@ +package com.navercorp.pinpoint.common.util; +import java.util.Objects; + +public class KeyValueTokenizer { + + public static final TokenFactory KEY_VALUE_FACTORY = new TokenFactory() { + public KeyValue accept(String key, String value) { + return new KeyValue(key, value); + } + }; + + public static final TokenFactory KEY_VALUE_TRIM_FACTORY = new TokenFactory() { + public KeyValue accept(String key, String value) { + return new KeyValue(key.trim(), value.trim()); + } + }; + + public static KeyValue tokenize(String text, String delimiter) { + return tokenize(text, delimiter, KEY_VALUE_FACTORY); + } + + /** + * Tokenizes the given text into a key and value using the specified delimiter and token factory. + * + * @param text the text to tokenize + * @param delimiter the delimiter separating key and value + * @param factory the factory used to create the token + * @param the type of token to return + * @return the parsed token, or {@code null} if the delimiter is not found in the text + */ + public static T tokenize(String text, String delimiter, TokenFactory factory) { + Objects.requireNonNull(text, "text"); + + final int delimiterIndex = text.indexOf(delimiter); + if (delimiterIndex == -1) { + return null; + } + + final String key = text.substring(0, delimiterIndex); + + final int delimiterLength = delimiter.length(); + if (delimiterIndex == text.length() - delimiterLength) { + return factory.accept(key, ""); + } + String value = text.substring(delimiterIndex + delimiterLength); + return factory.accept(key, value); + } + + + public interface TokenFactory { + V accept(String key, String value); + } + + public static class KeyValue { + private final String key; + private final String value; + + public KeyValue(String key, String value) { + this.key = Objects.requireNonNull(key, "key"); + this.value = Objects.requireNonNull(value, "value"); + } + + public String getKey() { + return key; + } + + public String getValue() { + return value; + } + } +} diff --git a/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/util/ExceptionTraceQueryParameter.java b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/util/ExceptionTraceQueryParameter.java index 1af2212612db..5b7995514077 100644 --- a/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/util/ExceptionTraceQueryParameter.java +++ b/exceptiontrace/exceptiontrace-web/src/main/java/com/navercorp/pinpoint/exceptiontrace/web/util/ExceptionTraceQueryParameter.java @@ -19,6 +19,7 @@ import com.google.common.primitives.Ints; import com.navercorp.pinpoint.common.server.util.StringPrecondition; import com.navercorp.pinpoint.common.timeseries.window.TimePrecision; +import com.navercorp.pinpoint.common.util.KeyValueTokenizer; import com.navercorp.pinpoint.common.util.StringUtils; import com.navercorp.pinpoint.metric.web.util.QueryParameter; @@ -197,8 +198,8 @@ public Builder addAllFilters(Collection strings) { return self(); } for (String string : strings) { - String[] tag = string.split(":", 2); - filterByAttributes.put(tag[0], tag[1]); + KeyValueTokenizer.KeyValue keyValue = KeyValueTokenizer.tokenize(string, ":"); + filterByAttributes.put(keyValue.getKey(), keyValue.getValue()); } return self(); } diff --git a/metric-module/metric-commons/src/main/java/com/navercorp/pinpoint/metric/common/util/TagUtils.java b/metric-module/metric-commons/src/main/java/com/navercorp/pinpoint/metric/common/util/TagUtils.java index 9a17fd363bd7..d2f594be1ca7 100644 --- a/metric-module/metric-commons/src/main/java/com/navercorp/pinpoint/metric/common/util/TagUtils.java +++ b/metric-module/metric-commons/src/main/java/com/navercorp/pinpoint/metric/common/util/TagUtils.java @@ -18,6 +18,7 @@ import com.navercorp.pinpoint.common.util.CollectionUtils; +import com.navercorp.pinpoint.common.util.KeyValueTokenizer; import com.navercorp.pinpoint.metric.common.model.Tag; import org.apache.commons.lang3.StringUtils; @@ -33,7 +34,14 @@ public class TagUtils { private static final Pattern MULTI_VALUE_FIELD_PATTERN = Pattern.compile("[\\[\\]\"]"); - private static final Pattern JSON_TAG_STRING_PATTERN = Pattern.compile("[{}\"]"); + private static final String JSON_TAG_STRING = "{}\""; + + public static final KeyValueTokenizer.TokenFactory TAG_FACTORY = new KeyValueTokenizer.TokenFactory<>() { + @Override + public Tag accept(String key, String value) { + return new Tag(key, value); + } + }; private TagUtils() { } @@ -69,16 +77,7 @@ public static List parseTags(String tagStrings) { public static Tag parseTag(String tagString) { Objects.requireNonNull(tagString, "tagString"); - - String[] tag = StringUtils.split(tagString, ":", 2); - - if (tag.length == 1) { - return new Tag(tag[0], ""); - } else if (tag.length == 2) { - return new Tag(tag[0], tag[1]); - } else { - throw new IllegalArgumentException("tagString:" + tagString); - } + return KeyValueTokenizer.tokenize(tagString, ":", TAG_FACTORY); } private static String[] parseMultiValueFieldList(String string) { @@ -87,10 +86,11 @@ private static String[] parseMultiValueFieldList(String string) { } public static String toTagString(String jsonTagString) { - if (jsonTagString.equals("{}")) { + if ("{}".equals(jsonTagString)) { return ""; } - return JSON_TAG_STRING_PATTERN.matcher(jsonTagString).replaceAll(""); + + return org.springframework.util.StringUtils.deleteAny(jsonTagString, JSON_TAG_STRING); } public static String toTagString(List tagList) { diff --git a/metric-module/metric-commons/src/test/java/com/navercorp/pinpoint/metric/common/util/TagUtilsTest.java b/metric-module/metric-commons/src/test/java/com/navercorp/pinpoint/metric/common/util/TagUtilsTest.java index cae33a51e53c..17045b5e4b32 100644 --- a/metric-module/metric-commons/src/test/java/com/navercorp/pinpoint/metric/common/util/TagUtilsTest.java +++ b/metric-module/metric-commons/src/test/java/com/navercorp/pinpoint/metric/common/util/TagUtilsTest.java @@ -41,6 +41,14 @@ public void parseTagTest() { Assertions.assertEquals(tagList, result); } + @Test + public void parseTagTest_emptyValue() { + Tag tag = TagUtils.parseTag("A:"); + + Assertions.assertEquals("A", tag.getName()); + Assertions.assertEquals("", tag.getValue()); + } + @Test public void parseTagsListTest() { List tagList = List.of( diff --git a/redis/src/main/java/com/navercorp/pinpoint/channel/redis/kv/RedisKVPubChannelProvider.java b/redis/src/main/java/com/navercorp/pinpoint/channel/redis/kv/RedisKVPubChannelProvider.java index 1afa36d4f186..f14bfea67f9f 100644 --- a/redis/src/main/java/com/navercorp/pinpoint/channel/redis/kv/RedisKVPubChannelProvider.java +++ b/redis/src/main/java/com/navercorp/pinpoint/channel/redis/kv/RedisKVPubChannelProvider.java @@ -17,6 +17,7 @@ import com.navercorp.pinpoint.channel.PubChannel; import com.navercorp.pinpoint.channel.PubChannelProvider; +import com.navercorp.pinpoint.common.util.KeyValueTokenizer; import org.springframework.data.redis.core.RedisTemplate; import java.time.Duration; @@ -35,12 +36,12 @@ class RedisKVPubChannelProvider implements PubChannelProvider { @Override public PubChannel getPubChannel(String key) { - String[] words = key.split(":", 2); - if (words.length != 2) { - throw new IllegalArgumentException("the key must contain expire duration"); + KeyValueTokenizer.KeyValue keyValue = KeyValueTokenizer.tokenize(key, ":"); + if (keyValue == null) { + throw new IllegalArgumentException("the key must contain ':' key:" + key); } - Duration expire = Duration.parse(words[0]); - return new RedisKVPubChannel(this.template, expire.toMillis(), words[1]); + Duration expire = Duration.parse(keyValue.getKey()); + return new RedisKVPubChannel(this.template, expire.toMillis(), keyValue.getValue()); } } diff --git a/redis/src/main/java/com/navercorp/pinpoint/channel/redis/kv/RedisKVSubChannelProvider.java b/redis/src/main/java/com/navercorp/pinpoint/channel/redis/kv/RedisKVSubChannelProvider.java index df2b121239d7..b5c01b031d32 100644 --- a/redis/src/main/java/com/navercorp/pinpoint/channel/redis/kv/RedisKVSubChannelProvider.java +++ b/redis/src/main/java/com/navercorp/pinpoint/channel/redis/kv/RedisKVSubChannelProvider.java @@ -17,6 +17,7 @@ import com.navercorp.pinpoint.channel.SubChannel; import com.navercorp.pinpoint.channel.SubChannelProvider; +import com.navercorp.pinpoint.common.util.KeyValueTokenizer; import org.springframework.data.redis.core.RedisTemplate; import reactor.core.scheduler.Scheduler; @@ -38,12 +39,12 @@ class RedisKVSubChannelProvider implements SubChannelProvider { @Override public SubChannel getSubChannel(String key) { - String[] words = key.split(":", 2); - if (words.length != 2) { - throw new IllegalArgumentException("the key must contain period"); + KeyValueTokenizer.KeyValue keyValue = KeyValueTokenizer.tokenize(key, ":"); + if (keyValue == null) { + throw new IllegalArgumentException("the key must contain ':' key:" + key); } - Duration period = Duration.parse(words[0]); - return new RedisKVSubChannel(this.template, this.scheduler, period, words[1]); + Duration period = Duration.parse(keyValue.getKey()); + return new RedisKVSubChannel(this.template, this.scheduler, period, keyValue.getValue()); } } diff --git a/web/src/main/java/com/navercorp/pinpoint/web/applicationmap/controller/ServerMapHistogramController.java b/web/src/main/java/com/navercorp/pinpoint/web/applicationmap/controller/ServerMapHistogramController.java index 4e60098dbad6..0c749a18818b 100644 --- a/web/src/main/java/com/navercorp/pinpoint/web/applicationmap/controller/ServerMapHistogramController.java +++ b/web/src/main/java/com/navercorp/pinpoint/web/applicationmap/controller/ServerMapHistogramController.java @@ -256,7 +256,7 @@ private Application newApplication(String nodeKey) { if (!NODE_KEY_VALIDATION_PATTERN.matcher(nodeKey).matches()) { throw new IllegalArgumentException("Invalid node key format: " + nodeKey); } - String[] parts = NODE_DELIMITER_PATTERN.split(nodeKey); + String[] parts = NODE_DELIMITER_PATTERN.split(nodeKey, 2); String applicationName = parts[0]; String serviceTypeName = parts[1]; @@ -345,7 +345,7 @@ public LinkHistogramSummaryView getLinkTimeHistogramData( if (!LINK_KEY_VALIDATION_PATTERN.matcher(linkKey).matches()) { throw new IllegalArgumentException("Invalid linkKey format: expected 'fromApp~toApp' but got: " + linkKey); } - String[] parts = LINK_DELIMITER_PATTERN.split(linkKey); + String[] parts = LINK_DELIMITER_PATTERN.split(linkKey, 2); if (parts.length != 2) { throw new IllegalArgumentException("Invalid linkKey format: expected 'fromApp~toApp' but got: " + linkKey); }