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.
- *
- * - The following characters are escaped by prefixing them with a '\' : \b,\f,\n,\r,\t,\,"
- * - Other control characters in the range 0x0000-0x001F are escaped using the \\uXXXX notation
- * - UTF-16 surrogate pairs are encoded using the \\uXXXX\\uXXXX notation
- * - any other character is printed as-is
- *
- */
- 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.
+ *
+ * - The following characters are escaped by prefixing them with a '\' : \b,\f,\n,\r,\t,\,"
+ * - Other control characters in the range 0x0000-0x001F are escaped using the \\uXXXX notation
+ * - UTF-16 surrogate pairs are encoded using the \\uXXXX\\uXXXX notation
+ * - any other character is printed as-is
+ *
+ */
+ 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);
+ }
+}