diff --git a/pom.xml b/pom.xml index a674b26..ade193e 100644 --- a/pom.xml +++ b/pom.xml @@ -75,6 +75,13 @@ test + + com.google.code.gson + gson + 2.8.9 + test + + diff --git a/src/main/java/io/growing/sdk/java/com/googlecode/protobuf/format/JsonFormat.java b/src/main/java/io/growing/sdk/java/com/googlecode/protobuf/format/JsonFormat.java index f570c21..59c97f5 100644 --- a/src/main/java/io/growing/sdk/java/com/googlecode/protobuf/format/JsonFormat.java +++ b/src/main/java/io/growing/sdk/java/com/googlecode/protobuf/format/JsonFormat.java @@ -38,8 +38,6 @@ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT import java.io.OutputStreamWriter; import java.math.BigInteger; import java.nio.charset.Charset; -import java.text.CharacterIterator; -import java.text.StringCharacterIterator; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -54,6 +52,7 @@ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT import com.google.protobuf.Descriptors.EnumValueDescriptor; import com.google.protobuf.Descriptors.FieldDescriptor; +import static io.growing.sdk.java.com.googlecode.protobuf.format.util.JsonUtils.*; import static io.growing.sdk.java.com.googlecode.protobuf.format.util.TextUtils.*; /** @@ -1297,76 +1296,7 @@ public InvalidEscapeSequence(String description) { } /** - * Implements JSON string escaping as specified here. - * - */ - static String escapeText(String input) { - StringBuilder builder = new StringBuilder(input.length()); - CharacterIterator iter = new StringCharacterIterator(input); - for (char c = iter.first(); c != CharacterIterator.DONE; c = iter.next()) { - switch (c) { - case '\b': - builder.append("\\b"); - break; - case '\f': - builder.append("\\f"); - break; - case '\n': - builder.append("\\n"); - break; - case '\r': - builder.append("\\r"); - break; - case '\t': - builder.append("\\t"); - break; - case '\\': - builder.append("\\\\"); - break; - case '"': - builder.append("\\\""); - break; - default: - // Check for other control characters - if (c >= 0x0000 && c <= 0x001F) { - appendEscapedUnicode(builder, c); - } else if (Character.isHighSurrogate(c)) { - // Encode the surrogate pair using 2 six-character sequence (\\uXXXX\\uXXXX) - appendEscapedUnicode(builder, c); - c = iter.next(); - if (c == CharacterIterator.DONE) { - throw new IllegalArgumentException("invalid unicode string: unexpected high surrogate pair value without corresponding low value."); - } - appendEscapedUnicode(builder, c); - } else { - // Anything else can be printed as-is - builder.append(c); - } - break; - } - } - return builder.toString(); - } - - static void appendEscapedUnicode(StringBuilder builder, char ch) { - String prefix = "\\u"; - if (ch < 0x10) { - prefix = "\\u000"; - } else if (ch < 0x100) { - prefix = "\\u00"; - } else if (ch < 0x1000) { - prefix = "\\u0"; - } - builder.append(prefix).append(Integer.toHexString(ch)); - } - - /** - * Un-escape a text string as escaped using {@link #escapeText(String)}. + * Un-escape a text string as escaped using {@link io.growing.sdk.java.com.googlecode.protobuf.format.util.JsonUtils#escapeText(String)}. */ static String unescapeText(String input) throws JsonFormat.InvalidEscapeSequence { StringBuilder builder = new StringBuilder(); diff --git a/src/main/java/io/growing/sdk/java/com/googlecode/protobuf/format/util/JsonUtils.java b/src/main/java/io/growing/sdk/java/com/googlecode/protobuf/format/util/JsonUtils.java new file mode 100644 index 0000000..2c1cd07 --- /dev/null +++ b/src/main/java/io/growing/sdk/java/com/googlecode/protobuf/format/util/JsonUtils.java @@ -0,0 +1,113 @@ +/* + Copyright (c) 2009, Orbitz World Wide + All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the Orbitz World Wide nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + The JsonUtils implementation is referenced from protobuf-java-format + https://code.google.com/archive/p/protobuf-java-format/ +*/ + +package io.growing.sdk.java.com.googlecode.protobuf.format.util; + +import java.text.CharacterIterator; +import java.text.StringCharacterIterator; + +public class JsonUtils { + // ================================================================= + // Utility functions + // + // Some of these methods are package-private because Descriptors.java uses + // them. + + /** + * Implements JSON string escaping as specified here. + * + */ + public static String escapeText(String input) { + StringBuilder builder = new StringBuilder(input.length()); + CharacterIterator iter = new StringCharacterIterator(input); + for (char c = iter.first(); c != CharacterIterator.DONE; c = iter.next()) { + switch (c) { + case '\b': + builder.append("\\b"); + break; + case '\f': + builder.append("\\f"); + break; + case '\n': + builder.append("\\n"); + break; + case '\r': + builder.append("\\r"); + break; + case '\t': + builder.append("\\t"); + break; + case '\\': + builder.append("\\\\"); + break; + case '"': + builder.append("\\\""); + break; + default: + // Check for other control characters + if (c >= 0x0000 && c <= 0x001F) { + appendEscapedUnicode(builder, c); + } else if (Character.isHighSurrogate(c)) { + // Encode the surrogate pair using 2 six-character sequence (\\uXXXX\\uXXXX) + appendEscapedUnicode(builder, c); + c = iter.next(); + if (c == CharacterIterator.DONE) { + throw new IllegalArgumentException("invalid unicode string: unexpected high surrogate pair value without corresponding low value."); + } + appendEscapedUnicode(builder, c); + } else { + // Anything else can be printed as-is + builder.append(c); + } + break; + } + } + return builder.toString(); + } + + static void appendEscapedUnicode(StringBuilder builder, char ch) { + String prefix = "\\u"; + if (ch < 0x10) { + prefix = "\\u000"; + } else if (ch < 0x100) { + prefix = "\\u00"; + } else if (ch < 0x1000) { + prefix = "\\u0"; + } + builder.append(prefix).append(Integer.toHexString(ch)); + } +} diff --git a/src/main/java/io/growing/sdk/java/dto/GioCdpItemMessage.java b/src/main/java/io/growing/sdk/java/dto/GioCdpItemMessage.java index 34ed6d5..e4d8247 100644 --- a/src/main/java/io/growing/sdk/java/dto/GioCdpItemMessage.java +++ b/src/main/java/io/growing/sdk/java/dto/GioCdpItemMessage.java @@ -6,6 +6,7 @@ import java.io.Serializable; import java.util.List; +import java.util.Map; /** * @author : tong.wang @@ -76,6 +77,14 @@ public Builder addItemVariable(String key, List value) { return this; } + public Builder addItemVariable(String key, Map value) { + if (key != null && value != null && !value.isEmpty()) { + builder.putAttributes(key, StringUtils.map2Str(value)); + } + + return this; + } + public Builder addItemVariable(String key, String value) { if (key != null && value != null) { builder.putAttributes(key, value); diff --git a/src/main/java/io/growing/sdk/java/dto/GioCdpUserMessage.java b/src/main/java/io/growing/sdk/java/dto/GioCdpUserMessage.java index 08b2808..37b5b37 100644 --- a/src/main/java/io/growing/sdk/java/dto/GioCdpUserMessage.java +++ b/src/main/java/io/growing/sdk/java/dto/GioCdpUserMessage.java @@ -114,6 +114,14 @@ public Builder addUserVariable(String key, List value) { return this; } + public Builder addUserVariable(String key, Map value) { + if (key != null && value != null && !value.isEmpty()) { + builder.putAttributes(key, StringUtils.map2Str(value)); + } + + return this; + } + private Builder addVariableObject(String key, Object value) { if (key != null && value != null) { key = key.trim(); diff --git a/src/main/java/io/growing/sdk/java/utils/StringUtils.java b/src/main/java/io/growing/sdk/java/utils/StringUtils.java index b5a07bb..9b9ca84 100644 --- a/src/main/java/io/growing/sdk/java/utils/StringUtils.java +++ b/src/main/java/io/growing/sdk/java/utils/StringUtils.java @@ -1,7 +1,10 @@ package io.growing.sdk.java.utils; +import io.growing.sdk.java.com.googlecode.protobuf.format.util.JsonUtils; + import java.util.Iterator; import java.util.List; +import java.util.Map; /** * @author : tong.wang @@ -18,6 +21,41 @@ public static boolean isBlank(String value) { return value == null || value.isEmpty(); } + public static String map2Str(Map map) { + try { + if (map != null && !map.isEmpty()) { + boolean printedComma = false; + StringBuilder builder = new StringBuilder("{"); + for (Map.Entry entry : map.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + + if (key != null && value != null) { + if (printedComma) { + builder.append(","); + } else { + printedComma = true; + } + printField(builder, key); + builder.append(":"); + printField(builder, toString(value)); + } + } + builder.append("}"); + return builder.toString(); + } + } catch (Exception ignored) { + } + + return "{}"; + } + + private static void printField(StringBuilder builder, String value) { + builder.append("\""); + builder.append(JsonUtils.escapeText(value)); + builder.append("\""); + } + public static String list2Str(List value) { if (value != null && !value.isEmpty()) { final String listSplit = "||"; diff --git a/src/main/java/io/growing/sdk/java/utils/VersionInfo.java b/src/main/java/io/growing/sdk/java/utils/VersionInfo.java index 2f59655..b739fc7 100644 --- a/src/main/java/io/growing/sdk/java/utils/VersionInfo.java +++ b/src/main/java/io/growing/sdk/java/utils/VersionInfo.java @@ -8,7 +8,7 @@ public class VersionInfo { private static String version = null; - private static final String defaultVersion = "1.0.13-cdp"; + private static final String defaultVersion = "1.0.15-cdp"; public static String getVersion() { if (version == null) { diff --git a/src/test/java/io/growing/sdk/java/test/Case0PropertiesTest.java b/src/test/java/io/growing/sdk/java/test/Case0PropertiesTest.java index 4123aaf..d0d14f1 100644 --- a/src/test/java/io/growing/sdk/java/test/Case0PropertiesTest.java +++ b/src/test/java/io/growing/sdk/java/test/Case0PropertiesTest.java @@ -48,15 +48,15 @@ public void checkProperties() { sender = new GrowingAPI.Builder().setDataSourceId(DATASOURCE_ID).setProjectKey(PROJECT_KEY).build(); // check properties - Assert.assertEquals(ConfigUtils.getStringValue("api.host", ""), "http://localhost:8080"); - Assert.assertEquals(ConfigUtils.getStringValue("project.id", ""), "123456654321"); - Assert.assertEquals(StoreStrategyClient.CURRENT_STRATEGY.name(), "ABORT_POLICY"); + Assert.assertEquals("https://www.growingio.com", ConfigUtils.getStringValue("api.host", "")); + Assert.assertEquals("123456654321", ConfigUtils.getStringValue("project.id", "")); + Assert.assertEquals("ABORT_POLICY", StoreStrategyClient.CURRENT_STRATEGY.name()); Assert.assertTrue(RunMode.isProductionMode()); - Assert.assertEquals(getStaticField(GioLogger.class, "loggerLevel"), "error"); + Assert.assertEquals("error", getStaticField(GioLogger.class, "loggerLevel")); - Assert.assertEquals(getStaticField(AbortPolicyStoreStrategy.class, "THREADS"), Integer.parseInt(magicNumber)); - Assert.assertEquals(getStaticField(AbortPolicyStoreStrategy.class, "SEND_INTERVAL"), Integer.parseInt(magicNumber + 1)); - Assert.assertEquals(getStaticField(AbortPolicyStoreStrategy.class, "LIMIT"), Integer.parseInt(magicNumber + 2)); + Assert.assertEquals(Integer.parseInt(magicNumber), getStaticField(AbortPolicyStoreStrategy.class, "THREADS")); + Assert.assertEquals(Integer.parseInt(magicNumber + 1), getStaticField(AbortPolicyStoreStrategy.class, "SEND_INTERVAL")); + Assert.assertEquals(Integer.parseInt(magicNumber + 2), getStaticField(AbortPolicyStoreStrategy.class, "LIMIT")); } private static void setStaticField(Class clazz, String fieldName, Object value) { diff --git a/src/test/java/io/growing/sdk/java/test/Case1MockHttpTest.java b/src/test/java/io/growing/sdk/java/test/Case1MockHttpTest.java index d83da9d..75a2a1f 100644 --- a/src/test/java/io/growing/sdk/java/test/Case1MockHttpTest.java +++ b/src/test/java/io/growing/sdk/java/test/Case1MockHttpTest.java @@ -1,5 +1,6 @@ package io.growing.sdk.java.test; +import com.google.gson.Gson; import io.growing.collector.tunnel.protocol.EventV3Dto; import io.growing.collector.tunnel.protocol.EventV3List; import io.growing.collector.tunnel.protocol.ItemDto; @@ -160,6 +161,14 @@ public void onSend(URL url, byte[] msg) { public void sendUserEvent() throws InterruptedException { final CountDownLatch countDownLatch = new CountDownLatch(1); + final HashMap map = new HashMap(); + map.put("key1", "value1"); + map.put("a\\b", "b\\c"); + map.put("\b", "\t"); + map.put("", ""); + map.put(null, null); + map.put("中文key", "中文value"); + factory.setStubHttpURLConnectionListener(new StubStreamHandlerFactory.StubHttpURLConnectionListener() { @Override public void onSend(URL url, byte[] msg) { @@ -181,6 +190,9 @@ public void onSend(URL url, byte[] msg) { Assert.assertEquals("中文||English||にほんご", attributes.get("列表属性中文")); Assert.assertEquals(101, attributes.get("list_attribute_length").split("\\|\\|").length); + String jsonValue = new Gson().toJson(map); + Assert.assertEquals(jsonValue, attributes.get("map_attribute")); + } catch (Exception e) { mException = e; } @@ -199,6 +211,7 @@ public void onSend(URL url, byte[] msg) { .addUserVariable("", Arrays.asList("")) .addUserVariable("列表属性中文", Arrays.asList("中文", "English", "にほんご")) .addUserVariable("list_attribute_length", makeSequence(0, 100)) + .addUserVariable("map_attribute", map) .build()); countDownLatch.await(); @@ -208,6 +221,14 @@ public void onSend(URL url, byte[] msg) { public void sendItemEvent() throws InterruptedException { final CountDownLatch countDownLatch = new CountDownLatch(1); + final HashMap map = new HashMap(); + map.put("key1", "value1"); + map.put("a\\b", "b\\c"); + map.put("\b", "\t"); + map.put("", ""); + map.put(null, null); + map.put("中文key", "中文value"); + factory.setStubHttpURLConnectionListener(new StubStreamHandlerFactory.StubHttpURLConnectionListener() { @Override public void onSend(URL url, byte[] msg) { @@ -230,6 +251,9 @@ public void onSend(URL url, byte[] msg) { Assert.assertEquals("中文||English||にほんご", attributes.get("列表属性中文")); Assert.assertEquals(101, attributes.get("list_attribute_length").split("\\|\\|").length); + String jsonValue = new Gson().toJson(map); + Assert.assertEquals(jsonValue, attributes.get("map_attribute")); + } catch (Exception e) { mException = e; } @@ -249,6 +273,7 @@ public void onSend(URL url, byte[] msg) { .addItemVariable("", Arrays.asList("")) .addItemVariable("列表属性中文", Arrays.asList("中文", "English", "にほんご")) .addItemVariable("list_attribute_length", makeSequence(0, 100)) + .addItemVariable("map_attribute", map) .build()); countDownLatch.await(); diff --git a/src/test/java/io/growing/sdk/java/test/Case4StringUtilsTest.java b/src/test/java/io/growing/sdk/java/test/Case4StringUtilsTest.java new file mode 100644 index 0000000..63a2a61 --- /dev/null +++ b/src/test/java/io/growing/sdk/java/test/Case4StringUtilsTest.java @@ -0,0 +1,103 @@ +package io.growing.sdk.java.test; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import io.growing.sdk.java.utils.StringUtils; +import org.junit.Assert; +import org.junit.Test; + +import java.util.HashMap; + +public class Case4StringUtilsTest { + + @Test + public void checkMap2Str() { + Gson gson = new Gson(); + HashMap map = new HashMap(); + map.put("key1", "value1"); + map.put("key2", "value2"); + map.put("key3", "value3"); + map.put("中文key", "中文value"); + String gsonString = gson.toJson(map); + String utilString = StringUtils.map2Str(map); + + Assert.assertEquals(gsonString, utilString); + } + + @Test + public void parseMap2Str() { + Gson gson = new Gson(); + HashMap map = new HashMap(); + map.put("key1", "value1"); + map.put("key2", "value2"); + map.put("key3", "value3"); + map.put("中文key", "中文value"); + String utilString = StringUtils.map2Str(map); + JsonElement jsonElement = JsonParser.parseString(utilString); + Assert.assertTrue(jsonElement.isJsonObject()); + JsonObject jsonObject = jsonElement.getAsJsonObject(); + Assert.assertEquals(jsonObject.get("key1").getAsString(), "value1"); + Assert.assertEquals(jsonObject.get("key2").getAsString(), "value2"); + Assert.assertEquals(jsonObject.get("key3").getAsString(), "value3"); + Assert.assertEquals(jsonObject.get("中文key").getAsString(), "中文value"); + } + + @Test + public void checkMap2StrWithEscapeText() { + Gson gson = new Gson(); + HashMap map = new HashMap(); + map.put("\b", "a\\b"); + String gsonString = gson.toJson(map); + String utilString = StringUtils.map2Str(map); + + Assert.assertEquals(gsonString, utilString); + } + + @Test + public void parseMap2StrWithEscapeText() { + HashMap map = new HashMap(); + map.put("key", "a\\b"); + String utilString = StringUtils.map2Str(map); + JsonElement jsonElement = JsonParser.parseString(utilString); + Assert.assertTrue(jsonElement.isJsonObject()); + String value = jsonElement.getAsJsonObject().get("key").getAsString(); + Assert.assertEquals("a\\b", value); + } + + @Test + public void checkEmpty() { + Gson gson = new Gson(); + HashMap map = new HashMap(); + String gsonString = gson.toJson(map); + String utilString = StringUtils.map2Str(map); + + Assert.assertEquals(gsonString, utilString); + + map.put(null, null); + gsonString = gson.toJson(map); + utilString = StringUtils.map2Str(map); + + Assert.assertEquals(gsonString, utilString); + + map.put("key", null); + gsonString = gson.toJson(map); + utilString = StringUtils.map2Str(map); + + Assert.assertEquals(gsonString, utilString); + + map.put("key", ""); + gsonString = gson.toJson(map); + utilString = StringUtils.map2Str(map); + + Assert.assertEquals(gsonString, utilString); + + // 与gson保持一致,将空字符串的key解析为有效值 + map.put("", "value"); + gsonString = gson.toJson(map); + utilString = StringUtils.map2Str(map); + + Assert.assertEquals(gsonString, utilString); + } +}