From 55521b675de289c3c1ba5f80d40d338a61c9aac5 Mon Sep 17 00:00:00 2001 From: Desu Sai Venkat <48179357+desusai7@users.noreply.github.com> Date: Tue, 6 Feb 2024 12:54:11 +0530 Subject: [PATCH 1/4] fix: handling the serialization of special floating point values while serializing any object (#382) * fix: removed invalid numbers if found while serializing RudderMessage object * chore: minor improvements while removing invalid numbers from message * fix: stringifying special floating numbers instead of filtering them out * chore: fixed sonarcloud issues * chore: renamed dump to process across the codebase * fix: fixed serializing of special floating point numbers in RudderContext and RudderTraits Adapter as well * fix: fixed failing unit tests for invalid numbers * chore: minor improvements --------- Co-authored-by: Desu Sai Venkat --- .../android/sdk/core/DBPersistentManager.java | 2 +- .../sdk/core/EventInsertionCallback.java | 2 +- .../android/sdk/core/EventRepository.java | 16 +- .../android/sdk/core/RudderClient.java | 12 +- .../sdk/core/RudderCloudModeManager.java | 4 +- .../android/sdk/core/RudderContext.java | 10 +- .../sdk/core/RudderDeviceModeManager.java | 90 ++++---- ...RudderDeviceModeTransformationManager.java | 50 ++-- .../android/sdk/core/RudderMessage.java | 4 + .../sdk/core/RudderPreferenceManager.java | 2 +- .../sdk/core/RudderServerConfigManager.java | 31 +-- .../android/sdk/core/RudderTraits.java | 4 +- .../TransformationResponseDeserializer.java | 15 +- .../core/ecomm/events/CartViewedEvent.java | 3 +- .../ecomm/events/CheckoutStartedEvent.java | 3 +- .../events/CheckoutStepCompletedEvent.java | 3 +- .../ecomm/events/CheckoutStepViewedEvent.java | 3 +- .../ecomm/events/OrderCancelledEvent.java | 3 +- .../ecomm/events/OrderCompletedEvent.java | 3 +- .../core/ecomm/events/OrderRefundedEvent.java | 3 +- .../core/ecomm/events/OrderUpdatedEvent.java | 3 +- .../ecomm/events/ProductAddedToCartEvent.java | 3 +- .../events/ProductAddedToWishListEvent.java | 3 +- .../ecomm/events/ProductClickedEvent.java | 3 +- .../events/ProductListFilteredEvent.java | 7 +- .../ecomm/events/ProductListViewedEvent.java | 3 +- .../ecomm/events/ProductRemovedEvent.java | 3 +- .../ProductRemovedFromWishListEvent.java | 3 +- .../core/ecomm/events/ProductSharedEvent.java | 3 +- .../core/ecomm/events/ProductViewedEvent.java | 3 +- .../ecomm/events/PromotionClickedEvent.java | 3 +- .../ecomm/events/PromotionViewedEvent.java | 3 +- .../WishListProductAddedToCartEvent.java | 3 +- .../android/sdk/core/gson/RudderGson.java | 85 ++++++- .../gson/gsonadapters/DoubleTypeAdapter.java | 35 +++ .../gson/gsonadapters/FloatTypeAdapter.java | 35 +++ .../RudderContextTypeAdapter.java | 7 +- .../gsonadapters/RudderTraitsTypeAdapter.java | 7 +- .../android/sdk/core/util/Utils.java | 26 ++- .../sdk/core/DBPersistentManagerTest.java | 5 +- .../android/sdk/core/EventRepositoryTest.java | 36 ++- .../android/sdk/core/RudderGsonTest.java | 216 +++++++++++++++++- .../core/RudderUserSessionManagerTest.java | 5 - .../android/sdk/core/SerializationTest.java | 5 - .../sdk/core/SourceConfigurationTest.java | 6 - .../consent/ConsentFilterHandlerTest.java | 1 - sample-kotlin/build.gradle | 1 - 47 files changed, 566 insertions(+), 210 deletions(-) create mode 100644 core/src/main/java/com/rudderstack/android/sdk/core/gson/gsonadapters/DoubleTypeAdapter.java create mode 100644 core/src/main/java/com/rudderstack/android/sdk/core/gson/gsonadapters/FloatTypeAdapter.java diff --git a/core/src/main/java/com/rudderstack/android/sdk/core/DBPersistentManager.java b/core/src/main/java/com/rudderstack/android/sdk/core/DBPersistentManager.java index 79015277e..248e10aef 100644 --- a/core/src/main/java/com/rudderstack/android/sdk/core/DBPersistentManager.java +++ b/core/src/main/java/com/rudderstack/android/sdk/core/DBPersistentManager.java @@ -62,7 +62,7 @@ class DBPersistentManager/* extends SQLiteOpenHelper*/ { private static final int STATUS_DEVICE_MODE_DONE = 0b01; private static final int STATUS_ALL_DONE = 0b11; private static final int STATUS_NEW = 0b00; - // This column purpose is to identify if an event is dumped to device mode destinations without transformations or not. + // This column purpose is to identify if an event is sent to device mode destinations without transformations or not. private static final String DM_PROCESSED_COL = "dm_processed"; // status value for DM_PROCESSED column private static final int DM_PROCESSED_PENDING = 0; diff --git a/core/src/main/java/com/rudderstack/android/sdk/core/EventInsertionCallback.java b/core/src/main/java/com/rudderstack/android/sdk/core/EventInsertionCallback.java index e59af8fe0..8e49be5d5 100644 --- a/core/src/main/java/com/rudderstack/android/sdk/core/EventInsertionCallback.java +++ b/core/src/main/java/com/rudderstack/android/sdk/core/EventInsertionCallback.java @@ -15,6 +15,6 @@ public EventInsertionCallback(RudderMessage message, RudderDeviceModeManager dev @Override public void onInsertion(Integer rowId) { - deviceModeManager.makeFactoryDump(message, rowId, false); + deviceModeManager.processMessage(message, rowId, false); } } diff --git a/core/src/main/java/com/rudderstack/android/sdk/core/EventRepository.java b/core/src/main/java/com/rudderstack/android/sdk/core/EventRepository.java index b69aa80a6..8d4ccc5bb 100644 --- a/core/src/main/java/com/rudderstack/android/sdk/core/EventRepository.java +++ b/core/src/main/java/com/rudderstack/android/sdk/core/EventRepository.java @@ -326,31 +326,35 @@ private void saveFlushConfig() { /* - * generic method for dumping all the events + * generic method for processing all the events * */ void processMessage(@NonNull RudderMessage message) { if (!isSDKEnabled) { incrementDiscardedCounter(1, Collections.singletonMap(LABEL_TYPE, ReportManager.LABEL_TYPE_SDK_DISABLED)); return; } - RudderLogger.logDebug(String.format(Locale.US, "EventRepository: dump: eventName: %s", message.getEventName())); + RudderLogger.logDebug(String.format(Locale.US, "EventRepository: processMessage: eventName: %s", message.getEventName())); applyRudderOptionsToMessageIntegrations(message); RudderMessage updatedMessage = updateMessageWithConsentedDestinations(message); userSessionManager.applySessionTracking(updatedMessage); String eventJson = getEventJsonString(updatedMessage); - RudderLogger.logVerbose(String.format(Locale.US, "EventRepository: dump: message: %s", eventJson)); + if (eventJson == null) { + RudderLogger.logError("EventRepository: processMessage: eventJson is null after serialization"); + return; + } if (isMessageJsonExceedingMaxSize(eventJson)) { incrementDiscardedCounter(1, Collections.singletonMap(LABEL_TYPE, ReportManager.LABEL_TYPE_MSG_SIZE_INVALID)); - RudderLogger.logError(String.format(Locale.US, "EventRepository: dump: Event size exceeds the maximum permitted event size(%d)", Utils.MAX_EVENT_SIZE)); + RudderLogger.logError(String.format(Locale.US, "EventRepository: processMessage: Event size exceeds the maximum permitted event size(%d)", Utils.MAX_EVENT_SIZE)); return; } + RudderLogger.logVerbose(String.format(Locale.US, "EventRepository: processMessage: message: %s", eventJson)); dbManager.saveEvent(eventJson, new EventInsertionCallback(message, deviceModeManager)); } - String getEventJsonString(RudderMessage updatedMessage) { - return RudderGson.getInstance().toJson(updatedMessage); + String getEventJsonString(RudderMessage message) { + return RudderGson.serialize(message); } private boolean isMessageJsonExceedingMaxSize(String eventJson) { diff --git a/core/src/main/java/com/rudderstack/android/sdk/core/RudderClient.java b/core/src/main/java/com/rudderstack/android/sdk/core/RudderClient.java index b12bd95e0..820ccc4eb 100644 --- a/core/src/main/java/com/rudderstack/android/sdk/core/RudderClient.java +++ b/core/src/main/java/com/rudderstack/android/sdk/core/RudderClient.java @@ -231,7 +231,7 @@ public void track(@NonNull RudderMessageBuilder builder) { */ public void track(@NonNull RudderMessage message) { message.setType(MessageType.TRACK); - dumpMessage(message); + processMessage(message); } @@ -293,7 +293,7 @@ public void screen(@NonNull RudderMessageBuilder builder) { */ public void screen(@NonNull RudderMessage message) { message.setType(MessageType.SCREEN); - dumpMessage(message); + processMessage(message); } @@ -364,7 +364,7 @@ public void screen(@NonNull String screenName, @Nullable RudderProperty property */ public void identify(@NonNull RudderMessage message) { message.setType(MessageType.IDENTIFY); - dumpMessage(message); + processMessage(message); } @@ -455,7 +455,7 @@ public void alias(@NonNull RudderMessageBuilder builder) { */ void alias(@NonNull RudderMessage message) { message.setType(MessageType.ALIAS); - dumpMessage(message); + processMessage(message); } @@ -531,11 +531,11 @@ public void group(@NonNull RudderMessageBuilder builder) { @Deprecated public void group(@NonNull RudderMessage message) { message.setType(MessageType.GROUP); - dumpMessage(message); + processMessage(message); } - private void dumpMessage(@NonNull RudderMessage message) { + private void processMessage(@NonNull RudderMessage message) { if (getOptOutStatus()) { incrementDiscardedCounter(1, Collections.singletonMap(ReportManager.LABEL_TYPE, ReportManager.LABEL_TYPE_OPT_OUT)); diff --git a/core/src/main/java/com/rudderstack/android/sdk/core/RudderCloudModeManager.java b/core/src/main/java/com/rudderstack/android/sdk/core/RudderCloudModeManager.java index b44ae1808..d0d19b8e0 100644 --- a/core/src/main/java/com/rudderstack/android/sdk/core/RudderCloudModeManager.java +++ b/core/src/main/java/com/rudderstack/android/sdk/core/RudderCloudModeManager.java @@ -104,8 +104,8 @@ public void run() { private void deleteEventsWithoutAnonymousId(ArrayList messages, ArrayList messageIds) { List eventsToDelete = new ArrayList<>(); for (int i = 0; i < messages.size(); i++) { - Map message = RudderGson.getInstance().fromJson(messages.get(i), Map.class); - if (!message.containsKey("anonymousId") || message.get("anonymousId") == null) { + Map message = RudderGson.deserialize(messages.get(i), Map.class); + if (message != null && (!message.containsKey("anonymousId") || message.get("anonymousId") == null)) { eventsToDelete.add(messageIds.get(i)); } } diff --git a/core/src/main/java/com/rudderstack/android/sdk/core/RudderContext.java b/core/src/main/java/com/rudderstack/android/sdk/core/RudderContext.java index c84f7db17..b134d421c 100644 --- a/core/src/main/java/com/rudderstack/android/sdk/core/RudderContext.java +++ b/core/src/main/java/com/rudderstack/android/sdk/core/RudderContext.java @@ -86,7 +86,7 @@ public class RudderContext { RudderLogger.logDebug(String.format(Locale.US, "Traits from persistence storage%s", traitsJson)); if (traitsJson == null) { RudderTraits traits = new RudderTraits(anonymousId); - this.traits = Utils.convertToMap(RudderGson.getInstance().toJson(traits)); + this.traits = Utils.convertToMap(traits); this.persistTraits(); RudderLogger.logDebug("New traits has been saved"); } else { @@ -119,7 +119,7 @@ void resetTraits() { RudderTraits traits = new RudderTraits(); // convert the whole traits to map and take care of the extras synchronized (this) { - this.traits = Utils.convertToMap(RudderGson.getInstance().toJson(traits)); + this.traits = Utils.convertToMap(traits); } } @@ -130,7 +130,7 @@ void updateTraits(RudderTraits traits) { } // convert the whole traits to map and take care of the extras - Map traitsMap = Utils.convertToMap(RudderGson.getInstance().toJson(traits)); + Map traitsMap = Utils.convertToMap(traits); String existingId = (String) this.traits.get("id"); String newId = (String) traitsMap.get("id"); @@ -161,7 +161,7 @@ void persistTraits() { if (RudderClient.getApplication() != null) { RudderPreferenceManager preferenceManger = RudderPreferenceManager.getInstance(RudderClient.getApplication()); synchronized (this) { - preferenceManger.saveTraits(RudderGson.getInstance().toJson(this.traits)); + preferenceManger.saveTraits(RudderGson.serialize(this.traits)); } } } catch (NullPointerException ex) { @@ -343,7 +343,7 @@ void persistExternalIds() { try { if (RudderClient.getApplication() != null) { RudderPreferenceManager preferenceManger = RudderPreferenceManager.getInstance(RudderClient.getApplication()); - preferenceManger.saveExternalIds(RudderGson.getInstance().toJson(this.externalIds)); + preferenceManger.saveExternalIds(RudderGson.serialize(this.externalIds)); } } catch (NullPointerException ex) { ReportManager.reportError(ex); diff --git a/core/src/main/java/com/rudderstack/android/sdk/core/RudderDeviceModeManager.java b/core/src/main/java/com/rudderstack/android/sdk/core/RudderDeviceModeManager.java index 2cdc5a828..668623323 100644 --- a/core/src/main/java/com/rudderstack/android/sdk/core/RudderDeviceModeManager.java +++ b/core/src/main/java/com/rudderstack/android/sdk/core/RudderDeviceModeManager.java @@ -97,6 +97,7 @@ private List getConsentedDestinations(RudderServerConfi return consentedDestinations; } + private static void collectDissentedMetrics(List destinations, List consentedDestinations) { List destinationsCopy = new ArrayList<>(destinations); destinationsCopy.removeAll(consentedDestinations); @@ -105,8 +106,9 @@ private static void collectDissentedMetrics(List destin } } + private static void reportDiscardedDestinationWithType(String destinationName, String type) { - Map labelsMap = new HashMap<>(); + Map labelsMap = new HashMap<>(); labelsMap.put(ReportManager.LABEL_TYPE, type); labelsMap.put(ReportManager.LABEL_INTEGRATION, destinationName); ReportManager.incrementDeviceModeDiscardedCounter(1, labelsMap); @@ -193,7 +195,7 @@ private void initiateFactories(List destinations) { integrationOperationsMap.put(key, nativeOp); handleCallBacks(key, nativeOp); } else { - reportDiscardedDestinationWithType(destination == null? key : destination.destinationDefinition.displayName + reportDiscardedDestinationWithType(destination == null ? key : destination.destinationDefinition.displayName , ReportManager.LABEL_TYPE_DESTINATION_DISABLED); RudderLogger.logDebug(String.format(Locale.US, "EventRepository: initiateFactories: destination was null or not enabled for %s", key)); } @@ -212,25 +214,24 @@ private void isDeviceModeFactoriesNotPresent() { /** * Let's consider the conditions. * 1. Only cloud mode, no device mode - * a. before initialization - * Events are stored with status new in database. - * b. after initialization - * Since areDeviceModeFactoriesAbsent is true, replayMessageQueue is called, device mode events are - * dumped appropriately and marked as device_mode_done + * a. before initialization + * Events are stored with status new in database. + * b. after initialization + * Since areDeviceModeFactoriesAbsent is true, replayMessageQueue is called, device mode events are + * sent appropriately and marked as device_mode_done * 2. Device Modes are present. - * a. With transformations - * i. before initialization - * As usual saved in db. - * ii. after initialization - * replayMessageQueue is called which fetches events are polled from db, mark them as dm_processed status done, - * and send it to eligible device mode destinations without transformation - * b. Without transformations - * i. before initialization - * Saved to DB - * ii. after initialization - * replay is called and all events are sent over to destinations, also marked as - * device_mode_done - * + * a. With transformations + * i. before initialization + * As usual saved in db. + * ii. after initialization + * replayMessageQueue is called which fetches events are polled from db, mark them as dm_processed status done, + * and send it to eligible device mode destinations without transformation + * b. Without transformations + * i. before initialization + * Saved to DB + * ii. after initialization + * replay is called and all events are sent over to destinations, also marked as + * device_mode_done */ private void replayMessageQueue() { List messageIds = new ArrayList<>(); @@ -242,30 +243,33 @@ private void replayMessageQueue() { RudderLogger.logDebug(String.format(Locale.US, "RudderDeviceModeManager: replayMessageQueue: replaying old messages with factories. Count: %d", messageIds.size())); for (int i = 0; i < messageIds.size(); i++) { try { - RudderMessage message = RudderGson.getInstance().fromJson(messages.get(i), RudderMessage.class); - makeFactoryDump(message, messageIds.get(i), true); + RudderMessage message = RudderGson.deserialize(messages.get(i), RudderMessage.class); + if (message != null) { + processMessage(message, messageIds.get(i), true); + } } catch (Exception e) { ReportManager.reportError(e); - RudderLogger.logError(String.format(Locale.US, "RudderDeviceModeManager: replayMessageQueue: Exception in dumping message %s due to %s", messages.get(i), e.getMessage())); + RudderLogger.logError(String.format(Locale.US, "RudderDeviceModeManager: replayMessageQueue: Exception in replaying message %s due to %s", messages.get(i), e.getMessage())); } } } while (dbPersistentManager.getDeviceModeWithProcessedPendingEventsRecordCount() > 0); } - void makeFactoryDump(RudderMessage message, Integer rowId, boolean fromHistory) { + void processMessage(RudderMessage message, Integer rowId, boolean fromHistory) { synchronized (this) { if (this.areDeviceModeFactoriesAbsent) { markDeviceModeTransformationDone(rowId); } else if (areFactoriesInitialized || fromHistory) { List eligibleDestinations = getEligibleDestinations(message); updateMessageStatusBasedOnTransformations(eligibleDestinations, rowId, message); - dumpMessageToDestinationWithoutTransformation(eligibleDestinations, message); + processMessageToDestinationWithoutTransformation(eligibleDestinations, message); } } } /** * This mark the message as device_mode_done and dm_processed_done in the database + * * @param rowId The rowId of the message */ private void markDeviceModeTransformationDone(int rowId) { @@ -286,46 +290,48 @@ private void updateMessageStatusBasedOnTransformations(List eligibleDest } } - private void dumpMessageToDestinationWithoutTransformation(List eligibleDestinations, RudderMessage message) { + private void processMessageToDestinationWithoutTransformation(List eligibleDestinations, RudderMessage message) { List destinationsWithoutTransformations = getDestinationsWithTransformationStatus(TRANSFORMATION_STATUS.DISABLED, eligibleDestinations); - dumpEventToDestinations(message, destinationsWithoutTransformations, "makeFactoryDump"); + sendEventToDestinations(message, destinationsWithoutTransformations, "processMessage"); } /** - * @param message The message object which should be dumped to the supplied list of device mode destinations. - * @param destinations The List of Device Mode Destinations to which this message should be dumped + * @param message The message object which should be sent to the supplied list of device mode destinations. + * @param destinations The List of Device Mode Destinations to which this message should be sent * @param logTag name of the calling method, which is supposed to be printed in the logs, as this method is utilized by multiple methods */ - void dumpEventToDestinations(RudderMessage message, List destinations, String logTag) { + void sendEventToDestinations(RudderMessage message, List destinations, String logTag) { for (String destinationName : destinations) { RudderIntegration integration = integrationOperationsMap.get(destinationName); if (integration != null) { try { - RudderLogger.logDebug(String.format(Locale.US, "RudderDeviceModeManager: %s: dumping event %s for %s", logTag, message.getEventName(), destinationName)); - RudderLogger.logVerbose(String.format(Locale.US, "RudderDeviceModeManager: Dumping: %s", RudderGson.getInstance().toJson(message))); + RudderLogger.logDebug(String.format(Locale.US, "RudderDeviceModeManager: %s: sending event %s for %s", logTag, message.getEventName(), destinationName)); + RudderLogger.logVerbose(String.format(Locale.US, "RudderDeviceModeManager: sending: %s", RudderGson.serialize(message))); addDeviceModeCounter(message.getType(), destinationName); integration.dump(message); } catch (Exception e) { ReportManager.reportError(e); - RudderLogger.logError(String.format(Locale.US, "RudderDeviceModeManager: %s: Exception in dumping message %s to %s factory %s", logTag, message.getEventName(), destinationName, e.getMessage())); + RudderLogger.logError(String.format(Locale.US, "RudderDeviceModeManager: %s: Exception in sending message %s to %s factory %s", logTag, message.getEventName(), destinationName, e.getMessage())); } } } } + private void addDeviceModeCounter(String type, String destinationName) { Map labelMap = new HashMap<>(); labelMap.put(ReportManager.LABEL_TYPE, type); labelMap.put(ReportManager.LABEL_INTEGRATION, destinationName); incrementDeviceModeEventCounter(1, labelMap); } - void dumpOriginalEvents(TransformationRequest transformationRequest, boolean onTransformationError) { + + void sendOriginalEvents(TransformationRequest transformationRequest, boolean onTransformationError) { if (transformationRequest.batch != null) { - RudderLogger.logDebug(String.format(Locale.US, "RudderDeviceModeManager: dumpOriginalEvents: dumping back the original events to the transformations enabled destinations as there is transformation error.")); + RudderLogger.logDebug("RudderDeviceModeManager: sendOriginalEvents: sending back the original events to the transformations enabled destinations as there is transformation error."); for (TransformationRequestEvent transformationRequestEvent : transformationRequest.batch) { if (transformationRequestEvent != null && transformationRequestEvent.event != null) { List destinationsWithTransformationsEnabled = getDestinationNameForIds(transformationRequestEvent.destinationIds); List destinations = onTransformationError ? getDestinationsAcceptingEventsOnTransformationError(destinationsWithTransformationsEnabled) : destinationsWithTransformationsEnabled; - dumpEventToDestinations(transformationRequestEvent.event, destinations, "dumpOriginalEvents"); + sendEventToDestinations(transformationRequestEvent.event, destinations, "sendOriginalEvents"); } } } @@ -352,7 +358,7 @@ private List getDestinationsAcceptingEventsOnTransformationError(List transformedEvents = transformedDestination.payload; sortTransformedEventBasedOnOrderNo(transformedEvents); - sendEventsToTransformedDestinations(transformedDestination, destinationName); + sendTransformedEventsToDestination(transformedDestination, destinationName); } } - private void sendEventsToTransformedDestinations(TransformedDestination transformedDestination, String destinationName) { + private void sendTransformedEventsToDestination(TransformedDestination transformedDestination, String destinationName) { if (transformedDestination.payload == null || transformedDestination.payload.isEmpty()) return; for (TransformedEvent transformedEvent : transformedDestination.payload) { @@ -375,7 +381,7 @@ private void sendEventsToTransformedDestinations(TransformedDestination transfor boolean onTransformationError = !transformedEvent.status.equals("200"); if (onTransformationError) { StringBuilder errorMsg = new StringBuilder(); - errorMsg.append("RudderDeviceModeManager: dumpTransformedEvents: "); + errorMsg.append("RudderDeviceModeManager: sendTransformedEventsToDestination: "); if (transformedEvent.status.equals("410")) { errorMsg.append("The requested transformation is not available on the destination or there is a configuration issue. "); } else { @@ -394,10 +400,10 @@ private void sendEventsToTransformedDestinations(TransformedDestination transfor } } else if (message == null) { // If there is no transformation error and message is null then it means that the event is dropped in the transformation. - RudderLogger.logDebug(String.format(Locale.US, "RudderDeviceModeManager: dumpTransformedEvents: event is dropped in the transformation for %s", destinationName)); + RudderLogger.logDebug(String.format(Locale.US, "RudderDeviceModeManager: sendTransformedEventsToDestination: event is dropped in the transformation for %s", destinationName)); continue; } - dumpEventToDestinations(message, Collections.singletonList(destinationName), "dumpTransformedEvents"); + sendEventToDestinations(message, Collections.singletonList(destinationName), "sendTransformedEventsToDestination"); } } diff --git a/core/src/main/java/com/rudderstack/android/sdk/core/RudderDeviceModeTransformationManager.java b/core/src/main/java/com/rudderstack/android/sdk/core/RudderDeviceModeTransformationManager.java index 4df66d7ec..bd6116b75 100644 --- a/core/src/main/java/com/rudderstack/android/sdk/core/RudderDeviceModeTransformationManager.java +++ b/core/src/main/java/com/rudderstack/android/sdk/core/RudderDeviceModeTransformationManager.java @@ -69,8 +69,11 @@ public void run() { } createMessageIdTransformationRequestMap(); TransformationRequest transformationRequest = createTransformationRequestPayload(); - String requestJson = RudderGson.getInstance().toJson(transformationRequest); - + String requestJson = RudderGson.serialize(transformationRequest); + if (requestJson == null) { + RudderLogger.logError("DeviceModeTransformationManager: TransformationProcessor: Error in creating transformation request payload"); + break; + } RudderLogger.logDebug(String.format(Locale.US, "DeviceModeTransformationManager: TransformationProcessor: Payload: %s", requestJson)); RudderLogger.logInfo(String.format(Locale.US, "DeviceModeTransformationManager: TransformationProcessor: EventCount: %d", messageIds.size())); @@ -92,8 +95,11 @@ public void run() { private void createMessageIdTransformationRequestMap() { for (int i = 0; i < messageIds.size(); i++) { - - RudderMessage message = RudderGson.getInstance().fromJson(messages.get(i), RudderMessage.class); + RudderMessage message = RudderGson.deserialize(messages.get(i), RudderMessage.class); + if (message == null) { + RudderLogger.logError("DeviceModeTransformationManager: createMessageIdTransformationRequestMap: Error in deserializing message"); + continue; + } reportMessageSubmittedMetric(message); messageIdTransformationRequestMap.put(messageIds.get(i), message); } @@ -114,11 +120,11 @@ private boolean handleTransformationResponse(Result result, TransformationReques return true; } else if (result.status == NetworkResponses.BAD_REQUEST) { reportBadRequestMetric(); - RudderLogger.logDebug("DeviceModeTransformationManager: TransformationProcessor: Bad request, dumping back the original events to the factories"); - dumpOriginalEvents(transformationRequest); + RudderLogger.logDebug("DeviceModeTransformationManager: TransformationProcessor: Bad request, sending back the original events to the factories"); + sendOriginalEvents(transformationRequest); } else if (result.status == NetworkResponses.ERROR) { handleError(transformationRequest); - } else if (result.status == NetworkResponses.RESOURCE_NOT_FOUND) { // dumping back the original messages itself to the factories as transformation feature is not enabled + } else if (result.status == NetworkResponses.RESOURCE_NOT_FOUND) { // sending back the original messages itself to the factories as transformation feature is not enabled reportResourceNotFoundMetric(); handleResourceNotFound(transformationRequest); } else { @@ -147,7 +153,7 @@ private void handleError(TransformationRequest transformationRequest) { if (retryCount++ == MAX_RETRIES) { retryCount = 0; reportMaxRetryExceededMetric(); - dumpOriginalEvents(transformationRequest); + sendOriginalEvents(transformationRequest); } else { incrementRetryCountMetric(); RudderLogger.logDebug("DeviceModeTransformationManager: TransformationProcessor: Retrying in " + delay + "s"); @@ -170,24 +176,28 @@ private void incrementRetryCountMetric() { ReportManager.incrementDMTRetryCounter(1); } - private void dumpOriginalEvents(TransformationRequest transformationRequest) { + private void sendOriginalEvents(TransformationRequest transformationRequest) { deviceModeSleepCount = 0; - rudderDeviceModeManager.dumpOriginalEvents(transformationRequest, true); + rudderDeviceModeManager.sendOriginalEvents(transformationRequest, true); completeDeviceModeEventProcessing(); } private void handleResourceNotFound(TransformationRequest transformationRequest) { deviceModeSleepCount = 0; - rudderDeviceModeManager.dumpOriginalEvents(transformationRequest, false); + rudderDeviceModeManager.sendOriginalEvents(transformationRequest, false); completeDeviceModeEventProcessing(); } private void handleSuccess(Result result) { deviceModeSleepCount = 0; try { - TransformationResponse transformationResponse = RudderGson.getInstance().fromJson(result.response, TransformationResponse.class); + TransformationResponse transformationResponse = RudderGson.deserialize(result.response, TransformationResponse.class); + if (transformationResponse == null) { + RudderLogger.logError("DeviceModeTransformationManager: handleSuccess: Error in deserializing transformation response"); + return; + } incrementDmtSuccessMetric(transformationResponse); - rudderDeviceModeManager.dumpTransformedEvents(transformationResponse); + rudderDeviceModeManager.sendTransformedEvents(transformationResponse); completeDeviceModeEventProcessing(); } catch (Exception e) { ReportManager.reportError(e); @@ -196,16 +206,16 @@ private void handleSuccess(Result result) { } private void incrementDmtSuccessMetric(TransformationResponse transformationResponse) { - if(transformationResponse == null || transformationResponse.transformedBatch == null) { + if (transformationResponse == null || transformationResponse.transformedBatch == null) { return; } for (TransformationResponse.TransformedDestination transformedDestination : transformationResponse.transformedBatch) { - if(transformedDestination.payload == null) { - continue; - } - for (TransformationResponse.TransformedEvent transformedEvent: - transformedDestination.payload) { - if(transformedEvent.event == null) { + if (transformedDestination.payload == null) { + continue; + } + for (TransformationResponse.TransformedEvent transformedEvent : + transformedDestination.payload) { + if (transformedEvent.event == null) { continue; } ReportManager.incrementDMTEventSuccessResponseCounter(1, diff --git a/core/src/main/java/com/rudderstack/android/sdk/core/RudderMessage.java b/core/src/main/java/com/rudderstack/android/sdk/core/RudderMessage.java index d4833a1fb..6ce27d27e 100644 --- a/core/src/main/java/com/rudderstack/android/sdk/core/RudderMessage.java +++ b/core/src/main/java/com/rudderstack/android/sdk/core/RudderMessage.java @@ -264,6 +264,10 @@ void setSession(RudderUserSession userSession) { this.context.setSession(userSession); } + @VisibleForTesting(otherwise = VisibleForTesting.NONE) + void setContext(RudderContext context) { + this.context = context; + } @VisibleForTesting String getMessageId () { return this.messageId; diff --git a/core/src/main/java/com/rudderstack/android/sdk/core/RudderPreferenceManager.java b/core/src/main/java/com/rudderstack/android/sdk/core/RudderPreferenceManager.java index 073a948cd..bda72ea20 100644 --- a/core/src/main/java/com/rudderstack/android/sdk/core/RudderPreferenceManager.java +++ b/core/src/main/java/com/rudderstack/android/sdk/core/RudderPreferenceManager.java @@ -119,7 +119,7 @@ void clearCurrentAnonymousIdValue() { if (traits != null) { Map traitsMap = Utils.convertToMap(traits); traitsMap.remove("anonymousId"); - saveTraits(RudderGson.getInstance().toJson(traitsMap)); + saveTraits(RudderGson.serialize(traitsMap)); } } diff --git a/core/src/main/java/com/rudderstack/android/sdk/core/RudderServerConfigManager.java b/core/src/main/java/com/rudderstack/android/sdk/core/RudderServerConfigManager.java index 57cce35dd..9b8afdba3 100644 --- a/core/src/main/java/com/rudderstack/android/sdk/core/RudderServerConfigManager.java +++ b/core/src/main/java/com/rudderstack/android/sdk/core/RudderServerConfigManager.java @@ -56,6 +56,7 @@ private void fetchConfig() { lock.unlock(); }); } + void getFetchedConfig(FetchedConfigCallback callback) { executorService.submit(() -> { lock.lock(); @@ -75,7 +76,10 @@ private void downloadConfig() { Result result = networkManager.sendNetworkRequest(null, requestUrl, RequestMethod.GET, false); if (result.status == NetworkResponses.SUCCESS) { try { - RudderServerConfig rudderServerConfig = RudderGson.getInstance().fromJson(result.response, RudderServerConfig.class); + RudderServerConfig rudderServerConfig = RudderGson.deserialize(result.response, RudderServerConfig.class); + if (rudderServerConfig == null) { + throw new NullPointerException("RudderServerConfig is null"); + } RudderLogger.logDebug(String.format(Locale.US, "RudderServerConfigManager: downloadConfig: configJson: %s", result.response)); // save config for future use preferenceManger.updateLastUpdatedTime(); @@ -109,8 +113,8 @@ private void downloadConfig() { } void saveRudderServerConfig(RudderServerConfig rudderServerConfig) { - try(FileOutputStream fos = context.openFileOutput(RUDDER_SERVER_CONFIG_FILE_NAME, Context.MODE_PRIVATE); - ObjectOutputStream os = new ObjectOutputStream(fos)) { + try (FileOutputStream fos = context.openFileOutput(RUDDER_SERVER_CONFIG_FILE_NAME, Context.MODE_PRIVATE); + ObjectOutputStream os = new ObjectOutputStream(fos)) { os.writeObject(rudderServerConfig); } catch (Exception e) { @@ -125,16 +129,15 @@ private RudderServerConfig getFetchedRudderServerConfig() { if (!Utils.fileExists(context, RUDDER_SERVER_CONFIG_FILE_NAME)) { return null; } - try(FileInputStream fis = context.openFileInput(RUDDER_SERVER_CONFIG_FILE_NAME); - ObjectInputStream is = new ObjectInputStream(fis)) { - - rudderServerConfig = (RudderServerConfig) is.readObject(); - } catch (Exception e) { - //will cause too many exceptions -// ReportManager.reportError(e); - RudderLogger.logError("RudderServerConfigManager: getRudderServerConfig: Failed to read RudderServerConfig Object from File"); - e.printStackTrace(); - } + try (FileInputStream fis = context.openFileInput(RUDDER_SERVER_CONFIG_FILE_NAME); + ObjectInputStream is = new ObjectInputStream(fis)) { + + rudderServerConfig = (RudderServerConfig) is.readObject(); + } catch (Exception e) { + //will cause too many exceptions + RudderLogger.logError("RudderServerConfigManager: getRudderServerConfig: Failed to read RudderServerConfig Object from File"); + e.printStackTrace(); + } return rudderServerConfig; } @@ -157,8 +160,10 @@ private void sleep(int retryCount) { } catch (InterruptedException ex) { ReportManager.reportError(ex); RudderLogger.logError(String.format(Locale.US, "RudderServerConfigManager: Sleep: Exception while the thread is in sleep %s", ex.getLocalizedMessage())); + Thread.currentThread().interrupt(); } } + interface FetchedConfigCallback { void onConfigFetched(RudderServerConfig config); } diff --git a/core/src/main/java/com/rudderstack/android/sdk/core/RudderTraits.java b/core/src/main/java/com/rudderstack/android/sdk/core/RudderTraits.java index 18d9e21f0..31911087a 100644 --- a/core/src/main/java/com/rudderstack/android/sdk/core/RudderTraits.java +++ b/core/src/main/java/com/rudderstack/android/sdk/core/RudderTraits.java @@ -84,7 +84,7 @@ public static String getAnonymousId(Map traitsMap) { */ public static String getAddress(Map traitsMap) { if (traitsMap != null & traitsMap.containsKey(ADDRESS_KEY)) - return RudderGson.getInstance().toJson(traitsMap.get(ADDRESS_KEY)); + return RudderGson.serialize(traitsMap.get(ADDRESS_KEY)); return null; } @@ -672,7 +672,7 @@ public Address putStreet(String street) { * @return address Address */ public static Address fromString(String address) { - return RudderGson.getInstance().fromJson(address, Address.class); + return RudderGson.deserialize(address, Address.class); } } diff --git a/core/src/main/java/com/rudderstack/android/sdk/core/TransformationResponseDeserializer.java b/core/src/main/java/com/rudderstack/android/sdk/core/TransformationResponseDeserializer.java index fb4fa42fa..f408522a4 100644 --- a/core/src/main/java/com/rudderstack/android/sdk/core/TransformationResponseDeserializer.java +++ b/core/src/main/java/com/rudderstack/android/sdk/core/TransformationResponseDeserializer.java @@ -13,6 +13,8 @@ import java.util.List; public class TransformationResponseDeserializer implements JsonDeserializer { + public static final String EVENT = "event"; + @Override public TransformationResponse deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { JsonObject jsonObject = json.getAsJsonObject(); @@ -31,19 +33,16 @@ public TransformationResponse deserialize(JsonElement json, Type typeOfT, JsonDe String status = payloadObject.get("status").getAsString(); RudderMessage message = null; - if (payloadObject.has("event") && !payloadObject.get("event").isJsonNull()) { - JsonObject eventObject = payloadObject.getAsJsonObject("event"); + if (payloadObject.has(EVENT) && !payloadObject.get(EVENT).isJsonNull()) { + JsonObject eventObject = payloadObject.getAsJsonObject(EVENT); if (eventObject.size() > 0) { - try { - message = RudderGson.getInstance().fromJson(eventObject, RudderMessage.class); - } catch (Exception e) { - ReportManager.reportError(e); - RudderLogger.logError(String.format("TransformationResponseDeserializer: Error while parsing event object for the destinationId: %s, and error: %s", id, e)); + message = RudderGson.deserialize(eventObject, RudderMessage.class); + if (message == null) { + RudderLogger.logError(String.format("TransformationResponseDeserializer: Error while parsing event object for the destinationId: %s", id)); continue; } } } - TransformationResponse.TransformedEvent payload = new TransformationResponse.TransformedEvent(orderNo, status, message); payloadList.add(payload); } diff --git a/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/CartViewedEvent.java b/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/CartViewedEvent.java index adf86ab4a..6e6764a8f 100644 --- a/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/CartViewedEvent.java +++ b/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/CartViewedEvent.java @@ -4,7 +4,6 @@ import com.rudderstack.android.sdk.core.ecomm.ECommerceCart; import com.rudderstack.android.sdk.core.ecomm.ECommerceEvents; import com.rudderstack.android.sdk.core.ecomm.ECommercePropertyBuilder; -import com.rudderstack.android.sdk.core.gson.RudderGson; import com.rudderstack.android.sdk.core.util.Utils; public class CartViewedEvent extends ECommercePropertyBuilder { @@ -28,7 +27,7 @@ public String event() { @Override public RudderProperty properties() { RudderProperty property = new RudderProperty(); - property.putValue(Utils.convertToMap(RudderGson.getInstance().toJson(this.cart))); + property.putValue(Utils.convertToMap(this.cart)); return property; } } diff --git a/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/CheckoutStartedEvent.java b/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/CheckoutStartedEvent.java index 3e460859f..081f3f50d 100644 --- a/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/CheckoutStartedEvent.java +++ b/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/CheckoutStartedEvent.java @@ -4,7 +4,6 @@ import com.rudderstack.android.sdk.core.ecomm.ECommerceEvents; import com.rudderstack.android.sdk.core.ecomm.ECommerceOrder; import com.rudderstack.android.sdk.core.ecomm.ECommercePropertyBuilder; -import com.rudderstack.android.sdk.core.gson.RudderGson; import com.rudderstack.android.sdk.core.util.Utils; public class CheckoutStartedEvent extends ECommercePropertyBuilder { @@ -23,7 +22,7 @@ public String event() { public RudderProperty properties() { RudderProperty property = new RudderProperty(); if (this.order != null) { - property.putValue(Utils.convertToMap(RudderGson.getInstance().toJson(this.order))); + property.putValue(Utils.convertToMap(this.order)); } return property; } diff --git a/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/CheckoutStepCompletedEvent.java b/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/CheckoutStepCompletedEvent.java index 390427946..9c71fe13f 100644 --- a/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/CheckoutStepCompletedEvent.java +++ b/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/CheckoutStepCompletedEvent.java @@ -4,7 +4,6 @@ import com.rudderstack.android.sdk.core.ecomm.ECommerceCheckout; import com.rudderstack.android.sdk.core.ecomm.ECommerceEvents; import com.rudderstack.android.sdk.core.ecomm.ECommercePropertyBuilder; -import com.rudderstack.android.sdk.core.gson.RudderGson; import com.rudderstack.android.sdk.core.util.Utils; public class CheckoutStepCompletedEvent extends ECommercePropertyBuilder { @@ -29,7 +28,7 @@ public String event() { public RudderProperty properties() { RudderProperty property = new RudderProperty(); if (this.checkout != null) { - property.putValue(Utils.convertToMap(RudderGson.getInstance().toJson(this.checkout))); + property.putValue(Utils.convertToMap(this.checkout)); } return property; } diff --git a/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/CheckoutStepViewedEvent.java b/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/CheckoutStepViewedEvent.java index 49e391121..e679bb989 100644 --- a/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/CheckoutStepViewedEvent.java +++ b/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/CheckoutStepViewedEvent.java @@ -4,7 +4,6 @@ import com.rudderstack.android.sdk.core.ecomm.ECommerceCheckout; import com.rudderstack.android.sdk.core.ecomm.ECommerceEvents; import com.rudderstack.android.sdk.core.ecomm.ECommercePropertyBuilder; -import com.rudderstack.android.sdk.core.gson.RudderGson; import com.rudderstack.android.sdk.core.util.Utils; public class CheckoutStepViewedEvent extends ECommercePropertyBuilder { @@ -29,7 +28,7 @@ public String event() { public RudderProperty properties() { RudderProperty property = new RudderProperty(); if (this.checkout != null) { - property.putValue(Utils.convertToMap(RudderGson.getInstance().toJson(this.checkout))); + property.putValue(Utils.convertToMap(this.checkout)); } return property; } diff --git a/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/OrderCancelledEvent.java b/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/OrderCancelledEvent.java index 07027f681..1689a8d66 100644 --- a/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/OrderCancelledEvent.java +++ b/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/OrderCancelledEvent.java @@ -4,7 +4,6 @@ import com.rudderstack.android.sdk.core.ecomm.ECommerceEvents; import com.rudderstack.android.sdk.core.ecomm.ECommerceOrder; import com.rudderstack.android.sdk.core.ecomm.ECommercePropertyBuilder; -import com.rudderstack.android.sdk.core.gson.RudderGson; import com.rudderstack.android.sdk.core.util.Utils; public class OrderCancelledEvent extends ECommercePropertyBuilder { @@ -29,7 +28,7 @@ public String event() { public RudderProperty properties() { RudderProperty property = new RudderProperty(); if (this.order != null) { - property.putValue(Utils.convertToMap(RudderGson.getInstance().toJson(this.order))); + property.putValue(Utils.convertToMap(this.order)); } return property; } diff --git a/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/OrderCompletedEvent.java b/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/OrderCompletedEvent.java index 09767e766..8889ea453 100644 --- a/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/OrderCompletedEvent.java +++ b/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/OrderCompletedEvent.java @@ -4,7 +4,6 @@ import com.rudderstack.android.sdk.core.ecomm.ECommerceEvents; import com.rudderstack.android.sdk.core.ecomm.ECommerceOrder; import com.rudderstack.android.sdk.core.ecomm.ECommercePropertyBuilder; -import com.rudderstack.android.sdk.core.gson.RudderGson; import com.rudderstack.android.sdk.core.util.Utils; public class OrderCompletedEvent extends ECommercePropertyBuilder { @@ -29,7 +28,7 @@ public String event() { public RudderProperty properties() { RudderProperty property = new RudderProperty(); if (this.order != null) - property.putValue(Utils.convertToMap(RudderGson.getInstance().toJson(this.order))); + property.putValue(Utils.convertToMap(this.order)); return property; } } diff --git a/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/OrderRefundedEvent.java b/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/OrderRefundedEvent.java index cf5516c8f..bc6f5562a 100644 --- a/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/OrderRefundedEvent.java +++ b/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/OrderRefundedEvent.java @@ -6,7 +6,6 @@ import com.rudderstack.android.sdk.core.ecomm.ECommerceParamNames; import com.rudderstack.android.sdk.core.ecomm.ECommerceProduct; import com.rudderstack.android.sdk.core.ecomm.ECommercePropertyBuilder; -import com.rudderstack.android.sdk.core.gson.RudderGson; import com.rudderstack.android.sdk.core.util.Utils; import java.util.ArrayList; @@ -69,7 +68,7 @@ public RudderProperty properties() { } } if (this.products != null && !this.products.isEmpty()) { - property.put(ECommerceParamNames.PRODUCTS, Utils.convertToList(RudderGson.getInstance().toJson(this.products))); + property.put(ECommerceParamNames.PRODUCTS, Utils.convertToList(this.products)); } return property; } diff --git a/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/OrderUpdatedEvent.java b/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/OrderUpdatedEvent.java index 64129cf60..01b73fb1f 100644 --- a/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/OrderUpdatedEvent.java +++ b/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/OrderUpdatedEvent.java @@ -4,7 +4,6 @@ import com.rudderstack.android.sdk.core.ecomm.ECommerceEvents; import com.rudderstack.android.sdk.core.ecomm.ECommerceOrder; import com.rudderstack.android.sdk.core.ecomm.ECommercePropertyBuilder; -import com.rudderstack.android.sdk.core.gson.RudderGson; import com.rudderstack.android.sdk.core.util.Utils; public class OrderUpdatedEvent extends ECommercePropertyBuilder { @@ -29,7 +28,7 @@ public String event() { public RudderProperty properties() { RudderProperty property = new RudderProperty(); if (this.order != null) { - property.putValue(Utils.convertToMap(RudderGson.getInstance().toJson(this.order))); + property.putValue(Utils.convertToMap(this.order)); } return property; } diff --git a/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/ProductAddedToCartEvent.java b/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/ProductAddedToCartEvent.java index 0e2d6646b..727fb5ec6 100644 --- a/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/ProductAddedToCartEvent.java +++ b/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/ProductAddedToCartEvent.java @@ -5,7 +5,6 @@ import com.rudderstack.android.sdk.core.ecomm.ECommerceParamNames; import com.rudderstack.android.sdk.core.ecomm.ECommerceProduct; import com.rudderstack.android.sdk.core.ecomm.ECommercePropertyBuilder; -import com.rudderstack.android.sdk.core.gson.RudderGson; import com.rudderstack.android.sdk.core.util.Utils; public class ProductAddedToCartEvent extends ECommercePropertyBuilder { @@ -37,7 +36,7 @@ public ProductAddedToCartEvent withProductBuilder(ECommerceProduct.Builder build public RudderProperty properties() { RudderProperty property = new RudderProperty(); if (this.product != null) { - property.putValue(Utils.convertToMap(RudderGson.getInstance().toJson(this.product))); + property.putValue(Utils.convertToMap(this.product)); } if (this.cartId != null) { property.put(ECommerceParamNames.CART_ID, this.cartId); diff --git a/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/ProductAddedToWishListEvent.java b/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/ProductAddedToWishListEvent.java index a8be58d63..c3f541d85 100644 --- a/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/ProductAddedToWishListEvent.java +++ b/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/ProductAddedToWishListEvent.java @@ -6,7 +6,6 @@ import com.rudderstack.android.sdk.core.ecomm.ECommerceProduct; import com.rudderstack.android.sdk.core.ecomm.ECommercePropertyBuilder; import com.rudderstack.android.sdk.core.ecomm.ECommerceWishList; -import com.rudderstack.android.sdk.core.gson.RudderGson; import com.rudderstack.android.sdk.core.util.Utils; public class ProductAddedToWishListEvent extends ECommercePropertyBuilder { @@ -42,7 +41,7 @@ public RudderProperty properties() { property.put(ECommerceParamNames.WISHLIST_NAME, this.wishList.getWishListName()); } if (this.product != null) { - property.putValue(Utils.convertToMap(RudderGson.getInstance().toJson(this.product))); + property.putValue(Utils.convertToMap(this.product)); } return property; } diff --git a/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/ProductClickedEvent.java b/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/ProductClickedEvent.java index ba1fe4484..c1d5ce420 100644 --- a/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/ProductClickedEvent.java +++ b/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/ProductClickedEvent.java @@ -4,7 +4,6 @@ import com.rudderstack.android.sdk.core.ecomm.ECommerceEvents; import com.rudderstack.android.sdk.core.ecomm.ECommerceProduct; import com.rudderstack.android.sdk.core.ecomm.ECommercePropertyBuilder; -import com.rudderstack.android.sdk.core.gson.RudderGson; import com.rudderstack.android.sdk.core.util.Utils; public class ProductClickedEvent extends ECommercePropertyBuilder { @@ -29,7 +28,7 @@ public String event() { public RudderProperty properties() { RudderProperty property = new RudderProperty(); if (this.product != null) { - property.putValue(Utils.convertToMap(RudderGson.getInstance().toJson(this.product))); + property.putValue(Utils.convertToMap(this.product)); } return property; } diff --git a/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/ProductListFilteredEvent.java b/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/ProductListFilteredEvent.java index 6a695f5ef..81c09e045 100644 --- a/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/ProductListFilteredEvent.java +++ b/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/ProductListFilteredEvent.java @@ -9,7 +9,6 @@ import com.rudderstack.android.sdk.core.ecomm.ECommerceProduct; import com.rudderstack.android.sdk.core.ecomm.ECommercePropertyBuilder; import com.rudderstack.android.sdk.core.ecomm.ECommerceSort; -import com.rudderstack.android.sdk.core.gson.RudderGson; import com.rudderstack.android.sdk.core.util.Utils; import java.util.ArrayList; @@ -172,13 +171,13 @@ public RudderProperty properties() { property.put(ECommerceParamNames.CATEGORY, this.category); } if (this.products != null && !this.products.isEmpty()) { - property.put(ECommerceParamNames.PRODUCTS, Utils.convertToList(RudderGson.getInstance().toJson(this.products))); + property.put(ECommerceParamNames.PRODUCTS, Utils.convertToList(this.products)); } if (this.sorts != null && !this.sorts.isEmpty()) { - property.put(ECommerceParamNames.SORTS, Utils.convertToList(RudderGson.getInstance().toJson(this.sorts))); + property.put(ECommerceParamNames.SORTS, Utils.convertToList(this.sorts)); } if (this.filters != null && !this.filters.isEmpty()) { - property.put(ECommerceParamNames.FILTERS, Utils.convertToList(RudderGson.getInstance().toJson(this.filters))); + property.put(ECommerceParamNames.FILTERS, Utils.convertToList(this.filters)); } return property; diff --git a/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/ProductListViewedEvent.java b/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/ProductListViewedEvent.java index f12176522..5041a2c6b 100644 --- a/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/ProductListViewedEvent.java +++ b/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/ProductListViewedEvent.java @@ -7,7 +7,6 @@ import com.rudderstack.android.sdk.core.ecomm.ECommerceParamNames; import com.rudderstack.android.sdk.core.ecomm.ECommerceProduct; import com.rudderstack.android.sdk.core.ecomm.ECommercePropertyBuilder; -import com.rudderstack.android.sdk.core.gson.RudderGson; import com.rudderstack.android.sdk.core.util.Utils; import java.util.ArrayList; @@ -70,7 +69,7 @@ public RudderProperty properties() { property.put(ECommerceParamNames.CATEGORY, this.category); } if (this.products != null && !this.products.isEmpty()) { - property.put(ECommerceParamNames.PRODUCTS, Utils.convertToList(RudderGson.getInstance().toJson(this.products))); + property.put(ECommerceParamNames.PRODUCTS, Utils.convertToList(this.products)); } return property; } diff --git a/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/ProductRemovedEvent.java b/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/ProductRemovedEvent.java index c37e967f9..66ddd1321 100644 --- a/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/ProductRemovedEvent.java +++ b/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/ProductRemovedEvent.java @@ -4,7 +4,6 @@ import com.rudderstack.android.sdk.core.ecomm.ECommerceEvents; import com.rudderstack.android.sdk.core.ecomm.ECommerceProduct; import com.rudderstack.android.sdk.core.ecomm.ECommercePropertyBuilder; -import com.rudderstack.android.sdk.core.gson.RudderGson; import com.rudderstack.android.sdk.core.util.Utils; public class ProductRemovedEvent extends ECommercePropertyBuilder { @@ -29,7 +28,7 @@ public String event() { public RudderProperty properties() { RudderProperty property = new RudderProperty(); if (this.product != null) { - property.putValue(Utils.convertToMap(RudderGson.getInstance().toJson(this.product))); + property.putValue(Utils.convertToMap(this.product)); } return property; } diff --git a/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/ProductRemovedFromWishListEvent.java b/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/ProductRemovedFromWishListEvent.java index 992cb5429..88a848837 100644 --- a/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/ProductRemovedFromWishListEvent.java +++ b/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/ProductRemovedFromWishListEvent.java @@ -6,7 +6,6 @@ import com.rudderstack.android.sdk.core.ecomm.ECommerceProduct; import com.rudderstack.android.sdk.core.ecomm.ECommercePropertyBuilder; import com.rudderstack.android.sdk.core.ecomm.ECommerceWishList; -import com.rudderstack.android.sdk.core.gson.RudderGson; import com.rudderstack.android.sdk.core.util.Utils; public class ProductRemovedFromWishListEvent extends ECommercePropertyBuilder { @@ -42,7 +41,7 @@ public RudderProperty properties() { property.put(ECommerceParamNames.WISHLIST_NAME, this.wishList.getWishListName()); } if (this.product != null) { - property.putValue(Utils.convertToMap(RudderGson.getInstance().toJson(this.product))); + property.putValue(Utils.convertToMap(this.product)); } return property; } diff --git a/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/ProductSharedEvent.java b/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/ProductSharedEvent.java index f358daa1e..b37f8bfcd 100644 --- a/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/ProductSharedEvent.java +++ b/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/ProductSharedEvent.java @@ -7,7 +7,6 @@ import com.rudderstack.android.sdk.core.ecomm.ECommerceParamNames; import com.rudderstack.android.sdk.core.ecomm.ECommerceProduct; import com.rudderstack.android.sdk.core.ecomm.ECommercePropertyBuilder; -import com.rudderstack.android.sdk.core.gson.RudderGson; import com.rudderstack.android.sdk.core.util.Utils; public class ProductSharedEvent extends ECommercePropertyBuilder { @@ -53,7 +52,7 @@ public String event() { public RudderProperty properties() { RudderProperty property = new RudderProperty(); if (this.product != null) { - property.putValue(Utils.convertToMap(RudderGson.getInstance().toJson(this.product))); + property.putValue(Utils.convertToMap(this.product)); } if (!TextUtils.isEmpty(this.socialChannel)) { property.put(ECommerceParamNames.SHARE_VIA, this.socialChannel); diff --git a/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/ProductViewedEvent.java b/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/ProductViewedEvent.java index c7a9bf323..37e5e09af 100644 --- a/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/ProductViewedEvent.java +++ b/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/ProductViewedEvent.java @@ -4,7 +4,6 @@ import com.rudderstack.android.sdk.core.ecomm.ECommerceEvents; import com.rudderstack.android.sdk.core.ecomm.ECommerceProduct; import com.rudderstack.android.sdk.core.ecomm.ECommercePropertyBuilder; -import com.rudderstack.android.sdk.core.gson.RudderGson; import com.rudderstack.android.sdk.core.util.Utils; public class ProductViewedEvent extends ECommercePropertyBuilder { @@ -29,7 +28,7 @@ public String event() { public RudderProperty properties() { RudderProperty property = new RudderProperty(); if (this.product != null) { - property.putValue(Utils.convertToMap(RudderGson.getInstance().toJson(this.product))); + property.putValue(Utils.convertToMap(this.product)); } return property; } diff --git a/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/PromotionClickedEvent.java b/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/PromotionClickedEvent.java index ab794f9c4..891d2d745 100644 --- a/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/PromotionClickedEvent.java +++ b/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/PromotionClickedEvent.java @@ -4,7 +4,6 @@ import com.rudderstack.android.sdk.core.ecomm.ECommerceEvents; import com.rudderstack.android.sdk.core.ecomm.ECommercePromotion; import com.rudderstack.android.sdk.core.ecomm.ECommercePropertyBuilder; -import com.rudderstack.android.sdk.core.gson.RudderGson; import com.rudderstack.android.sdk.core.util.Utils; public class PromotionClickedEvent extends ECommercePropertyBuilder { @@ -29,7 +28,7 @@ public String event() { public RudderProperty properties() { RudderProperty property = new RudderProperty(); if (this.promotion != null) { - property.putValue(Utils.convertToMap(RudderGson.getInstance().toJson(this.promotion))); + property.putValue(Utils.convertToMap(this.promotion)); } return property; } diff --git a/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/PromotionViewedEvent.java b/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/PromotionViewedEvent.java index af4158e42..bdbe56c2f 100644 --- a/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/PromotionViewedEvent.java +++ b/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/PromotionViewedEvent.java @@ -4,7 +4,6 @@ import com.rudderstack.android.sdk.core.ecomm.ECommerceEvents; import com.rudderstack.android.sdk.core.ecomm.ECommercePromotion; import com.rudderstack.android.sdk.core.ecomm.ECommercePropertyBuilder; -import com.rudderstack.android.sdk.core.gson.RudderGson; import com.rudderstack.android.sdk.core.util.Utils; public class PromotionViewedEvent extends ECommercePropertyBuilder { @@ -30,7 +29,7 @@ public String event() { public RudderProperty properties() { RudderProperty property = new RudderProperty(); if (this.promotion != null) { - property.putValue(Utils.convertToMap(RudderGson.getInstance().toJson(this.promotion))); + property.putValue(Utils.convertToMap(this.promotion)); } return property; } diff --git a/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/WishListProductAddedToCartEvent.java b/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/WishListProductAddedToCartEvent.java index cc71171eb..5f0762606 100644 --- a/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/WishListProductAddedToCartEvent.java +++ b/core/src/main/java/com/rudderstack/android/sdk/core/ecomm/events/WishListProductAddedToCartEvent.java @@ -9,7 +9,6 @@ import com.rudderstack.android.sdk.core.ecomm.ECommerceProduct; import com.rudderstack.android.sdk.core.ecomm.ECommercePropertyBuilder; import com.rudderstack.android.sdk.core.ecomm.ECommerceWishList; -import com.rudderstack.android.sdk.core.gson.RudderGson; import com.rudderstack.android.sdk.core.util.Utils; public class WishListProductAddedToCartEvent extends ECommercePropertyBuilder { @@ -64,7 +63,7 @@ public RudderProperty properties() { property.put(ECommerceParamNames.WISHLIST_NAME, this.wishList.getWishListName()); } if (this.product != null) { - property.putValue(Utils.convertToMap(RudderGson.getInstance().toJson(this.product))); + property.putValue(Utils.convertToMap(this.product)); } if (!TextUtils.isEmpty(this.cartId)) { property.put(ECommerceParamNames.CART_ID, this.cartId); diff --git a/core/src/main/java/com/rudderstack/android/sdk/core/gson/RudderGson.java b/core/src/main/java/com/rudderstack/android/sdk/core/gson/RudderGson.java index 35fc4c2fe..5934fb6a7 100644 --- a/core/src/main/java/com/rudderstack/android/sdk/core/gson/RudderGson.java +++ b/core/src/main/java/com/rudderstack/android/sdk/core/gson/RudderGson.java @@ -1,9 +1,17 @@ package com.rudderstack.android.sdk.core.gson; +import androidx.annotation.Nullable; + import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.internal.bind.TypeAdapters; +import com.rudderstack.android.sdk.core.ReportManager; import com.rudderstack.android.sdk.core.RudderContext; +import com.rudderstack.android.sdk.core.RudderLogger; import com.rudderstack.android.sdk.core.RudderTraits; +import com.rudderstack.android.sdk.core.gson.gsonadapters.DoubleTypeAdapter; +import com.rudderstack.android.sdk.core.gson.gsonadapters.FloatTypeAdapter; import com.rudderstack.android.sdk.core.gson.gsonadapters.RudderContextTypeAdapter; import com.rudderstack.android.sdk.core.gson.gsonadapters.RudderJSONArrayTypeAdapter; import com.rudderstack.android.sdk.core.gson.gsonadapters.RudderJSONObjectTypeAdapter; @@ -12,22 +20,79 @@ import org.json.JSONArray; import org.json.JSONObject; +import java.lang.reflect.Type; + public class RudderGson { - private static Gson instance; + public static final String RUDDER_GSON_DESERIALIZE_EXCEPTION = "RudderGson: deserialize: Exception: "; + private static Gson gson = buildGsonInstance(); private RudderGson() { // private constructor to prevent instantiation } - public static Gson getInstance() { - if (instance == null) { - instance = new GsonBuilder() - .registerTypeAdapter(RudderTraits.class, new RudderTraitsTypeAdapter()) - .registerTypeAdapter(RudderContext.class, new RudderContextTypeAdapter()) - .registerTypeAdapter(JSONObject.class, new RudderJSONObjectTypeAdapter()) - .registerTypeAdapter(JSONArray.class, new RudderJSONArrayTypeAdapter()) - .create(); + private static Gson buildGsonInstance() { + return new GsonBuilder() + .registerTypeAdapterFactory(TypeAdapters.newFactory(double.class, Double.class, new DoubleTypeAdapter())) + .registerTypeAdapterFactory(TypeAdapters.newFactory(float.class, Float.class, new FloatTypeAdapter())) + .registerTypeAdapter(RudderTraits.class, new RudderTraitsTypeAdapter()) + .registerTypeAdapter(RudderContext.class, new RudderContextTypeAdapter()) + .registerTypeAdapter(JSONObject.class, new RudderJSONObjectTypeAdapter()) + .registerTypeAdapter(JSONArray.class, new RudderJSONArrayTypeAdapter()) + .create(); + } + + @Nullable + public static String serialize(Object object) { + try { + return gson.toJson(object); + } catch (Exception e) { + RudderLogger.logError("RudderGson: serialize: Exception: " + e.getMessage()); + ReportManager.reportError(e); + } + return null; + } + + @Nullable + public static T deserialize(String json, Class classOfT) { + try { + return gson.fromJson(json, classOfT); + } catch (Exception e) { + RudderLogger.logError(RUDDER_GSON_DESERIALIZE_EXCEPTION + e.getMessage()); + ReportManager.reportError(e); + } + return null; + } + + @Nullable + public static T deserialize(String json, Type typeOfT) { + try { + return gson.fromJson(json, typeOfT); + } catch (Exception e) { + RudderLogger.logError(RUDDER_GSON_DESERIALIZE_EXCEPTION + e.getMessage()); + ReportManager.reportError(e); + } + return null; + } + + @Nullable + public static T deserialize(JsonElement element, Class classOfT) { + try { + return gson.fromJson(element, classOfT); + } catch (Exception e) { + RudderLogger.logError(RUDDER_GSON_DESERIALIZE_EXCEPTION + e.getMessage()); + ReportManager.reportError(e); + } + return null; + } + + @Nullable + public static T deserialize(JsonElement element, Type typeOfT) { + try { + return gson.fromJson(element, typeOfT); + } catch (Exception e) { + RudderLogger.logError(RUDDER_GSON_DESERIALIZE_EXCEPTION + e.getMessage()); + ReportManager.reportError(e); } - return instance; + return null; } } diff --git a/core/src/main/java/com/rudderstack/android/sdk/core/gson/gsonadapters/DoubleTypeAdapter.java b/core/src/main/java/com/rudderstack/android/sdk/core/gson/gsonadapters/DoubleTypeAdapter.java new file mode 100644 index 000000000..9b72e8691 --- /dev/null +++ b/core/src/main/java/com/rudderstack/android/sdk/core/gson/gsonadapters/DoubleTypeAdapter.java @@ -0,0 +1,35 @@ +package com.rudderstack.android.sdk.core.gson.gsonadapters; + +import static java.lang.Double.POSITIVE_INFINITY; + +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; + +import java.io.IOException; + +public class DoubleTypeAdapter extends TypeAdapter { + @Override + public Number read(JsonReader in) throws IOException { + if (in.peek() == JsonToken.NULL) { + in.nextNull(); + return null; + } + return in.nextDouble(); + } + + @Override + public void write(JsonWriter out, Number value) throws IOException { + double doubleValue = value.doubleValue(); + if (Double.isNaN(doubleValue)) { + out.value("NaN"); + } else if (doubleValue == POSITIVE_INFINITY) { + out.value("Infinity"); + } else if (doubleValue == Double.NEGATIVE_INFINITY) { + out.value("-Infinity"); + } else { + out.value(value); + } + } +} \ No newline at end of file diff --git a/core/src/main/java/com/rudderstack/android/sdk/core/gson/gsonadapters/FloatTypeAdapter.java b/core/src/main/java/com/rudderstack/android/sdk/core/gson/gsonadapters/FloatTypeAdapter.java new file mode 100644 index 000000000..7220878c2 --- /dev/null +++ b/core/src/main/java/com/rudderstack/android/sdk/core/gson/gsonadapters/FloatTypeAdapter.java @@ -0,0 +1,35 @@ +package com.rudderstack.android.sdk.core.gson.gsonadapters; + +import android.renderscript.Type; + +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; + +import java.io.IOException; + +public class FloatTypeAdapter extends TypeAdapter { + @Override + public Number read(JsonReader in) throws IOException { + if (in.peek() == JsonToken.NULL) { + in.nextNull(); + return null; + } + return (float) in.nextDouble(); + } + + @Override + public void write(JsonWriter out, Number value) throws IOException { + float floatValue = value.floatValue(); + if (Float.isNaN(floatValue)) { + out.value("NaN"); + } else if (floatValue == Float.POSITIVE_INFINITY) { + out.value("Infinity"); + } else if (floatValue == Float.NEGATIVE_INFINITY) { + out.value("-Infinity"); + } else { + out.value(value); + } + } +} \ No newline at end of file diff --git a/core/src/main/java/com/rudderstack/android/sdk/core/gson/gsonadapters/RudderContextTypeAdapter.java b/core/src/main/java/com/rudderstack/android/sdk/core/gson/gsonadapters/RudderContextTypeAdapter.java index aabd41dab..f54cd71d5 100644 --- a/core/src/main/java/com/rudderstack/android/sdk/core/gson/gsonadapters/RudderContextTypeAdapter.java +++ b/core/src/main/java/com/rudderstack/android/sdk/core/gson/gsonadapters/RudderContextTypeAdapter.java @@ -1,10 +1,12 @@ package com.rudderstack.android.sdk.core.gson.gsonadapters; import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializer; +import com.google.gson.internal.bind.TypeAdapters; import com.rudderstack.android.sdk.core.ReportManager; import com.rudderstack.android.sdk.core.RudderContext; @@ -18,7 +20,10 @@ public JsonElement serialize(RudderContext rudderContext, JsonSerializationContext context) { try { JsonObject outputContext = new JsonObject(); - Gson gson = new Gson(); + Gson gson = new GsonBuilder() + .registerTypeAdapterFactory(TypeAdapters.newFactory(double.class, Double.class, new DoubleTypeAdapter())) + .registerTypeAdapterFactory(TypeAdapters.newFactory(float.class, Float.class, new FloatTypeAdapter())) + .create(); JsonObject inputContext = (JsonObject) gson.toJsonTree(rudderContext); for (Map.Entry entry : inputContext.entrySet()) { if (entry.getKey().equals("customContextMap")) { diff --git a/core/src/main/java/com/rudderstack/android/sdk/core/gson/gsonadapters/RudderTraitsTypeAdapter.java b/core/src/main/java/com/rudderstack/android/sdk/core/gson/gsonadapters/RudderTraitsTypeAdapter.java index 465bd9158..cc02e950f 100644 --- a/core/src/main/java/com/rudderstack/android/sdk/core/gson/gsonadapters/RudderTraitsTypeAdapter.java +++ b/core/src/main/java/com/rudderstack/android/sdk/core/gson/gsonadapters/RudderTraitsTypeAdapter.java @@ -1,10 +1,12 @@ package com.rudderstack.android.sdk.core.gson.gsonadapters; import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializer; +import com.google.gson.internal.bind.TypeAdapters; import com.rudderstack.android.sdk.core.ReportManager; import com.rudderstack.android.sdk.core.RudderTraits; @@ -18,7 +20,10 @@ public JsonElement serialize(RudderTraits traits, JsonSerializationContext context) { try { JsonObject outputTraits = new JsonObject(); - Gson gson = new Gson(); + Gson gson = new GsonBuilder() + .registerTypeAdapterFactory(TypeAdapters.newFactory(double.class, Double.class, new DoubleTypeAdapter())) + .registerTypeAdapterFactory(TypeAdapters.newFactory(float.class, Float.class, new FloatTypeAdapter())) + .create(); JsonObject inputTraits = (JsonObject) gson.toJsonTree(traits); for (Map.Entry entry : inputTraits.entrySet()) { if (entry.getKey().equals("extras")) { diff --git a/core/src/main/java/com/rudderstack/android/sdk/core/util/Utils.java b/core/src/main/java/com/rudderstack/android/sdk/core/util/Utils.java index 554239134..2863551ce 100644 --- a/core/src/main/java/com/rudderstack/android/sdk/core/util/Utils.java +++ b/core/src/main/java/com/rudderstack/android/sdk/core/util/Utils.java @@ -12,9 +12,11 @@ import android.os.Build; import android.text.TextUtils; +import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import com.rudderstack.android.sdk.core.ReportManager; import com.rudderstack.android.sdk.core.RudderLogger; +import com.rudderstack.android.sdk.core.RudderMessage; import com.rudderstack.android.sdk.core.gson.RudderGson; import java.io.File; @@ -23,6 +25,8 @@ import java.util.ArrayList; import java.util.Date; import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; @@ -90,15 +94,27 @@ public static String getDeviceId(Application application) { return null; } - public static Map convertToMap(String json) { - return RudderGson.getInstance().fromJson(json, new TypeToken>() { - }.getType() + public static Map convertToMap(Object obj) { + String json = RudderGson.serialize(obj); + if (json == null) { + return new HashMap<>(); + } + Map map = RudderGson.deserialize(json, new TypeToken>() { + }.getType() ); + + return map == null ? new HashMap<>() : map; } - public static List> convertToList(String json) { - return RudderGson.getInstance().fromJson(json, new TypeToken>>() { + + public static List> convertToList(Object obj) { + String json = RudderGson.serialize(obj); + if (json == null) { + return new ArrayList<>(); + } + List> list = RudderGson.deserialize(json, new TypeToken>>() { }.getType()); + return list == null ? new ArrayList<>() : list; } public static String getWriteKeyFromStrings(Context context) { diff --git a/core/src/test/java/com/rudderstack/android/sdk/core/DBPersistentManagerTest.java b/core/src/test/java/com/rudderstack/android/sdk/core/DBPersistentManagerTest.java index bd9a239a7..ce9835f0f 100644 --- a/core/src/test/java/com/rudderstack/android/sdk/core/DBPersistentManagerTest.java +++ b/core/src/test/java/com/rudderstack/android/sdk/core/DBPersistentManagerTest.java @@ -81,9 +81,6 @@ public class DBPersistentManagerTest { private RudderDeviceModeManager deviceModeManager ; @Before public void setUp() throws Exception { -// final DBPersistentManager finalDbPersistentManager = DBPersistentManager.getInstance(ApplicationProvider -// .getApplicationContext()); -// dbPersistentManager = PowerMockito.spy(finalDbPersistentManager); dbPersistentManager = PowerMockito.mock(DBPersistentManager.class); PowerMockito.when(dbPersistentManager, "saveEventSync", anyString()).thenCallRealMethod(); PowerMockito.when(dbPersistentManager, "saveEvent", anyString(), any()).thenCallRealMethod(); @@ -215,7 +212,7 @@ private List parse(List messageJsons) { List messages = new ArrayList<>(); for (String mJson : messageJsons) { - messages.add(RudderGson.getInstance().fromJson(mJson, RudderMessage.class)); + messages.add(RudderGson.deserialize(mJson, RudderMessage.class)); } return messages; } diff --git a/core/src/test/java/com/rudderstack/android/sdk/core/EventRepositoryTest.java b/core/src/test/java/com/rudderstack/android/sdk/core/EventRepositoryTest.java index 3c95245eb..086ca6766 100644 --- a/core/src/test/java/com/rudderstack/android/sdk/core/EventRepositoryTest.java +++ b/core/src/test/java/com/rudderstack/android/sdk/core/EventRepositoryTest.java @@ -8,11 +8,28 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.anyString; +import static java.lang.Double.POSITIVE_INFINITY; import static java.util.concurrent.TimeUnit.SECONDS; import android.text.TextUtils; import com.google.common.collect.ImmutableList; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.internal.bind.TypeAdapters; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; +import com.rudderstack.android.sdk.core.gson.RudderGson; import com.rudderstack.android.sdk.core.util.Utils; import org.hamcrest.Matchers; @@ -33,9 +50,13 @@ import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; +import java.io.IOException; +import java.lang.reflect.Type; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicInteger; @@ -80,7 +101,6 @@ public void flush() throws Exception { final RudderNetworkManager.Result mockResult = new RudderNetworkManager.Result(RudderNetworkManager.NetworkResponses.SUCCESS, 200, "", null); -// Mockito.doNothing().when(dbPersistentManager.fetchAllEventsFromDB(anyList(), anyList())); Mockito.doAnswer((Answer) invocation -> { ((ArrayList) invocation.getArgument(0)).addAll(messageIds); ((ArrayList) invocation.getArgument(1)).addAll(messages); @@ -146,18 +166,10 @@ public void flush() throws Exception { anyBoolean() ); -// networkManager.sendNetworkRequest( -// arg1.capture(), -// arg2.capture(), -// arg3.capture() -// ); assertThat(result, is(true)); - System.out.println(arg1.getValue()); assertThat(arg1.getValue().replace(" ", ""), is(expectedPayload.replace("\n", "").replace(" ", ""))); - System.out.println(arg2.getValue()); assertThat(arg2.getValue().replace(" ", ""), is("api.rudderstack.com/v1/batch")); - System.out.println(arg3.getValue()); assertThat(arg3.getValue(), is(RudderNetworkManager.RequestMethod.POST)); } @@ -286,7 +298,7 @@ public void testGetEventJson() throws JSONException { jsonData.put("coDriverVersion", -1.0); RudderMessage message = new RudderMessageBuilder().setEventName("TestEvent").setProperty(jsonData).build(); String expectedJsonString = "{\n" + - " \"messageId\": \""+message.getMessageId()+"\",\n" + + " \"messageId\": \"" + message.getMessageId() + "\",\n" + " \"channel\": \"mobile\",\n" + " \"context\": {},\n" + " \"originalTimestamp\": \"2022-03-14T06:46:41.365Z\",\n" + @@ -357,8 +369,8 @@ public void testGetEventJson() throws JSONException { " },\n" + " \"integrations\": {}\n" + "}"; - String outputJsonString = repo.getEventJsonString(message); - assertThat("JSONObjects and JSONArray are serialized perfectly", outputJsonString , is(expectedJsonString.replace("\n", "").replace(" ", ""))); + String outputJsonString = repo.getEventJsonString(message); + assertThat("JSONObjects and JSONArray are serialized perfectly", outputJsonString, is(expectedJsonString.replace("\n", "").replace(" ", ""))); } } diff --git a/core/src/test/java/com/rudderstack/android/sdk/core/RudderGsonTest.java b/core/src/test/java/com/rudderstack/android/sdk/core/RudderGsonTest.java index 91384f8e3..489b36f5b 100644 --- a/core/src/test/java/com/rudderstack/android/sdk/core/RudderGsonTest.java +++ b/core/src/test/java/com/rudderstack/android/sdk/core/RudderGsonTest.java @@ -5,17 +5,29 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; +import static java.lang.Double.POSITIVE_INFINITY; import static java.util.concurrent.TimeUnit.SECONDS; +import android.text.TextUtils; + import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.MockedStatic; import org.mockito.Mockito; +import org.mockito.stubbing.Answer; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; @@ -24,7 +36,19 @@ import com.rudderstack.android.sdk.core.gson.RudderGson; import com.rudderstack.android.sdk.core.util.Utils; +@RunWith(PowerMockRunner.class) +@PrepareForTest({Utils.class}) public class RudderGsonTest { + + @Before + public void setup() throws Exception { + //mocking timestamp + PowerMockito.spy(Utils.class); + PowerMockito.when(Utils.class, "getTimeStamp" + ) + .thenAnswer((Answer) invocation -> "2022-03-14T06:46:41.365Z"); + } + @Test public void testRudderContextSerializationSynchronicity() { AtomicInteger contextsSerialized = new AtomicInteger(0); @@ -41,7 +65,7 @@ public void run() { } catch (IllegalAccessException e) { throw new RuntimeException(e); } - assertThat("contexts got serialized perfectly " + i, areJsonStringsEqual(RudderGson.getInstance().toJson(context), getRudderContextJsonString(i)), is(true)); + assertThat("contexts got serialized perfectly " + i, areJsonStringsEqual(RudderGson.serialize(context), getRudderContextJsonString(i)), is(true)); contextsSerialized.addAndGet(1); } } @@ -59,7 +83,7 @@ public void run() { } catch (IllegalAccessException e) { throw new RuntimeException(e); } - assertThat("contexts got serialized perfectly", areJsonStringsEqual(RudderGson.getInstance().toJson(context), getRudderContextJsonString(i)), is(true)); + assertThat("contexts got serialized perfectly", areJsonStringsEqual(RudderGson.serialize(context), getRudderContextJsonString(i)), is(true)); contextsSerialized.addAndGet(1); } } @@ -77,7 +101,7 @@ public void run() { } catch (IllegalAccessException e) { throw new RuntimeException(e); } - assertThat("contexts got serialized perfectly", areJsonStringsEqual(RudderGson.getInstance().toJson(context), getRudderContextJsonString(i)), is(true)); + assertThat("contexts got serialized perfectly", areJsonStringsEqual(RudderGson.serialize(context), getRudderContextJsonString(i)), is(true)); contextsSerialized.addAndGet(1); } } @@ -98,6 +122,7 @@ private RudderContext getRudderContext(int i) throws NoSuchFieldException, Illeg ReflectionUtils.setPrivateField(context, "customContextMap", customContextMap); return context; } + private RudderContext getDefaultRudderContext() throws NoSuchFieldException, IllegalAccessException { RudderContext context = Mockito.mock(RudderContext.class); RudderApp app = Mockito.mock(RudderApp.class); @@ -152,7 +177,7 @@ public void run() { super.run(); for (int i = 1; i <= 3000; i++) { RudderTraits traits = getTraits(i); - assertThat("traits got serialized perfectly", areJsonStringsEqual(RudderGson.getInstance().toJson(traits), getTraitsJsonString(i)), is(true)); + assertThat("traits got serialized perfectly", areJsonStringsEqual(RudderGson.serialize(traits), getTraitsJsonString(i)), is(true)); traitsSerialized.addAndGet(1); } } @@ -163,7 +188,7 @@ public void run() { super.run(); for (int i = 3001; i <= 6000; i++) { RudderTraits traits = getTraits(i); - assertThat("traits got serialized perfectly", areJsonStringsEqual(RudderGson.getInstance().toJson(traits), getTraitsJsonString(i)), is(true)); + assertThat("traits got serialized perfectly", areJsonStringsEqual(RudderGson.serialize(traits), getTraitsJsonString(i)), is(true)); traitsSerialized.addAndGet(1); } } @@ -174,7 +199,7 @@ public void run() { super.run(); for (int i = 6001; i <= 9000; i++) { RudderTraits traits = getTraits(i); - assertThat("traits got serialized perfectly", areJsonStringsEqual(RudderGson.getInstance().toJson(traits), getTraitsJsonString(i)), is(true)); + assertThat("traits got serialized perfectly", areJsonStringsEqual(RudderGson.serialize(traits), getTraitsJsonString(i)), is(true)); traitsSerialized.addAndGet(1); } } @@ -205,9 +230,9 @@ public void testJSONArrayJSONObjectSerializationSynchronicity() { utilities.when(Utils::getTimeStamp).thenReturn("2022-03-14T06:46:41.365Z"); for (int i = 1; i <= 1000; i++) { RudderMessage trackMessage = getTrackMessage(i); - assertThat("track message got serialized perfectly", areJsonStringsEqual(RudderGson.getInstance().toJson(trackMessage), getTrackJsonString(i, trackMessage)), is(true)); + assertThat("track message got serialized perfectly", areJsonStringsEqual(RudderGson.serialize(trackMessage), getTrackJsonString(i, trackMessage)), is(true)); RudderMessage identifyMessage = getIdentifyMessage(i); - assertThat("identify message got serialized perfectly", areJsonStringsEqual(RudderGson.getInstance().toJson(identifyMessage), getIdentifyJsonString(i, identifyMessage)), is(true)); + assertThat("identify message got serialized perfectly", areJsonStringsEqual(RudderGson.serialize(identifyMessage), getIdentifyJsonString(i, identifyMessage)), is(true)); messagesSerialized.addAndGet(2); } } @@ -219,9 +244,9 @@ public void testJSONArrayJSONObjectSerializationSynchronicity() { utilities.when(Utils::getTimeStamp).thenReturn("2022-03-14T06:46:41.365Z"); for (int i = 1001; i <= 2000; i++) { RudderMessage trackMessage = getTrackMessage(i); - assertThat("track message got serialized perfectly", areJsonStringsEqual(RudderGson.getInstance().toJson(trackMessage), getTrackJsonString(i, trackMessage)), is(true)); + assertThat("track message got serialized perfectly", areJsonStringsEqual(RudderGson.serialize(trackMessage), getTrackJsonString(i, trackMessage)), is(true)); RudderMessage identifyMessage = getIdentifyMessage(i); - assertThat("identify message got serialized perfectly", areJsonStringsEqual(RudderGson.getInstance().toJson(identifyMessage), getIdentifyJsonString(i, identifyMessage)), is(true)); + assertThat("identify message got serialized perfectly", areJsonStringsEqual(RudderGson.serialize(identifyMessage), getIdentifyJsonString(i, identifyMessage)), is(true)); messagesSerialized.addAndGet(2); } } @@ -271,4 +296,175 @@ public boolean areJsonStringsEqual(String json1, String json2) { boolean result = obj1.equals(obj2); return result; } + + @Test + public void testGsonWithInvalidNumbers() { + RudderContext context = new RudderContext(); + // insert traits in the context which contains invalid numbers + context.updateTraitsMap(getInvalidNumbersMap()); + // set custom context with invalid numbers + context.setCustomContexts(getInvalidNumbersMap()); + + Map eventProperties = getInvalidNumbersMap(); + eventProperties.put("list", new ArrayList<>(Arrays.asList(POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.MAX_VALUE, Double.MIN_VALUE, Double.NaN))); + eventProperties.put("map", getInvalidNumbersMap()); + + RudderMessage message = new RudderMessageBuilder().setEventName("TestEvent").setProperty(eventProperties).build(); + message.setContext(context); + + + String outputJsonString = RudderGson.serialize(message); + String expectedJsonString = "{" + + "\"messageId\": \"" + message.getMessageId() + "\"," + + "\"channel\": \"mobile\"," + + "\"context\": {" + + "\"traits\": {" + + "\"general\": 45," + + "\"minValue\": 4.9E-324," + + "\"maxValue\": 1.7976931348623157E308," + + "\"double\": 45.0," + + "\"positiveInfinity\": \"Infinity\"," + + "\"nan\": \"NaN\"," + + "\"float\": 45.0," + + "\"list\": [" + + "\"Infinity\"," + + "\"-Infinity\"," + + "1.7976931348623157E308," + + "4.9E-324," + + "\"NaN\"," + + "45," + + "45.0" + + "]," + + "\"map\": {" + + "\"general\": 45," + + "\"minValue\": 4.9E-324," + + "\"maxValue\": 1.7976931348623157E308," + + "\"double\": 45.0," + + "\"positiveInfinity\": \"Infinity\"," + + "\"nan\": \"NaN\"," + + "\"negativeInfinity\": \"-Infinity\"" + + "}," + + "\"long\": 45," + + "\"int\": 45," + + "\"negativeInfinity\": \"-Infinity\"" + + "}," + + "\"maxValue\": 1.7976931348623157E308," + + "\"double\": 45.0," + + "\"positiveInfinity\": \"Infinity\"," + + "\"float\": 45.0," + + "\"list\": [" + + "\"Infinity\"," + + "\"-Infinity\"," + + "1.7976931348623157E308," + + "4.9E-324," + + "\"NaN\"," + + "45," + + "45.0" + + "]," + + "\"long\": 45," + + "\"int\": 45," + + "\"negativeInfinity\": \"-Infinity\"," + + "\"general\": 45," + + "\"minValue\": 4.9E-324," + + "\"nan\": \"NaN\"," + + "\"map\": {" + + "\"general\": 45," + + "\"minValue\": 4.9E-324," + + "\"maxValue\": 1.7976931348623157E308," + + "\"double\": 45.0," + + "\"positiveInfinity\": \"Infinity\"," + + "\"nan\": \"NaN\"," + + "\"negativeInfinity\": \"-Infinity\"" + + "}" + + "}," + + "\"originalTimestamp\": \"2022-03-14T06:46:41.365Z\"," + + "\"event\": \"TestEvent\"," + + "\"properties\": {" + + "\"maxValue\": 1.7976931348623157E308," + + "\"double\": 45.0," + + "\"positiveInfinity\": \"Infinity\"," + + "\"float\": 45.0," + + "\"list\": [" + + "\"Infinity\"," + + "\"-Infinity\"," + + "1.7976931348623157E308," + + "4.9E-324," + + "\"NaN\"" + + "]," + + "\"long\": 45," + + "\"int\": 45," + + "\"negativeInfinity\": \"-Infinity\"," + + "\"general\": 45," + + "\"minValue\": 4.9E-324," + + "\"nan\": \"NaN\"," + + "\"map\": {" + + "\"general\": 45," + + "\"minValue\": 4.9E-324," + + "\"maxValue\": 1.7976931348623157E308," + + "\"double\": 45.0," + + "\"positiveInfinity\": \"Infinity\"," + + "\"nan\": \"NaN\"," + + "\"float\": 45.0," + + "\"list\": [" + + "\"Infinity\"," + + "\"-Infinity\"," + + "1.7976931348623157E308," + + "4.9E-324," + + "\"NaN\"," + + "45," + + "45.0" + + "]," + + "\"map\": {" + + "\"general\": 45," + + "\"minValue\": 4.9E-324," + + "\"maxValue\": 1.7976931348623157E308," + + "\"double\": 45.0," + + "\"positiveInfinity\": \"Infinity\"," + + "\"nan\": \"NaN\"," + + "\"negativeInfinity\": \"-Infinity\"" + + "}," + + "\"long\": 45," + + "\"int\": 45," + + "\"negativeInfinity\": \"-Infinity\"" + + "}" + + "}," + + "\"integrations\": {}" + + "}"; + assertThat("Invalid Numbers are removed and the message is serialized perfectly", outputJsonString, is(expectedJsonString.replace("\n", "").replace(" ", ""))); + } + + Map getInvalidNumbersMap() { + Map map = new HashMap<>(); + map.put("positiveInfinity", POSITIVE_INFINITY); + map.put("negativeInfinity", Double.NEGATIVE_INFINITY); + map.put("maxValue", Double.MAX_VALUE); + map.put("minValue", Double.MIN_VALUE); + map.put("nan", Double.NaN); + map.put("general", 45); + map.put("double", 45.0); + map.put("float", 45.0f); + map.put("long", 45L); + map.put("int", 45); + // add a nested map with invalid numbers + Map nestedMap = new HashMap<>(); + nestedMap.put("positiveInfinity", POSITIVE_INFINITY); + nestedMap.put("negativeInfinity", Double.NEGATIVE_INFINITY); + nestedMap.put("maxValue", Double.MAX_VALUE); + nestedMap.put("minValue", Double.MIN_VALUE); + nestedMap.put("nan", Double.NaN); + nestedMap.put("general", 45); + nestedMap.put("double", 45.0); + map.put("map", nestedMap); + // add a nested array with invalid numbers + List nestedList = new ArrayList<>(); + nestedList.add(POSITIVE_INFINITY); + nestedList.add(Double.NEGATIVE_INFINITY); + nestedList.add(Double.MAX_VALUE); + nestedList.add(Double.MIN_VALUE); + nestedList.add(Double.NaN); + nestedList.add(45); + nestedList.add(45.0); + map.put("list", nestedList); + return map; + } } diff --git a/core/src/test/java/com/rudderstack/android/sdk/core/RudderUserSessionManagerTest.java b/core/src/test/java/com/rudderstack/android/sdk/core/RudderUserSessionManagerTest.java index f63b94181..1b5511777 100644 --- a/core/src/test/java/com/rudderstack/android/sdk/core/RudderUserSessionManagerTest.java +++ b/core/src/test/java/com/rudderstack/android/sdk/core/RudderUserSessionManagerTest.java @@ -43,17 +43,12 @@ public void setup() throws Exception { @Test public void applySessionTracking() throws Exception { long testSessionId = 123L; - - userSessionManager.startSession(testSessionId); -// applicationLifeCycleManager.applySessionTracking(spyMessage); -// Mockito.verify(spyMessage).setSession(userSession); Mockito.verify(userSession).startSession(testSessionId); PowerMockito.doReturn(testSessionId).when(userSession).getSessionId(); RudderMessage message = new RudderMessageBuilder().setUserId("u-1").build(); RudderMessage spyMessage = PowerMockito.spy(message); -// PowerMockito.doAnswer((Answer) invocation -> null).when(spyMessage).setSession(Mockito.any()); PowerMockito.doNothing().when(spyMessage).setSession(userSession); userSessionManager.applySessionTracking(spyMessage); Mockito.verify(spyMessage).setSession(userSession); diff --git a/core/src/test/java/com/rudderstack/android/sdk/core/SerializationTest.java b/core/src/test/java/com/rudderstack/android/sdk/core/SerializationTest.java index 6709cec20..7e5421260 100644 --- a/core/src/test/java/com/rudderstack/android/sdk/core/SerializationTest.java +++ b/core/src/test/java/com/rudderstack/android/sdk/core/SerializationTest.java @@ -74,11 +74,7 @@ public class SerializationTest { "}"; @Test public void testObjectOutputStream() throws IOException, ClassNotFoundException { - System.out.println(INCOMING_JSON); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); -// baos.write(INCOMING_JSON.getBytes(StandardCharsets.UTF_8)); -// RudderServerConfig rudderServerConfig = new Gson().fromJson(INCOMING_JSON, RudderServerConfig.class); ObjectOutputStream os = new ObjectOutputStream(baos); os.writeObject(rudderServerConfig); @@ -95,7 +91,6 @@ public void testObjectOutputStream() throws IOException, ClassNotFoundException dummyoos.flush(); ByteArrayInputStream dummyBios = new ByteArrayInputStream(dummyos.toByteArray()); -// dummyBios.close(); ObjectInputStream dummyois = new ObjectInputStream(dummyBios); MatcherAssert.assertThat("abcd", Matchers.is((String) dummyois.readObject())); diff --git a/core/src/test/java/com/rudderstack/android/sdk/core/SourceConfigurationTest.java b/core/src/test/java/com/rudderstack/android/sdk/core/SourceConfigurationTest.java index 1518511b3..e2069db7d 100644 --- a/core/src/test/java/com/rudderstack/android/sdk/core/SourceConfigurationTest.java +++ b/core/src/test/java/com/rudderstack/android/sdk/core/SourceConfigurationTest.java @@ -21,12 +21,6 @@ public class SourceConfigurationTest { "}"; @Test public void testSourceConfigurationSerialization(){ -// SourceConfiguration sourceConfiguration = new SourceConfiguration(new -// SourceConfiguration.StatsCollection(new SourceConfiguration.Errors( -// true), new SourceConfiguration.Metrics(true))); -// assertEquals(sourceConfiguration.getStatsCollection().getErrors().isEnabled(), true); -// assertEquals(sourceConfiguration.getStatsCollection().getMetrics().isEnabled(), true); - SourceConfiguration config = new GsonBuilder().create().fromJson(TEST_SOURCE_CONFIGURATION, SourceConfiguration.class); assertTrue(config.getStatsCollection().getErrors().isEnabled()); assertTrue(config.getStatsCollection().getMetrics().isEnabled()); diff --git a/core/src/test/java/com/rudderstack/android/sdk/core/consent/ConsentFilterHandlerTest.java b/core/src/test/java/com/rudderstack/android/sdk/core/consent/ConsentFilterHandlerTest.java index 4584083de..214956d50 100644 --- a/core/src/test/java/com/rudderstack/android/sdk/core/consent/ConsentFilterHandlerTest.java +++ b/core/src/test/java/com/rudderstack/android/sdk/core/consent/ConsentFilterHandlerTest.java @@ -76,7 +76,6 @@ public void testApplyConsentWithEmptyDestinationList(){ .setRudderOption(option) .build(); ConsentFilterHandler consentFilterHandler = createConsentFilterHandler(Collections.emptyList(), Collections.emptySet()); -// consentFilterHandler.filterDestinationList() RudderMessage updatedMessage = consentFilterHandler.applyConsent( rudderMessage); assertThat(updatedMessage, Matchers.is(rudderMessage)); diff --git a/sample-kotlin/build.gradle b/sample-kotlin/build.gradle index 382e053be..bcad85874 100644 --- a/sample-kotlin/build.gradle +++ b/sample-kotlin/build.gradle @@ -63,7 +63,6 @@ dependencies { // Rudder Android Core SDK implementation project(':core') -// implementation 'com.rudderstack.android.sdk:core:1.19.1' implementation 'com.google.code.gson:gson:2.8.6' implementation "androidx.work:work-runtime:2.7.1" From a792ce26514b31d82317eb59f16a97979ddfc13c Mon Sep 17 00:00:00 2001 From: Debanjan Chatterjee Date: Tue, 6 Feb 2024 18:21:19 +0530 Subject: [PATCH 2/4] fix: race condition fix using semaphore (#388) * fix: race condition fix using semaphore * fix: sonarlint issues * fix: acquire and wait --- .../android/sdk/core/DBPersistentManager.java | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/rudderstack/android/sdk/core/DBPersistentManager.java b/core/src/main/java/com/rudderstack/android/sdk/core/DBPersistentManager.java index 248e10aef..751b88c3f 100644 --- a/core/src/main/java/com/rudderstack/android/sdk/core/DBPersistentManager.java +++ b/core/src/main/java/com/rudderstack/android/sdk/core/DBPersistentManager.java @@ -6,7 +6,6 @@ import android.content.ContentValues; import android.database.Cursor; import android.database.sqlite.SQLiteDatabaseCorruptException; -import android.icu.text.Collator; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; @@ -17,6 +16,7 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; +import com.rudderstack.android.ruddermetricsreporterandroid.RudderReporter; import com.rudderstack.android.sdk.core.persistence.DefaultPersistenceProviderFactory; import com.rudderstack.android.sdk.core.persistence.Persistence; import com.rudderstack.android.sdk.core.persistence.PersistenceProvider; @@ -33,6 +33,7 @@ import java.util.Queue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.Semaphore; /* * Helper class for SQLite operations @@ -244,6 +245,7 @@ void flushEvents() { try { if (persistence.isAccessible()) { + waitTillMigrationsAreDone(); String deleteSQL = String.format(Locale.US, "DELETE FROM %s", EVENTS_TABLE_NAME); RudderLogger.logDebug(String.format(Locale.US, "DBPersistentManager: flushEvents: deleteSQL: %s", deleteSQL)); synchronized (DB_LOCK) { @@ -266,6 +268,7 @@ void clearEventsFromDB(List messageIds) { try { // get writable database if (persistence.isAccessible()) { + waitTillMigrationsAreDone(); RudderLogger.logInfo(String.format(Locale.US, "DBPersistentManager: clearEventsFromDB: Clearing %d messages from DB", messageIds.size())); // format CSV string from messageIds list StringBuilder builder = new StringBuilder(); @@ -316,6 +319,7 @@ void getEventsFromDB(Map messageIdStatusMap,//(id (row_id), st } Cursor cursor; synchronized (DB_LOCK) { + waitTillMigrationsAreDone(); cursor = persistence.rawQuery(selectSQL, null); } if (!cursor.moveToFirst()) { @@ -505,7 +509,10 @@ private boolean checkIfColumnExists(String newColumn) { return false; } + private final Semaphore migrationSemaphore = new Semaphore(1); + void checkForMigrations() { + acquireSemaphore(); Runnable runnable = () -> { try { boolean isNewColumnAdded = false; @@ -525,12 +532,30 @@ void checkForMigrations() { } catch (SQLiteDatabaseCorruptException | ConcurrentModificationException | NullPointerException ex) { RudderLogger.logError(DBPERSISTENT_MANAGER_CHECK_FOR_MIGRATIONS_TAG + ex.getLocalizedMessage()); + } finally { + migrationSemaphore.release(); } }; // Need to perform db operations on a separate thread to support strict mode. executor.execute(runnable); } + private void acquireSemaphore() { + try { + migrationSemaphore.acquire(); + } catch (InterruptedException e) { + ReportManager.reportError(e); + Thread.currentThread().interrupt(); + } + } + private void waitTillMigrationsAreDone() { + if(migrationSemaphore.availablePermits() == 1 ){ + return; + } + acquireSemaphore(); + migrationSemaphore.release(); + } + private void performMigration(String columnName) { try { if (persistence.isAccessible()) { @@ -596,6 +621,7 @@ void markDeviceModeTransformationAndDMProcessedDone(List rowIds) { " WHERE " + MESSAGE_ID_COL + " IN " + rowIdsCSVString + ";"; synchronized (DB_LOCK) { + waitTillMigrationsAreDone(); persistence.execSQL(sql); } } @@ -605,6 +631,7 @@ public void markDeviceModeProcessedDone(Integer rowId) { DBPersistentManager.DM_PROCESSED_COL + " = " + DBPersistentManager.DM_PROCESSED_DONE + " WHERE " + MESSAGE_ID_COL + " = " + rowId + ";"; synchronized (DB_LOCK) { + waitTillMigrationsAreDone(); persistence.execSQL(sql); } } @@ -616,6 +643,7 @@ private void updateEventStatus(String rowIdsCSVString, int status) { ") WHERE " + MESSAGE_ID_COL + " IN " + rowIdsCSVString + ";"; synchronized (DB_LOCK) { + waitTillMigrationsAreDone(); persistence.execSQL(sql); } } @@ -627,6 +655,7 @@ void runGcForEvents() { private void deleteDoneEvents() { synchronized (DB_LOCK) { + waitTillMigrationsAreDone(); persistence.delete(EVENTS_TABLE_NAME, DBPersistentManager.STATUS_COL + " = " + DBPersistentManager.STATUS_ALL_DONE, null); From 4bc1d7d84f72d661cee70af3a672259d6d5f80a8 Mon Sep 17 00:00:00 2001 From: Desu Sai Venkat <48179357+desusai7@users.noreply.github.com> Date: Wed, 7 Feb 2024 17:14:50 +0530 Subject: [PATCH 3/4] fix: fixed serialization of strings twice (#390) * fix: fixed serialization of strings twice * test: added unit tests for string conversion to map and list * chore: minor improvements * chore: added comments about the new public functions in Utils * chore: minor improvements --- .../android/sdk/core/util/Utils.java | 35 +++++++++++++++++-- .../android/sdk/core/UtilsTest.java | 26 ++++++++++++++ 2 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 core/src/test/java/com/rudderstack/android/sdk/core/UtilsTest.java diff --git a/core/src/main/java/com/rudderstack/android/sdk/core/util/Utils.java b/core/src/main/java/com/rudderstack/android/sdk/core/util/Utils.java index 2863551ce..d11410933 100644 --- a/core/src/main/java/com/rudderstack/android/sdk/core/util/Utils.java +++ b/core/src/main/java/com/rudderstack/android/sdk/core/util/Utils.java @@ -94,8 +94,28 @@ public static String getDeviceId(Application application) { return null; } + @Nullable + private static String serializeObject(Object obj) { + String json; + if (obj instanceof String) { + json = (String) obj; + } else { + json = RudderGson.serialize(obj); + } + return json; + } + + /** + * Convert an object to a map + *

Serialize the object to a json string and then convert it to a map

+ *

If the object is a string, it is directly converted to a map

+ *

If the object results in an invalid json string after serialization, an empty map is returned

+ * + * @param obj the object to convert + * @return the map representation of the object + */ public static Map convertToMap(Object obj) { - String json = RudderGson.serialize(obj); + String json = serializeObject(obj); if (json == null) { return new HashMap<>(); } @@ -106,9 +126,18 @@ public static Map convertToMap(Object obj) { return map == null ? new HashMap<>() : map; } - + /** + * Convert an object to a list + *

Serialize the object to a json string and then convert it to a list

+ *

If the object is a string, it is directly converted to a list

+ *

If the object results in an invalid json string after serialization, an empty list is returned

+ * + * @param obj the object to convert + * @return the list representation of the object + */ public static List> convertToList(Object obj) { - String json = RudderGson.serialize(obj); + String json = serializeObject(obj); + if (json == null) { return new ArrayList<>(); } diff --git a/core/src/test/java/com/rudderstack/android/sdk/core/UtilsTest.java b/core/src/test/java/com/rudderstack/android/sdk/core/UtilsTest.java new file mode 100644 index 000000000..d175ff493 --- /dev/null +++ b/core/src/test/java/com/rudderstack/android/sdk/core/UtilsTest.java @@ -0,0 +1,26 @@ +package com.rudderstack.android.sdk.core; + +import static org.hamcrest.MatcherAssert.assertThat; + +import com.rudderstack.android.sdk.core.util.Utils; + +import org.junit.Test; + +import java.util.List; +import java.util.Map; + +public class UtilsTest { + @Test + public void testStringConversiontToMap() { + String rudderTraits = "{\"dirty\":{\"general\":45.0,\"minValue\":4.9E-324,\"maxValue\":1.7976931348623157E308,\"double\":45.0,\"positiveInfinity\":\"Infinity\",\"nan\":\"NaN\",\"float\":45.0,\"list\":[\"Infinity\",\"-Infinity\",1.7976931348623157E308,4.9E-324,\"NaN\",45.0,45.0],\"map\":{\"general\":45.0,\"minValue\":4.9E-324,\"maxValue\":1.7976931348623157E308,\"double\":45.0,\"positiveInfinity\":\"Infinity\",\"nan\":\"NaN\",\"negativeInfinity\":\"-Infinity\"},\"long\":45.0,\"int\":45.0,\"negativeInfinity\":\"-Infinity\"},\"anonymousId\":\"c5ee2cf1-97a3-4744-80fc-faabce7a7e51\",\"name\":\"Mr. User1\",\"id\":\"new user 2\",\"userId\":\"new user 2\",\"email\":\"user1@gmail.com\"}"; + Map map = Utils.convertToMap(rudderTraits); + assertThat("Map should not be empty", map.size() > 0); + } + + @Test + public void testStringConversionToArray() { + String externalIds = "[{\"id\":\"idValue\",\"type\":\"idTYpe\"}]"; + List> list = Utils.convertToList(externalIds); + assertThat("List should not be empty", list.size() > 0); + } +} From c41ffb4d8a67c6be722740c680ebd1ecff98aae5 Mon Sep 17 00:00:00 2001 From: GitHub actions Date: Wed, 7 Feb 2024 12:05:31 +0000 Subject: [PATCH 4/4] chore(release): 1.21.3 --- CHANGELOG.md | 9 +++++++++ gradle.properties | 4 ++-- package.json | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 919cf3695..f0e520fd0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +### [1.21.3](https://github.com/rudderlabs/rudder-sdk-android/compare/v1.21.2...v1.21.3) (2024-02-07) + + +### Bug Fixes + +* fixed serialization of strings twice ([#390](https://github.com/rudderlabs/rudder-sdk-android/issues/390)) ([4bc1d7d](https://github.com/rudderlabs/rudder-sdk-android/commit/4bc1d7d84f72d661cee70af3a672259d6d5f80a8)) +* handling the serialization of special floating point values while serializing any object ([#382](https://github.com/rudderlabs/rudder-sdk-android/issues/382)) ([55521b6](https://github.com/rudderlabs/rudder-sdk-android/commit/55521b675de289c3c1ba5f80d40d338a61c9aac5)) +* race condition fix using semaphore ([#388](https://github.com/rudderlabs/rudder-sdk-android/issues/388)) ([a792ce2](https://github.com/rudderlabs/rudder-sdk-android/commit/a792ce26514b31d82317eb59f16a97979ddfc13c)) + ### [1.21.2](https://github.com/rudderlabs/rudder-sdk-android/compare/v1.21.1...v1.21.2) (2024-01-25) diff --git a/gradle.properties b/gradle.properties index df3e5d1a7..9a59a53eb 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,8 +5,8 @@ android.enableJetifier=true android.enableR8.fullMode=true kotlin.code.style=official GROUP=com.rudderstack.android.sdk -VERSION_CODE=24 -VERSION_NAME=1.21.2 +VERSION_CODE=25 +VERSION_NAME=1.21.3 POM_NAME=Rudderstack SDK for android POM_DESCRIPTION=Rudderstack SDK for android POM_ARTIFACT_ID=core diff --git a/package.json b/package.json index a6f4c8d4a..2fa04df46 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "1.21.2", + "version": "1.21.3", "dependencies": { "properties-reader": "^2.2.0" }