From 4a69dcdc0680af9f2069460ffa0a1e34771d2566 Mon Sep 17 00:00:00 2001 From: zashraf1985 Date: Thu, 18 Aug 2022 13:57:51 -0700 Subject: [PATCH 1/8] Added serialization support for odp events --- .../java/com/optimizely/ab/odp/ODPEvent.java | 49 +++++++++++++++ .../ab/odp/serializer/ODPJsonSerializer.java | 24 ++++++++ .../serializer/ODPJsonSerializerFactory.java | 49 +++++++++++++++ .../odp/serializer/impl/GsonSerializer.java | 31 ++++++++++ .../serializer/impl/JacksonSerializer.java | 36 +++++++++++ .../odp/serializer/impl/JsonSerializer.java | 29 +++++++++ .../serializer/impl/JsonSimpleSerializer.java | 55 +++++++++++++++++ .../odp/serializer/ODPJsonSerializerTest.java | 60 +++++++++++++++++++ 8 files changed, 333 insertions(+) create mode 100644 core-api/src/main/java/com/optimizely/ab/odp/ODPEvent.java create mode 100644 core-api/src/main/java/com/optimizely/ab/odp/serializer/ODPJsonSerializer.java create mode 100644 core-api/src/main/java/com/optimizely/ab/odp/serializer/ODPJsonSerializerFactory.java create mode 100644 core-api/src/main/java/com/optimizely/ab/odp/serializer/impl/GsonSerializer.java create mode 100644 core-api/src/main/java/com/optimizely/ab/odp/serializer/impl/JacksonSerializer.java create mode 100644 core-api/src/main/java/com/optimizely/ab/odp/serializer/impl/JsonSerializer.java create mode 100644 core-api/src/main/java/com/optimizely/ab/odp/serializer/impl/JsonSimpleSerializer.java create mode 100644 core-api/src/test/java/com/optimizely/ab/odp/serializer/ODPJsonSerializerTest.java diff --git a/core-api/src/main/java/com/optimizely/ab/odp/ODPEvent.java b/core-api/src/main/java/com/optimizely/ab/odp/ODPEvent.java new file mode 100644 index 000000000..7e281340c --- /dev/null +++ b/core-api/src/main/java/com/optimizely/ab/odp/ODPEvent.java @@ -0,0 +1,49 @@ +package com.optimizely.ab.odp; + +import java.util.Map; + +public class ODPEvent { + private String type; + private String action; + private Map identifiers; + private Map data; + + public ODPEvent(String type, String action, Map identifiers, Map data) { + this.type = type; + this.action = action; + this.identifiers = identifiers; + this.data = data; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public Map getIdentifiers() { + return identifiers; + } + + public void setIdentifiers(Map identifiers) { + this.identifiers = identifiers; + } + + public Map getData() { + return data; + } + + public void setData(Map data) { + this.data = data; + } +} diff --git a/core-api/src/main/java/com/optimizely/ab/odp/serializer/ODPJsonSerializer.java b/core-api/src/main/java/com/optimizely/ab/odp/serializer/ODPJsonSerializer.java new file mode 100644 index 000000000..4f3922340 --- /dev/null +++ b/core-api/src/main/java/com/optimizely/ab/odp/serializer/ODPJsonSerializer.java @@ -0,0 +1,24 @@ +/** + * Copyright 2022, Optimizely Inc. and contributors + * + * Licensed 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 com.optimizely.ab.odp.serializer; + +import com.optimizely.ab.odp.ODPEvent; + +import java.util.List; + +public interface ODPJsonSerializer { + public String serializeEvents(List events); +} diff --git a/core-api/src/main/java/com/optimizely/ab/odp/serializer/ODPJsonSerializerFactory.java b/core-api/src/main/java/com/optimizely/ab/odp/serializer/ODPJsonSerializerFactory.java new file mode 100644 index 000000000..ca47e3bf4 --- /dev/null +++ b/core-api/src/main/java/com/optimizely/ab/odp/serializer/ODPJsonSerializerFactory.java @@ -0,0 +1,49 @@ +/** + * Copyright 2022, Optimizely Inc. and contributors + * + * Licensed 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 com.optimizely.ab.odp.serializer; + +import com.optimizely.ab.internal.JsonParserProvider; +import com.optimizely.ab.odp.serializer.impl.GsonSerializer; +import com.optimizely.ab.odp.serializer.impl.JacksonSerializer; +import com.optimizely.ab.odp.serializer.impl.JsonSerializer; +import com.optimizely.ab.odp.serializer.impl.JsonSimpleSerializer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ODPJsonSerializerFactory { + private static final Logger logger = LoggerFactory.getLogger(ODPJsonSerializerFactory.class); + + public static ODPJsonSerializer getSerializer() { + JsonParserProvider parserProvider = JsonParserProvider.getDefaultParser(); + ODPJsonSerializer jsonSerializer = null; + switch (parserProvider) { + case GSON_CONFIG_PARSER: + jsonSerializer = new GsonSerializer(); + break; + case JACKSON_CONFIG_PARSER: + jsonSerializer = new JacksonSerializer(); + break; + case JSON_CONFIG_PARSER: + jsonSerializer = new JsonSerializer(); + break; + case JSON_SIMPLE_CONFIG_PARSER: + jsonSerializer = new JsonSimpleSerializer(); + break; + } + logger.info("Using " + parserProvider.toString() + " serializer"); + return jsonSerializer; + } +} diff --git a/core-api/src/main/java/com/optimizely/ab/odp/serializer/impl/GsonSerializer.java b/core-api/src/main/java/com/optimizely/ab/odp/serializer/impl/GsonSerializer.java new file mode 100644 index 000000000..b4ddb4a87 --- /dev/null +++ b/core-api/src/main/java/com/optimizely/ab/odp/serializer/impl/GsonSerializer.java @@ -0,0 +1,31 @@ +/** + * Copyright 2022, Optimizely Inc. and contributors + * + * Licensed 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 com.optimizely.ab.odp.serializer.impl; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.optimizely.ab.odp.ODPEvent; +import com.optimizely.ab.odp.serializer.ODPJsonSerializer; + +import java.util.List; + +public class GsonSerializer implements ODPJsonSerializer { + @Override + public String serializeEvents(List events) { + Gson gson = new GsonBuilder().create(); + return gson.toJson(events); + } +} diff --git a/core-api/src/main/java/com/optimizely/ab/odp/serializer/impl/JacksonSerializer.java b/core-api/src/main/java/com/optimizely/ab/odp/serializer/impl/JacksonSerializer.java new file mode 100644 index 000000000..80cffa7d0 --- /dev/null +++ b/core-api/src/main/java/com/optimizely/ab/odp/serializer/impl/JacksonSerializer.java @@ -0,0 +1,36 @@ +/** + * Copyright 2022, Optimizely Inc. and contributors + * + * Licensed 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 com.optimizely.ab.odp.serializer.impl; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.optimizely.ab.odp.ODPEvent; +import com.optimizely.ab.odp.serializer.ODPJsonSerializer; + +import java.util.List; + +public class JacksonSerializer implements ODPJsonSerializer { + @Override + public String serializeEvents(List events) { + ObjectMapper objectMapper = new ObjectMapper(); + try { + return objectMapper.writeValueAsString(events); + } catch (JsonProcessingException e) { + // log error here + } + return null; + } +} diff --git a/core-api/src/main/java/com/optimizely/ab/odp/serializer/impl/JsonSerializer.java b/core-api/src/main/java/com/optimizely/ab/odp/serializer/impl/JsonSerializer.java new file mode 100644 index 000000000..69a16c18f --- /dev/null +++ b/core-api/src/main/java/com/optimizely/ab/odp/serializer/impl/JsonSerializer.java @@ -0,0 +1,29 @@ +/** + * Copyright 2022, Optimizely Inc. and contributors + * + * Licensed 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 com.optimizely.ab.odp.serializer.impl; + +import com.optimizely.ab.odp.ODPEvent; +import com.optimizely.ab.odp.serializer.ODPJsonSerializer; +import org.json.JSONArray; + +import java.util.List; + +public class JsonSerializer implements ODPJsonSerializer { + @Override + public String serializeEvents(List events) { + return new JSONArray(events).toString(); + } +} diff --git a/core-api/src/main/java/com/optimizely/ab/odp/serializer/impl/JsonSimpleSerializer.java b/core-api/src/main/java/com/optimizely/ab/odp/serializer/impl/JsonSimpleSerializer.java new file mode 100644 index 000000000..72978b455 --- /dev/null +++ b/core-api/src/main/java/com/optimizely/ab/odp/serializer/impl/JsonSimpleSerializer.java @@ -0,0 +1,55 @@ +/** + * Copyright 2022, Optimizely Inc. and contributors + * + * Licensed 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 com.optimizely.ab.odp.serializer.impl; + +import com.optimizely.ab.odp.ODPEvent; +import com.optimizely.ab.odp.serializer.ODPJsonSerializer; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; + +import java.util.List; +import java.util.Map; + +public class JsonSimpleSerializer implements ODPJsonSerializer { + @Override + public String serializeEvents(List events) { + JSONArray jsonArray = new JSONArray(); + for (ODPEvent event: events) { + JSONObject eventObject = new JSONObject(); + eventObject.put("type", event.getType()); + eventObject.put("action", event.getAction()); + + if (event.getIdentifiers() != null) { + JSONObject identifiers = new JSONObject(); + for (Map.Entry identifier : event.getIdentifiers().entrySet()) { + identifiers.put(identifier.getKey(), identifier.getValue()); + } + eventObject.put("identifiers", identifiers); + } + + if (event.getData() != null) { + JSONObject data = new JSONObject(); + for (Map.Entry dataEntry : event.getData().entrySet()) { + data.put(dataEntry.getKey(), dataEntry.getValue()); + } + eventObject.put("data", data); + } + + jsonArray.add(eventObject); + } + return jsonArray.toJSONString(); + } +} diff --git a/core-api/src/test/java/com/optimizely/ab/odp/serializer/ODPJsonSerializerTest.java b/core-api/src/test/java/com/optimizely/ab/odp/serializer/ODPJsonSerializerTest.java new file mode 100644 index 000000000..2944df022 --- /dev/null +++ b/core-api/src/test/java/com/optimizely/ab/odp/serializer/ODPJsonSerializerTest.java @@ -0,0 +1,60 @@ +package com.optimizely.ab.odp.serializer; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.optimizely.ab.odp.ODPEvent; +import com.optimizely.ab.odp.serializer.impl.*; +import org.json.JSONArray; +import org.json.JSONObject; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.*; + +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertTrue; + +@RunWith(Parameterized.class) +public class ODPJsonSerializerTest { + private final ODPJsonSerializer jsonSerializer; + + public ODPJsonSerializerTest(ODPJsonSerializer jsonSerializer) { + super(); + this.jsonSerializer = jsonSerializer; + } + + @Parameterized.Parameters + public static List input() { + return Arrays.asList(new GsonSerializer(), new JsonSerializer(), new JsonSimpleSerializer(), new JacksonSerializer()); + } + + @Test + public void serializeValidEvents() throws JsonProcessingException { + List events = Arrays.asList( + createTestEvent("1"), + createTestEvent("2"), + createTestEvent("3") + ); + + ObjectMapper mapper = new ObjectMapper(); + + String expectedResult = "[{\"type\":\"type-1\",\"action\":\"action-1\",\"identifiers\":{\"vuid-1-3\":\"fs-1-3\",\"vuid-1-1\":\"fs-1-1\",\"vuid-1-2\":\"fs-1-2\"},\"data\":{\"source\":\"java-sdk\",\"data-1\":\"data-value-1\"}},{\"type\":\"type-2\",\"action\":\"action-2\",\"identifiers\":{\"vuid-2-3\":\"fs-2-3\",\"vuid-2-2\":\"fs-2-2\",\"vuid-2-1\":\"fs-2-1\"},\"data\":{\"source\":\"java-sdk\",\"data-1\":\"data-value-2\"}},{\"type\":\"type-3\",\"action\":\"action-3\",\"identifiers\":{\"vuid-3-3\":\"fs-3-3\",\"vuid-3-2\":\"fs-3-2\",\"vuid-3-1\":\"fs-3-1\"},\"data\":{\"source\":\"java-sdk\",\"data-1\":\"data-value-3\"}}]"; + String serializedString = jsonSerializer.serializeEvents(events); + System.out.println(serializedString); + assertEquals(mapper.readTree(expectedResult), mapper.readTree(serializedString)); + } + + private static ODPEvent createTestEvent(String index) { + Map identifiers = new HashMap<>(); + identifiers.put("vuid-" + index + "-1", "fs-" + index + "-1"); + identifiers.put("vuid-" + index + "-2", "fs-" + index + "-2"); + identifiers.put("vuid-" + index + "-3", "fs-" + index + "-3"); + + Map data = new HashMap<>(); + data.put("source", "java-sdk"); + data.put("data-1", "data-value-" + index); + + return new ODPEvent("type-" + index, "action-" + index, identifiers, data); + } +} \ No newline at end of file From eedd9736461bd0b553cb644d8394c3f4d339c928 Mon Sep 17 00:00:00 2001 From: zashraf1985 Date: Thu, 18 Aug 2022 14:09:09 -0700 Subject: [PATCH 2/8] Added missing headers --- .../java/com/optimizely/ab/odp/ODPEvent.java | 15 +++++++++++++++ .../odp/serializer/ODPJsonSerializerTest.java | 17 +++++++++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/core-api/src/main/java/com/optimizely/ab/odp/ODPEvent.java b/core-api/src/main/java/com/optimizely/ab/odp/ODPEvent.java index 7e281340c..a93ed35b1 100644 --- a/core-api/src/main/java/com/optimizely/ab/odp/ODPEvent.java +++ b/core-api/src/main/java/com/optimizely/ab/odp/ODPEvent.java @@ -1,3 +1,18 @@ +/** + * Copyright 2022, Optimizely Inc. and contributors + * + * Licensed 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 com.optimizely.ab.odp; import java.util.Map; diff --git a/core-api/src/test/java/com/optimizely/ab/odp/serializer/ODPJsonSerializerTest.java b/core-api/src/test/java/com/optimizely/ab/odp/serializer/ODPJsonSerializerTest.java index 2944df022..c8d274863 100644 --- a/core-api/src/test/java/com/optimizely/ab/odp/serializer/ODPJsonSerializerTest.java +++ b/core-api/src/test/java/com/optimizely/ab/odp/serializer/ODPJsonSerializerTest.java @@ -1,11 +1,24 @@ +/** + * Copyright 2022, Optimizely Inc. and contributors + * + * Licensed 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 com.optimizely.ab.odp.serializer; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.optimizely.ab.odp.ODPEvent; import com.optimizely.ab.odp.serializer.impl.*; -import org.json.JSONArray; -import org.json.JSONObject; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; From 4e2745b5a1710288dbc9f25fdce099316656c79d Mon Sep 17 00:00:00 2001 From: zashraf1985 Date: Thu, 18 Aug 2022 16:02:44 -0700 Subject: [PATCH 3/8] Implemented sendEvents method. --- .../com/optimizely/ab/odp/ODPApiManager.java | 2 + .../odp/serializer/ODPJsonSerializerTest.java | 2 +- .../ab/odp/DefaultODPApiManager.java | 39 +++++++++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/core-api/src/main/java/com/optimizely/ab/odp/ODPApiManager.java b/core-api/src/main/java/com/optimizely/ab/odp/ODPApiManager.java index ed40e37fd..fae9fd958 100644 --- a/core-api/src/main/java/com/optimizely/ab/odp/ODPApiManager.java +++ b/core-api/src/main/java/com/optimizely/ab/odp/ODPApiManager.java @@ -19,4 +19,6 @@ public interface ODPApiManager { String fetchQualifiedSegments(String apiKey, String apiEndpoint, String userKey, String userValue, List segmentsToCheck); + + void sendEvents(String apiKey, String apiEndpoint, List events); } diff --git a/core-api/src/test/java/com/optimizely/ab/odp/serializer/ODPJsonSerializerTest.java b/core-api/src/test/java/com/optimizely/ab/odp/serializer/ODPJsonSerializerTest.java index c8d274863..9df4ab912 100644 --- a/core-api/src/test/java/com/optimizely/ab/odp/serializer/ODPJsonSerializerTest.java +++ b/core-api/src/test/java/com/optimizely/ab/odp/serializer/ODPJsonSerializerTest.java @@ -70,4 +70,4 @@ private static ODPEvent createTestEvent(String index) { return new ODPEvent("type-" + index, "action-" + index, identifiers, data); } -} \ No newline at end of file +} diff --git a/core-httpclient-impl/src/main/java/com/optimizely/ab/odp/DefaultODPApiManager.java b/core-httpclient-impl/src/main/java/com/optimizely/ab/odp/DefaultODPApiManager.java index f0703dcb0..210c06e04 100644 --- a/core-httpclient-impl/src/main/java/com/optimizely/ab/odp/DefaultODPApiManager.java +++ b/core-httpclient-impl/src/main/java/com/optimizely/ab/odp/DefaultODPApiManager.java @@ -17,6 +17,8 @@ import com.optimizely.ab.OptimizelyHttpClient; import com.optimizely.ab.annotations.VisibleForTesting; +import com.optimizely.ab.odp.serializer.ODPJsonSerializer; +import com.optimizely.ab.odp.serializer.ODPJsonSerializerFactory; import org.apache.http.HttpStatus; import org.apache.http.StatusLine; import org.apache.http.client.methods.CloseableHttpResponse; @@ -167,6 +169,43 @@ public String fetchQualifiedSegments(String apiKey, String apiEndpoint, String u return null; } + @Override + public void sendEvents(String apiKey, String apiEndpoint, List events) { + HttpPost request = new HttpPost(apiEndpoint); + String requestPayload = ODPJsonSerializerFactory.getSerializer().serializeEvents(events); + + if (requestPayload == null || requestPayload.isEmpty()) { + logger.error("ODP event send failed (Failed to serialize event payload)"); + return; + } + + try { + request.setEntity(new StringEntity(requestPayload)); + } catch (UnsupportedEncodingException e) { + logger.error("ODP event send failed (Error encoding request payload)", e); + return; + } + request.setHeader("x-api-key", apiKey); + request.setHeader("content-type", "application/json"); + + CloseableHttpResponse response = null; + try { + response = httpClient.execute(request); + } catch (IOException e) { + logger.error("Error retrieving response from event request", e); + return; + } + + if (response.getStatusLine().getStatusCode() >= 400) { + StatusLine statusLine = response.getStatusLine(); + logger.error(String.format("ODP event send failed (Response code: %d, %s)", statusLine.getStatusCode(), statusLine.getReasonPhrase())); + } else { + logger.debug("ODP Event Dispatched successfully"); + } + + closeHttpResponse(response); + } + private static void closeHttpResponse(CloseableHttpResponse response) { if (response != null) { try { From a8f11a788925b1ccbc70acad1d496d305e6bad4e Mon Sep 17 00:00:00 2001 From: zashraf1985 Date: Thu, 18 Aug 2022 17:03:02 -0700 Subject: [PATCH 4/8] did some refactoring and added unit tests. --- .../parser/ResponseJsonParserFactoryTest.java | 1 - .../ODPJsonSerializerFactoryTest.java | 50 +++++++++++++++++++ .../odp/serializer/ODPJsonSerializerTest.java | 12 +++-- .../ab/odp/DefaultODPApiManager.java | 21 ++++++-- .../ab/odp/DefaultODPApiManagerTest.java | 42 ++++++++++++++-- 5 files changed, 115 insertions(+), 11 deletions(-) create mode 100644 core-api/src/test/java/com/optimizely/ab/odp/serializer/ODPJsonSerializerFactoryTest.java diff --git a/core-api/src/test/java/com/optimizely/ab/odp/parser/ResponseJsonParserFactoryTest.java b/core-api/src/test/java/com/optimizely/ab/odp/parser/ResponseJsonParserFactoryTest.java index f5d8e8c89..a4f51a3a7 100644 --- a/core-api/src/test/java/com/optimizely/ab/odp/parser/ResponseJsonParserFactoryTest.java +++ b/core-api/src/test/java/com/optimizely/ab/odp/parser/ResponseJsonParserFactoryTest.java @@ -15,7 +15,6 @@ */ package com.optimizely.ab.odp.parser; -import com.optimizely.ab.internal.JsonParserProvider; import com.optimizely.ab.internal.PropertyUtils; import com.optimizely.ab.odp.parser.impl.GsonParser; import com.optimizely.ab.odp.parser.impl.JsonParser; diff --git a/core-api/src/test/java/com/optimizely/ab/odp/serializer/ODPJsonSerializerFactoryTest.java b/core-api/src/test/java/com/optimizely/ab/odp/serializer/ODPJsonSerializerFactoryTest.java new file mode 100644 index 000000000..c1219a49c --- /dev/null +++ b/core-api/src/test/java/com/optimizely/ab/odp/serializer/ODPJsonSerializerFactoryTest.java @@ -0,0 +1,50 @@ +/** + * Copyright 2022, Optimizely Inc. and contributors + * + * Licensed 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 com.optimizely.ab.odp.serializer; + +import com.optimizely.ab.internal.PropertyUtils; +import com.optimizely.ab.odp.serializer.impl.GsonSerializer; +import com.optimizely.ab.odp.serializer.impl.JsonSerializer; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class ODPJsonSerializerFactoryTest { + @Before + @After + public void clearParserSystemProperty() { + PropertyUtils.clear("default_parser"); + } + + @Test + public void getGsonSerializerWhenNoDefaultIsSet() { + assertEquals(GsonSerializer.class, ODPJsonSerializerFactory.getSerializer().getClass()); + } + + @Test + public void getCorrectSerializerWhenValidDefaultIsProvided() { + PropertyUtils.set("default_parser", "JSON_CONFIG_PARSER"); + assertEquals(JsonSerializer.class, ODPJsonSerializerFactory.getSerializer().getClass()); + } + + @Test + public void getGsonSerializerWhenGivenDefaultSerializerDoesNotExist() { + PropertyUtils.set("default_parser", "GARBAGE_VALUE"); + assertEquals(GsonSerializer.class, ODPJsonSerializerFactory.getSerializer().getClass()); + } +} diff --git a/core-api/src/test/java/com/optimizely/ab/odp/serializer/ODPJsonSerializerTest.java b/core-api/src/test/java/com/optimizely/ab/odp/serializer/ODPJsonSerializerTest.java index 9df4ab912..eba849a24 100644 --- a/core-api/src/test/java/com/optimizely/ab/odp/serializer/ODPJsonSerializerTest.java +++ b/core-api/src/test/java/com/optimizely/ab/odp/serializer/ODPJsonSerializerTest.java @@ -26,7 +26,6 @@ import java.util.*; import static junit.framework.TestCase.assertEquals; -import static junit.framework.TestCase.assertTrue; @RunWith(Parameterized.class) public class ODPJsonSerializerTest { @@ -43,7 +42,7 @@ public static List input() { } @Test - public void serializeValidEvents() throws JsonProcessingException { + public void serializeMultipleEvents() throws JsonProcessingException { List events = Arrays.asList( createTestEvent("1"), createTestEvent("2"), @@ -54,10 +53,17 @@ public void serializeValidEvents() throws JsonProcessingException { String expectedResult = "[{\"type\":\"type-1\",\"action\":\"action-1\",\"identifiers\":{\"vuid-1-3\":\"fs-1-3\",\"vuid-1-1\":\"fs-1-1\",\"vuid-1-2\":\"fs-1-2\"},\"data\":{\"source\":\"java-sdk\",\"data-1\":\"data-value-1\"}},{\"type\":\"type-2\",\"action\":\"action-2\",\"identifiers\":{\"vuid-2-3\":\"fs-2-3\",\"vuid-2-2\":\"fs-2-2\",\"vuid-2-1\":\"fs-2-1\"},\"data\":{\"source\":\"java-sdk\",\"data-1\":\"data-value-2\"}},{\"type\":\"type-3\",\"action\":\"action-3\",\"identifiers\":{\"vuid-3-3\":\"fs-3-3\",\"vuid-3-2\":\"fs-3-2\",\"vuid-3-1\":\"fs-3-1\"},\"data\":{\"source\":\"java-sdk\",\"data-1\":\"data-value-3\"}}]"; String serializedString = jsonSerializer.serializeEvents(events); - System.out.println(serializedString); assertEquals(mapper.readTree(expectedResult), mapper.readTree(serializedString)); } + @Test + public void serializeEmptyList() throws JsonProcessingException { + List events = Collections.emptyList(); + String expectedResult = "[]"; + String serializedString = jsonSerializer.serializeEvents(events); + assertEquals(expectedResult, serializedString); + } + private static ODPEvent createTestEvent(String index) { Map identifiers = new HashMap<>(); identifiers.put("vuid-" + index + "-1", "fs-" + index + "-1"); diff --git a/core-httpclient-impl/src/main/java/com/optimizely/ab/odp/DefaultODPApiManager.java b/core-httpclient-impl/src/main/java/com/optimizely/ab/odp/DefaultODPApiManager.java index 210c06e04..a2d8dfc4f 100644 --- a/core-httpclient-impl/src/main/java/com/optimizely/ab/odp/DefaultODPApiManager.java +++ b/core-httpclient-impl/src/main/java/com/optimizely/ab/odp/DefaultODPApiManager.java @@ -37,13 +37,26 @@ public class DefaultODPApiManager implements ODPApiManager { private final OptimizelyHttpClient httpClient; + private final ODPJsonSerializer jsonSerializer; + public DefaultODPApiManager() { - this(OptimizelyHttpClient.builder().build()); + this(OptimizelyHttpClient.builder().build(), ODPJsonSerializerFactory.getSerializer()); } @VisibleForTesting - DefaultODPApiManager(OptimizelyHttpClient httpClient) { - this.httpClient = httpClient; + DefaultODPApiManager(OptimizelyHttpClient httpClient, ODPJsonSerializer jsonSerializer) { + + if (httpClient != null) { + this.httpClient = httpClient; + } else { + this.httpClient = OptimizelyHttpClient.builder().build(); + } + + if (jsonSerializer != null) { + this.jsonSerializer = jsonSerializer; + } else { + this.jsonSerializer = ODPJsonSerializerFactory.getSerializer(); + } } @VisibleForTesting @@ -172,7 +185,7 @@ public String fetchQualifiedSegments(String apiKey, String apiEndpoint, String u @Override public void sendEvents(String apiKey, String apiEndpoint, List events) { HttpPost request = new HttpPost(apiEndpoint); - String requestPayload = ODPJsonSerializerFactory.getSerializer().serializeEvents(events); + String requestPayload = this.jsonSerializer.serializeEvents(events); if (requestPayload == null || requestPayload.isEmpty()) { logger.error("ODP event send failed (Failed to serialize event payload)"); diff --git a/core-httpclient-impl/src/test/java/com/optimizely/ab/odp/DefaultODPApiManagerTest.java b/core-httpclient-impl/src/test/java/com/optimizely/ab/odp/DefaultODPApiManagerTest.java index c81af2dce..ed20e5d10 100644 --- a/core-httpclient-impl/src/test/java/com/optimizely/ab/odp/DefaultODPApiManagerTest.java +++ b/core-httpclient-impl/src/test/java/com/optimizely/ab/odp/DefaultODPApiManagerTest.java @@ -18,6 +18,7 @@ import ch.qos.logback.classic.Level; import com.optimizely.ab.OptimizelyHttpClient; import com.optimizely.ab.internal.LogbackVerifier; +import com.optimizely.ab.odp.serializer.ODPJsonSerializer; import org.apache.http.StatusLine; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; @@ -30,6 +31,7 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import static org.junit.Assert.*; import static org.mockito.Matchers.any; @@ -38,11 +40,15 @@ public class DefaultODPApiManagerTest { private static final String validResponse = "{\"data\":{\"customer\":{\"audiences\":{\"edges\":[{\"node\":{\"name\":\"has_email\",\"state\":\"qualified\"}},{\"node\":{\"name\":\"has_email_opted_in\",\"state\":\"qualified\"}}]}}}}"; + private static final String validEventPayload = "[{\"action\":\"identified\",\"identifiers\":{\"vuid\":\"testid\"},\"data\":{\"data_source_type\":\"sdk\",\"data_source\":\"javascript-sdk\"},\"type\":\"fullstack\"}]"; + @Rule public LogbackVerifier logbackVerifier = new LogbackVerifier(); OptimizelyHttpClient mockHttpClient; + ODPJsonSerializer mockJsonSerializer; + @Before public void setUp() throws Exception { setupHttpClient(200); @@ -61,6 +67,11 @@ private void setupHttpClient(int statusCode) throws Exception { .thenReturn(httpResponse); } + private void setupJsonSerializer(String mockResponse) { + mockJsonSerializer = mock(ODPJsonSerializer.class); + when(mockJsonSerializer.serializeEvents(any())).thenReturn(mockResponse); + } + @Test public void generateCorrectSegmentsStringWhenListHasOneItem() { DefaultODPApiManager apiManager = new DefaultODPApiManager(); @@ -86,7 +97,7 @@ public void generateEmptyStringWhenGivenListIsEmpty() { @Test public void generateCorrectRequestBody() throws Exception { - ODPApiManager apiManager = new DefaultODPApiManager(mockHttpClient); + ODPApiManager apiManager = new DefaultODPApiManager(mockHttpClient, null); apiManager.fetchQualifiedSegments("key", "endPoint", "fs_user_id", "test_user", Arrays.asList("segment_1", "segment_2")); verify(mockHttpClient, times(1)).execute(any(HttpPost.class)); @@ -98,7 +109,7 @@ public void generateCorrectRequestBody() throws Exception { @Test public void returnResponseStringWhenStatusIs200() throws Exception { - ODPApiManager apiManager = new DefaultODPApiManager(mockHttpClient); + ODPApiManager apiManager = new DefaultODPApiManager(mockHttpClient, null); String responseString = apiManager.fetchQualifiedSegments("key", "endPoint", "fs_user_id", "test_user", Arrays.asList("segment_1", "segment_2")); verify(mockHttpClient, times(1)).execute(any(HttpPost.class)); assertEquals(validResponse, responseString); @@ -107,10 +118,35 @@ public void returnResponseStringWhenStatusIs200() throws Exception { @Test public void returnNullWhenStatusIsNot200AndLogError() throws Exception { setupHttpClient(500); - ODPApiManager apiManager = new DefaultODPApiManager(mockHttpClient); + ODPApiManager apiManager = new DefaultODPApiManager(mockHttpClient, null); String responseString = apiManager.fetchQualifiedSegments("key", "endPoint", "fs_user_id", "test_user", Arrays.asList("segment_1", "segment_2")); verify(mockHttpClient, times(1)).execute(any(HttpPost.class)); logbackVerifier.expectMessage(Level.ERROR, "Unexpected response from ODP server, Response code: 500, null"); assertEquals(null, responseString); } + + @Test + public void eventDispatchSuccess() { + setupJsonSerializer(validEventPayload); + ODPApiManager apiManager = new DefaultODPApiManager(mockHttpClient, mockJsonSerializer); + apiManager.sendEvents("testKey", "testEndpoint", Collections.emptyList()); + logbackVerifier.expectMessage(Level.DEBUG, "ODP Event Dispatched successfully"); + } + + @Test + public void eventDispatchFailStatus() throws Exception { + setupHttpClient(400); + setupJsonSerializer(validEventPayload); + ODPApiManager apiManager = new DefaultODPApiManager(mockHttpClient, mockJsonSerializer); + apiManager.sendEvents("testKey", "testEndpoint", Collections.emptyList()); + logbackVerifier.expectMessage(Level.ERROR, "ODP event send failed (Response code: 400, null)"); + } + + @Test + public void eventDispatchFailSerialize() { + setupJsonSerializer(null); + ODPApiManager apiManager = new DefaultODPApiManager(mockHttpClient, mockJsonSerializer); + apiManager.sendEvents("testKey", "testEndpoint", Collections.emptyList()); + logbackVerifier.expectMessage(Level.ERROR, "ODP event send failed (Failed to serialize event payload)"); + } } From 6e3515dc465001bf8b57967794c6da389ffead06 Mon Sep 17 00:00:00 2001 From: zashraf1985 Date: Tue, 23 Aug 2022 11:14:25 -0700 Subject: [PATCH 5/8] Incorporated review feedback. --- .../java/com/optimizely/ab/odp/ODPEvent.java | 8 ++++---- .../serializer/impl/JsonSimpleSerializer.java | 2 +- .../serializer/ODPJsonSerializerFactoryTest.java | 16 +++++++++++++++- .../ab/odp/serializer/ODPJsonSerializerTest.java | 9 +++++++-- 4 files changed, 27 insertions(+), 8 deletions(-) diff --git a/core-api/src/main/java/com/optimizely/ab/odp/ODPEvent.java b/core-api/src/main/java/com/optimizely/ab/odp/ODPEvent.java index a93ed35b1..34bd340b6 100644 --- a/core-api/src/main/java/com/optimizely/ab/odp/ODPEvent.java +++ b/core-api/src/main/java/com/optimizely/ab/odp/ODPEvent.java @@ -21,9 +21,9 @@ public class ODPEvent { private String type; private String action; private Map identifiers; - private Map data; + private Map data; - public ODPEvent(String type, String action, Map identifiers, Map data) { + public ODPEvent(String type, String action, Map identifiers, Map data) { this.type = type; this.action = action; this.identifiers = identifiers; @@ -54,11 +54,11 @@ public void setIdentifiers(Map identifiers) { this.identifiers = identifiers; } - public Map getData() { + public Map getData() { return data; } - public void setData(Map data) { + public void setData(Map data) { this.data = data; } } diff --git a/core-api/src/main/java/com/optimizely/ab/odp/serializer/impl/JsonSimpleSerializer.java b/core-api/src/main/java/com/optimizely/ab/odp/serializer/impl/JsonSimpleSerializer.java index 72978b455..96e5a7357 100644 --- a/core-api/src/main/java/com/optimizely/ab/odp/serializer/impl/JsonSimpleSerializer.java +++ b/core-api/src/main/java/com/optimizely/ab/odp/serializer/impl/JsonSimpleSerializer.java @@ -42,7 +42,7 @@ public String serializeEvents(List events) { if (event.getData() != null) { JSONObject data = new JSONObject(); - for (Map.Entry dataEntry : event.getData().entrySet()) { + for (Map.Entry dataEntry : event.getData().entrySet()) { data.put(dataEntry.getKey(), dataEntry.getValue()); } eventObject.put("data", data); diff --git a/core-api/src/test/java/com/optimizely/ab/odp/serializer/ODPJsonSerializerFactoryTest.java b/core-api/src/test/java/com/optimizely/ab/odp/serializer/ODPJsonSerializerFactoryTest.java index c1219a49c..5c47a1f4f 100644 --- a/core-api/src/test/java/com/optimizely/ab/odp/serializer/ODPJsonSerializerFactoryTest.java +++ b/core-api/src/test/java/com/optimizely/ab/odp/serializer/ODPJsonSerializerFactoryTest.java @@ -17,7 +17,9 @@ import com.optimizely.ab.internal.PropertyUtils; import com.optimizely.ab.odp.serializer.impl.GsonSerializer; +import com.optimizely.ab.odp.serializer.impl.JacksonSerializer; import com.optimizely.ab.odp.serializer.impl.JsonSerializer; +import com.optimizely.ab.odp.serializer.impl.JsonSimpleSerializer; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -37,11 +39,23 @@ public void getGsonSerializerWhenNoDefaultIsSet() { } @Test - public void getCorrectSerializerWhenValidDefaultIsProvided() { + public void getCorrectSerializerWhenValidDefaultIsProvidedIsJson() { PropertyUtils.set("default_parser", "JSON_CONFIG_PARSER"); assertEquals(JsonSerializer.class, ODPJsonSerializerFactory.getSerializer().getClass()); } + @Test + public void getCorrectSerializerWhenValidDefaultIsProvidedIsJsonSimple() { + PropertyUtils.set("default_parser", "JSON_SIMPLE_CONFIG_PARSER"); + assertEquals(JsonSimpleSerializer.class, ODPJsonSerializerFactory.getSerializer().getClass()); + } + + @Test + public void getCorrectSerializerWhenValidDefaultIsProvidedIsJackson() { + PropertyUtils.set("default_parser", "JACKSON_CONFIG_PARSER"); + assertEquals(JacksonSerializer.class, ODPJsonSerializerFactory.getSerializer().getClass()); + } + @Test public void getGsonSerializerWhenGivenDefaultSerializerDoesNotExist() { PropertyUtils.set("default_parser", "GARBAGE_VALUE"); diff --git a/core-api/src/test/java/com/optimizely/ab/odp/serializer/ODPJsonSerializerTest.java b/core-api/src/test/java/com/optimizely/ab/odp/serializer/ODPJsonSerializerTest.java index eba849a24..64b5d1055 100644 --- a/core-api/src/test/java/com/optimizely/ab/odp/serializer/ODPJsonSerializerTest.java +++ b/core-api/src/test/java/com/optimizely/ab/odp/serializer/ODPJsonSerializerTest.java @@ -51,7 +51,7 @@ public void serializeMultipleEvents() throws JsonProcessingException { ObjectMapper mapper = new ObjectMapper(); - String expectedResult = "[{\"type\":\"type-1\",\"action\":\"action-1\",\"identifiers\":{\"vuid-1-3\":\"fs-1-3\",\"vuid-1-1\":\"fs-1-1\",\"vuid-1-2\":\"fs-1-2\"},\"data\":{\"source\":\"java-sdk\",\"data-1\":\"data-value-1\"}},{\"type\":\"type-2\",\"action\":\"action-2\",\"identifiers\":{\"vuid-2-3\":\"fs-2-3\",\"vuid-2-2\":\"fs-2-2\",\"vuid-2-1\":\"fs-2-1\"},\"data\":{\"source\":\"java-sdk\",\"data-1\":\"data-value-2\"}},{\"type\":\"type-3\",\"action\":\"action-3\",\"identifiers\":{\"vuid-3-3\":\"fs-3-3\",\"vuid-3-2\":\"fs-3-2\",\"vuid-3-1\":\"fs-3-1\"},\"data\":{\"source\":\"java-sdk\",\"data-1\":\"data-value-3\"}}]"; + String expectedResult = "[{\"type\":\"type-1\",\"action\":\"action-1\",\"identifiers\":{\"vuid-1-3\":\"fs-1-3\",\"vuid-1-1\":\"fs-1-1\",\"vuid-1-2\":\"fs-1-2\"},\"data\":{\"source\":\"java-sdk\",\"data-1\":\"data-value-1\",\"data-num\":1,\"data-bool-true\":true,\"data-bool-false\":false,\"data-float\":2.33}},{\"type\":\"type-2\",\"action\":\"action-2\",\"identifiers\":{\"vuid-2-3\":\"fs-2-3\",\"vuid-2-2\":\"fs-2-2\",\"vuid-2-1\":\"fs-2-1\"},\"data\":{\"source\":\"java-sdk\",\"data-1\":\"data-value-2\",\"data-num\":2,\"data-bool-true\":true,\"data-bool-false\":false,\"data-float\":2.33}},{\"type\":\"type-3\",\"action\":\"action-3\",\"identifiers\":{\"vuid-3-3\":\"fs-3-3\",\"vuid-3-2\":\"fs-3-2\",\"vuid-3-1\":\"fs-3-1\"},\"data\":{\"source\":\"java-sdk\",\"data-1\":\"data-value-3\",\"data-num\":3,\"data-bool-true\":true,\"data-bool-false\":false,\"data-float\":2.33}}]"; String serializedString = jsonSerializer.serializeEvents(events); assertEquals(mapper.readTree(expectedResult), mapper.readTree(serializedString)); } @@ -70,9 +70,14 @@ private static ODPEvent createTestEvent(String index) { identifiers.put("vuid-" + index + "-2", "fs-" + index + "-2"); identifiers.put("vuid-" + index + "-3", "fs-" + index + "-3"); - Map data = new HashMap<>(); + Map data = new HashMap<>(); data.put("source", "java-sdk"); data.put("data-1", "data-value-" + index); + data.put("data-num", Integer.parseInt(index)); + data.put("data-float", 2.33); + data.put("data-bool-true", true); + data.put("data-bool-false", false); + return new ODPEvent("type-" + index, "action-" + index, identifiers, data); } From a86f66fe13af35f086c18a81b853a783280d0bcd Mon Sep 17 00:00:00 2001 From: zashraf1985 Date: Tue, 23 Aug 2022 11:24:59 -0700 Subject: [PATCH 6/8] Return statudCode from sendEvents --- .../java/com/optimizely/ab/odp/ODPApiManager.java | 2 +- .../com/optimizely/ab/odp/DefaultODPApiManager.java | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/core-api/src/main/java/com/optimizely/ab/odp/ODPApiManager.java b/core-api/src/main/java/com/optimizely/ab/odp/ODPApiManager.java index fae9fd958..08a28a36a 100644 --- a/core-api/src/main/java/com/optimizely/ab/odp/ODPApiManager.java +++ b/core-api/src/main/java/com/optimizely/ab/odp/ODPApiManager.java @@ -20,5 +20,5 @@ public interface ODPApiManager { String fetchQualifiedSegments(String apiKey, String apiEndpoint, String userKey, String userValue, List segmentsToCheck); - void sendEvents(String apiKey, String apiEndpoint, List events); + Integer sendEvents(String apiKey, String apiEndpoint, List events); } diff --git a/core-httpclient-impl/src/main/java/com/optimizely/ab/odp/DefaultODPApiManager.java b/core-httpclient-impl/src/main/java/com/optimizely/ab/odp/DefaultODPApiManager.java index a2d8dfc4f..b3a7188f3 100644 --- a/core-httpclient-impl/src/main/java/com/optimizely/ab/odp/DefaultODPApiManager.java +++ b/core-httpclient-impl/src/main/java/com/optimizely/ab/odp/DefaultODPApiManager.java @@ -19,7 +19,6 @@ import com.optimizely.ab.annotations.VisibleForTesting; import com.optimizely.ab.odp.serializer.ODPJsonSerializer; import com.optimizely.ab.odp.serializer.ODPJsonSerializerFactory; -import org.apache.http.HttpStatus; import org.apache.http.StatusLine; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; @@ -183,20 +182,20 @@ public String fetchQualifiedSegments(String apiKey, String apiEndpoint, String u } @Override - public void sendEvents(String apiKey, String apiEndpoint, List events) { + public Integer sendEvents(String apiKey, String apiEndpoint, List events) { HttpPost request = new HttpPost(apiEndpoint); String requestPayload = this.jsonSerializer.serializeEvents(events); if (requestPayload == null || requestPayload.isEmpty()) { logger.error("ODP event send failed (Failed to serialize event payload)"); - return; + return null; } try { request.setEntity(new StringEntity(requestPayload)); } catch (UnsupportedEncodingException e) { logger.error("ODP event send failed (Error encoding request payload)", e); - return; + return null; } request.setHeader("x-api-key", apiKey); request.setHeader("content-type", "application/json"); @@ -206,10 +205,11 @@ public void sendEvents(String apiKey, String apiEndpoint, List events) response = httpClient.execute(request); } catch (IOException e) { logger.error("Error retrieving response from event request", e); - return; + return null; } - if (response.getStatusLine().getStatusCode() >= 400) { + int statusCode = response.getStatusLine().getStatusCode(); + if ( statusCode >= 400) { StatusLine statusLine = response.getStatusLine(); logger.error(String.format("ODP event send failed (Response code: %d, %s)", statusLine.getStatusCode(), statusLine.getReasonPhrase())); } else { @@ -217,6 +217,7 @@ public void sendEvents(String apiKey, String apiEndpoint, List events) } closeHttpResponse(response); + return statusCode; } private static void closeHttpResponse(CloseableHttpResponse response) { From 3323a317775e9e5b364c9547ebd30bd48ad79c1f Mon Sep 17 00:00:00 2001 From: zashraf1985 Date: Thu, 25 Aug 2022 17:37:39 -0700 Subject: [PATCH 7/8] returning nonnull response so that we can add a check and retry on the top level functions. --- .../main/java/com/optimizely/ab/odp/DefaultODPApiManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core-httpclient-impl/src/main/java/com/optimizely/ab/odp/DefaultODPApiManager.java b/core-httpclient-impl/src/main/java/com/optimizely/ab/odp/DefaultODPApiManager.java index b3a7188f3..5224af1c3 100644 --- a/core-httpclient-impl/src/main/java/com/optimizely/ab/odp/DefaultODPApiManager.java +++ b/core-httpclient-impl/src/main/java/com/optimizely/ab/odp/DefaultODPApiManager.java @@ -205,7 +205,7 @@ public Integer sendEvents(String apiKey, String apiEndpoint, List even response = httpClient.execute(request); } catch (IOException e) { logger.error("Error retrieving response from event request", e); - return null; + return 0; } int statusCode = response.getStatusLine().getStatusCode(); From 543c81afd4c10096806fe2fec936d98ecf3eb28e Mon Sep 17 00:00:00 2001 From: zashraf1985 Date: Fri, 26 Aug 2022 16:44:51 -0700 Subject: [PATCH 8/8] Incorporated more review feedback --- .../com/optimizely/ab/odp/ODPApiManager.java | 2 +- .../odp/serializer/impl/GsonSerializer.java | 2 +- .../odp/serializer/impl/JsonSerializer.java | 32 ++++++++++- .../odp/serializer/ODPJsonSerializerTest.java | 3 +- .../ab/odp/DefaultODPApiManager.java | 54 ++++++++++--------- .../ab/odp/DefaultODPApiManagerTest.java | 37 +++---------- 6 files changed, 71 insertions(+), 59 deletions(-) diff --git a/core-api/src/main/java/com/optimizely/ab/odp/ODPApiManager.java b/core-api/src/main/java/com/optimizely/ab/odp/ODPApiManager.java index 08a28a36a..dee9413dd 100644 --- a/core-api/src/main/java/com/optimizely/ab/odp/ODPApiManager.java +++ b/core-api/src/main/java/com/optimizely/ab/odp/ODPApiManager.java @@ -20,5 +20,5 @@ public interface ODPApiManager { String fetchQualifiedSegments(String apiKey, String apiEndpoint, String userKey, String userValue, List segmentsToCheck); - Integer sendEvents(String apiKey, String apiEndpoint, List events); + Integer sendEvents(String apiKey, String apiEndpoint, String eventPayload); } diff --git a/core-api/src/main/java/com/optimizely/ab/odp/serializer/impl/GsonSerializer.java b/core-api/src/main/java/com/optimizely/ab/odp/serializer/impl/GsonSerializer.java index b4ddb4a87..d72963260 100644 --- a/core-api/src/main/java/com/optimizely/ab/odp/serializer/impl/GsonSerializer.java +++ b/core-api/src/main/java/com/optimizely/ab/odp/serializer/impl/GsonSerializer.java @@ -25,7 +25,7 @@ public class GsonSerializer implements ODPJsonSerializer { @Override public String serializeEvents(List events) { - Gson gson = new GsonBuilder().create(); + Gson gson = new GsonBuilder().serializeNulls().create(); return gson.toJson(events); } } diff --git a/core-api/src/main/java/com/optimizely/ab/odp/serializer/impl/JsonSerializer.java b/core-api/src/main/java/com/optimizely/ab/odp/serializer/impl/JsonSerializer.java index 69a16c18f..c65c1fda3 100644 --- a/core-api/src/main/java/com/optimizely/ab/odp/serializer/impl/JsonSerializer.java +++ b/core-api/src/main/java/com/optimizely/ab/odp/serializer/impl/JsonSerializer.java @@ -18,12 +18,42 @@ import com.optimizely.ab.odp.ODPEvent; import com.optimizely.ab.odp.serializer.ODPJsonSerializer; import org.json.JSONArray; +import org.json.JSONObject; import java.util.List; +import java.util.Map; public class JsonSerializer implements ODPJsonSerializer { @Override public String serializeEvents(List events) { - return new JSONArray(events).toString(); + JSONArray jsonArray = new JSONArray(); + for (ODPEvent event: events) { + JSONObject eventObject = new JSONObject(); + eventObject.put("type", event.getType()); + eventObject.put("action", event.getAction()); + + if (event.getIdentifiers() != null) { + JSONObject identifiers = new JSONObject(); + for (Map.Entry identifier : event.getIdentifiers().entrySet()) { + identifiers.put(identifier.getKey(), identifier.getValue()); + } + eventObject.put("identifiers", identifiers); + } + + if (event.getData() != null) { + JSONObject data = new JSONObject(); + for (Map.Entry dataEntry : event.getData().entrySet()) { + data.put(dataEntry.getKey(), getJSONObjectValue(dataEntry.getValue())); + } + eventObject.put("data", data); + } + + jsonArray.put(eventObject); + } + return jsonArray.toString(); + } + + private static Object getJSONObjectValue(Object value) { + return value == null ? JSONObject.NULL : value; } } diff --git a/core-api/src/test/java/com/optimizely/ab/odp/serializer/ODPJsonSerializerTest.java b/core-api/src/test/java/com/optimizely/ab/odp/serializer/ODPJsonSerializerTest.java index 64b5d1055..7a9538a8f 100644 --- a/core-api/src/test/java/com/optimizely/ab/odp/serializer/ODPJsonSerializerTest.java +++ b/core-api/src/test/java/com/optimizely/ab/odp/serializer/ODPJsonSerializerTest.java @@ -51,7 +51,7 @@ public void serializeMultipleEvents() throws JsonProcessingException { ObjectMapper mapper = new ObjectMapper(); - String expectedResult = "[{\"type\":\"type-1\",\"action\":\"action-1\",\"identifiers\":{\"vuid-1-3\":\"fs-1-3\",\"vuid-1-1\":\"fs-1-1\",\"vuid-1-2\":\"fs-1-2\"},\"data\":{\"source\":\"java-sdk\",\"data-1\":\"data-value-1\",\"data-num\":1,\"data-bool-true\":true,\"data-bool-false\":false,\"data-float\":2.33}},{\"type\":\"type-2\",\"action\":\"action-2\",\"identifiers\":{\"vuid-2-3\":\"fs-2-3\",\"vuid-2-2\":\"fs-2-2\",\"vuid-2-1\":\"fs-2-1\"},\"data\":{\"source\":\"java-sdk\",\"data-1\":\"data-value-2\",\"data-num\":2,\"data-bool-true\":true,\"data-bool-false\":false,\"data-float\":2.33}},{\"type\":\"type-3\",\"action\":\"action-3\",\"identifiers\":{\"vuid-3-3\":\"fs-3-3\",\"vuid-3-2\":\"fs-3-2\",\"vuid-3-1\":\"fs-3-1\"},\"data\":{\"source\":\"java-sdk\",\"data-1\":\"data-value-3\",\"data-num\":3,\"data-bool-true\":true,\"data-bool-false\":false,\"data-float\":2.33}}]"; + String expectedResult = "[{\"type\":\"type-1\",\"action\":\"action-1\",\"identifiers\":{\"vuid-1-3\":\"fs-1-3\",\"vuid-1-1\":\"fs-1-1\",\"vuid-1-2\":\"fs-1-2\"},\"data\":{\"source\":\"java-sdk\",\"data-1\":\"data-value-1\",\"data-num\":1,\"data-bool-true\":true,\"data-bool-false\":false,\"data-float\":2.33,\"data-null\":null}},{\"type\":\"type-2\",\"action\":\"action-2\",\"identifiers\":{\"vuid-2-3\":\"fs-2-3\",\"vuid-2-2\":\"fs-2-2\",\"vuid-2-1\":\"fs-2-1\"},\"data\":{\"source\":\"java-sdk\",\"data-1\":\"data-value-2\",\"data-num\":2,\"data-bool-true\":true,\"data-bool-false\":false,\"data-float\":2.33,\"data-null\":null}},{\"type\":\"type-3\",\"action\":\"action-3\",\"identifiers\":{\"vuid-3-3\":\"fs-3-3\",\"vuid-3-2\":\"fs-3-2\",\"vuid-3-1\":\"fs-3-1\"},\"data\":{\"source\":\"java-sdk\",\"data-1\":\"data-value-3\",\"data-num\":3,\"data-bool-true\":true,\"data-bool-false\":false,\"data-float\":2.33,\"data-null\":null}}]"; String serializedString = jsonSerializer.serializeEvents(events); assertEquals(mapper.readTree(expectedResult), mapper.readTree(serializedString)); } @@ -77,6 +77,7 @@ private static ODPEvent createTestEvent(String index) { data.put("data-float", 2.33); data.put("data-bool-true", true); data.put("data-bool-false", false); + data.put("data-null", null); return new ODPEvent("type-" + index, "action-" + index, identifiers, data); diff --git a/core-httpclient-impl/src/main/java/com/optimizely/ab/odp/DefaultODPApiManager.java b/core-httpclient-impl/src/main/java/com/optimizely/ab/odp/DefaultODPApiManager.java index 5224af1c3..07ee1b3eb 100644 --- a/core-httpclient-impl/src/main/java/com/optimizely/ab/odp/DefaultODPApiManager.java +++ b/core-httpclient-impl/src/main/java/com/optimizely/ab/odp/DefaultODPApiManager.java @@ -17,8 +17,6 @@ import com.optimizely.ab.OptimizelyHttpClient; import com.optimizely.ab.annotations.VisibleForTesting; -import com.optimizely.ab.odp.serializer.ODPJsonSerializer; -import com.optimizely.ab.odp.serializer.ODPJsonSerializerFactory; import org.apache.http.StatusLine; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; @@ -36,26 +34,13 @@ public class DefaultODPApiManager implements ODPApiManager { private final OptimizelyHttpClient httpClient; - private final ODPJsonSerializer jsonSerializer; - public DefaultODPApiManager() { - this(OptimizelyHttpClient.builder().build(), ODPJsonSerializerFactory.getSerializer()); + this(OptimizelyHttpClient.builder().build()); } @VisibleForTesting - DefaultODPApiManager(OptimizelyHttpClient httpClient, ODPJsonSerializer jsonSerializer) { - - if (httpClient != null) { - this.httpClient = httpClient; - } else { - this.httpClient = OptimizelyHttpClient.builder().build(); - } - - if (jsonSerializer != null) { - this.jsonSerializer = jsonSerializer; - } else { - this.jsonSerializer = ODPJsonSerializerFactory.getSerializer(); - } + DefaultODPApiManager(OptimizelyHttpClient httpClient) { + this.httpClient = httpClient; } @VisibleForTesting @@ -181,18 +166,35 @@ public String fetchQualifiedSegments(String apiKey, String apiEndpoint, String u return null; } + /* + eventPayload Format + [ + { + "action": "identified", + "identifiers": {"vuid": , "fs_user_id": , ....}, + "data": {“source”: , ....}, + "type": " fullstack " + }, + { + "action": "client_initialized", + "identifiers": {"vuid": , ....}, + "data": {“source”: , ....}, + "type": "fullstack" + } + ] + + Returns: + 1. null, When there was a non-recoverable error and no retry is needed. + 2. 0 If an unexpected error occurred and retrying can be useful. + 3. HTTPStatus code if httpclient was able to make the request and was able to receive response. + It is recommended to retry if status code was 5xx. + */ @Override - public Integer sendEvents(String apiKey, String apiEndpoint, List events) { + public Integer sendEvents(String apiKey, String apiEndpoint, String eventPayload) { HttpPost request = new HttpPost(apiEndpoint); - String requestPayload = this.jsonSerializer.serializeEvents(events); - - if (requestPayload == null || requestPayload.isEmpty()) { - logger.error("ODP event send failed (Failed to serialize event payload)"); - return null; - } try { - request.setEntity(new StringEntity(requestPayload)); + request.setEntity(new StringEntity(eventPayload)); } catch (UnsupportedEncodingException e) { logger.error("ODP event send failed (Error encoding request payload)", e); return null; diff --git a/core-httpclient-impl/src/test/java/com/optimizely/ab/odp/DefaultODPApiManagerTest.java b/core-httpclient-impl/src/test/java/com/optimizely/ab/odp/DefaultODPApiManagerTest.java index ed20e5d10..77440f804 100644 --- a/core-httpclient-impl/src/test/java/com/optimizely/ab/odp/DefaultODPApiManagerTest.java +++ b/core-httpclient-impl/src/test/java/com/optimizely/ab/odp/DefaultODPApiManagerTest.java @@ -18,7 +18,6 @@ import ch.qos.logback.classic.Level; import com.optimizely.ab.OptimizelyHttpClient; import com.optimizely.ab.internal.LogbackVerifier; -import com.optimizely.ab.odp.serializer.ODPJsonSerializer; import org.apache.http.StatusLine; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; @@ -31,7 +30,6 @@ import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import static org.junit.Assert.*; import static org.mockito.Matchers.any; @@ -40,15 +38,11 @@ public class DefaultODPApiManagerTest { private static final String validResponse = "{\"data\":{\"customer\":{\"audiences\":{\"edges\":[{\"node\":{\"name\":\"has_email\",\"state\":\"qualified\"}},{\"node\":{\"name\":\"has_email_opted_in\",\"state\":\"qualified\"}}]}}}}"; - private static final String validEventPayload = "[{\"action\":\"identified\",\"identifiers\":{\"vuid\":\"testid\"},\"data\":{\"data_source_type\":\"sdk\",\"data_source\":\"javascript-sdk\"},\"type\":\"fullstack\"}]"; - @Rule public LogbackVerifier logbackVerifier = new LogbackVerifier(); OptimizelyHttpClient mockHttpClient; - ODPJsonSerializer mockJsonSerializer; - @Before public void setUp() throws Exception { setupHttpClient(200); @@ -67,11 +61,6 @@ private void setupHttpClient(int statusCode) throws Exception { .thenReturn(httpResponse); } - private void setupJsonSerializer(String mockResponse) { - mockJsonSerializer = mock(ODPJsonSerializer.class); - when(mockJsonSerializer.serializeEvents(any())).thenReturn(mockResponse); - } - @Test public void generateCorrectSegmentsStringWhenListHasOneItem() { DefaultODPApiManager apiManager = new DefaultODPApiManager(); @@ -97,7 +86,7 @@ public void generateEmptyStringWhenGivenListIsEmpty() { @Test public void generateCorrectRequestBody() throws Exception { - ODPApiManager apiManager = new DefaultODPApiManager(mockHttpClient, null); + ODPApiManager apiManager = new DefaultODPApiManager(mockHttpClient); apiManager.fetchQualifiedSegments("key", "endPoint", "fs_user_id", "test_user", Arrays.asList("segment_1", "segment_2")); verify(mockHttpClient, times(1)).execute(any(HttpPost.class)); @@ -109,7 +98,7 @@ public void generateCorrectRequestBody() throws Exception { @Test public void returnResponseStringWhenStatusIs200() throws Exception { - ODPApiManager apiManager = new DefaultODPApiManager(mockHttpClient, null); + ODPApiManager apiManager = new DefaultODPApiManager(mockHttpClient); String responseString = apiManager.fetchQualifiedSegments("key", "endPoint", "fs_user_id", "test_user", Arrays.asList("segment_1", "segment_2")); verify(mockHttpClient, times(1)).execute(any(HttpPost.class)); assertEquals(validResponse, responseString); @@ -118,35 +107,25 @@ public void returnResponseStringWhenStatusIs200() throws Exception { @Test public void returnNullWhenStatusIsNot200AndLogError() throws Exception { setupHttpClient(500); - ODPApiManager apiManager = new DefaultODPApiManager(mockHttpClient, null); + ODPApiManager apiManager = new DefaultODPApiManager(mockHttpClient); String responseString = apiManager.fetchQualifiedSegments("key", "endPoint", "fs_user_id", "test_user", Arrays.asList("segment_1", "segment_2")); verify(mockHttpClient, times(1)).execute(any(HttpPost.class)); logbackVerifier.expectMessage(Level.ERROR, "Unexpected response from ODP server, Response code: 500, null"); - assertEquals(null, responseString); + assertNull(responseString); } @Test public void eventDispatchSuccess() { - setupJsonSerializer(validEventPayload); - ODPApiManager apiManager = new DefaultODPApiManager(mockHttpClient, mockJsonSerializer); - apiManager.sendEvents("testKey", "testEndpoint", Collections.emptyList()); + ODPApiManager apiManager = new DefaultODPApiManager(mockHttpClient); + apiManager.sendEvents("testKey", "testEndpoint", "[]"); logbackVerifier.expectMessage(Level.DEBUG, "ODP Event Dispatched successfully"); } @Test public void eventDispatchFailStatus() throws Exception { setupHttpClient(400); - setupJsonSerializer(validEventPayload); - ODPApiManager apiManager = new DefaultODPApiManager(mockHttpClient, mockJsonSerializer); - apiManager.sendEvents("testKey", "testEndpoint", Collections.emptyList()); + ODPApiManager apiManager = new DefaultODPApiManager(mockHttpClient); + apiManager.sendEvents("testKey", "testEndpoint", "[]]"); logbackVerifier.expectMessage(Level.ERROR, "ODP event send failed (Response code: 400, null)"); } - - @Test - public void eventDispatchFailSerialize() { - setupJsonSerializer(null); - ODPApiManager apiManager = new DefaultODPApiManager(mockHttpClient, mockJsonSerializer); - apiManager.sendEvents("testKey", "testEndpoint", Collections.emptyList()); - logbackVerifier.expectMessage(Level.ERROR, "ODP event send failed (Failed to serialize event payload)"); - } }