diff --git a/src/androidTest/java/fake/SynchronizerSpyImpl.java b/src/androidTest/java/fake/SynchronizerSpyImpl.java index 81a1c8fdd..39ce46ff3 100644 --- a/src/androidTest/java/fake/SynchronizerSpyImpl.java +++ b/src/androidTest/java/fake/SynchronizerSpyImpl.java @@ -46,6 +46,11 @@ public void synchronizeSplits(long since) { mSynchronizer.synchronizeSplits(); } + @Override + public void synchronizeRuleBasedSegments(long changeNumber) { + mSynchronizer.synchronizeRuleBasedSegments(changeNumber); + } + @Override public void synchronizeSplits() { mSynchronizer.synchronizeSplits(); diff --git a/src/main/java/io/split/android/client/SplitFactoryHelper.java b/src/main/java/io/split/android/client/SplitFactoryHelper.java index af8e850c3..d2cd19d2a 100644 --- a/src/main/java/io/split/android/client/SplitFactoryHelper.java +++ b/src/main/java/io/split/android/client/SplitFactoryHelper.java @@ -45,6 +45,7 @@ import io.split.android.client.service.sseclient.ReconnectBackoffCounter; import io.split.android.client.service.sseclient.SseJwtParser; import io.split.android.client.service.sseclient.feedbackchannel.PushManagerEventBroadcaster; +import io.split.android.client.service.sseclient.notifications.InstantUpdateChangeNotification; import io.split.android.client.service.sseclient.notifications.MySegmentsV2PayloadDecoder; import io.split.android.client.service.sseclient.notifications.NotificationParser; import io.split.android.client.service.sseclient.notifications.NotificationProcessor; @@ -86,6 +87,7 @@ import io.split.android.client.storage.events.PersistentEventsStorage; import io.split.android.client.storage.general.GeneralInfoStorage; import io.split.android.client.storage.impressions.PersistentImpressionsStorage; +import io.split.android.client.storage.rbs.RuleBasedSegmentStorage; import io.split.android.client.storage.splits.SplitsStorage; import io.split.android.client.telemetry.TelemetrySynchronizer; import io.split.android.client.telemetry.TelemetrySynchronizerImpl; @@ -353,7 +355,7 @@ public StreamingComponents buildStreamingComponents(@NonNull SplitTaskExecutor s return new StreamingComponents(); } - BlockingQueue splitsUpdateNotificationQueue = new LinkedBlockingDeque<>(); + BlockingQueue splitsUpdateNotificationQueue = new LinkedBlockingDeque<>(); NotificationParser notificationParser = new NotificationParser(); NotificationProcessor notificationProcessor = new NotificationProcessor(splitTaskExecutor, splitTaskFactory, @@ -419,13 +421,15 @@ SplitUpdatesWorker getSplitUpdatesWorker(SplitClientConfig config, SplitTaskExecutor splitTaskExecutor, SplitTaskFactory splitTaskFactory, Synchronizer mSynchronizer, - BlockingQueue splitsUpdateNotificationQueue, + BlockingQueue splitsUpdateNotificationQueue, SplitsStorage splitsStorage, + RuleBasedSegmentStorage ruleBasedSegmentStorage, CompressionUtilProvider compressionProvider) { if (config.syncEnabled()) { return new SplitUpdatesWorker(mSynchronizer, splitsUpdateNotificationQueue, splitsStorage, + ruleBasedSegmentStorage, compressionProvider, splitTaskExecutor, splitTaskFactory); diff --git a/src/main/java/io/split/android/client/SplitFactoryImpl.java b/src/main/java/io/split/android/client/SplitFactoryImpl.java index 0fc89a587..fcae80ee2 100644 --- a/src/main/java/io/split/android/client/SplitFactoryImpl.java +++ b/src/main/java/io/split/android/client/SplitFactoryImpl.java @@ -241,6 +241,7 @@ private SplitFactoryImpl(@NonNull String apiToken, @NonNull Key key, @NonNull Sp mSynchronizer, streamingComponents.getSplitsUpdateNotificationQueue(), mStorageContainer.getSplitsStorage(), + mStorageContainer.getRuleBasedSegmentStorage(), compressionProvider), streamingComponents.getSyncGuardian()); diff --git a/src/main/java/io/split/android/client/events/SplitInternalEvent.java b/src/main/java/io/split/android/client/events/SplitInternalEvent.java index eaa25767d..ab070f2f6 100644 --- a/src/main/java/io/split/android/client/events/SplitInternalEvent.java +++ b/src/main/java/io/split/android/client/events/SplitInternalEvent.java @@ -16,4 +16,5 @@ public enum SplitInternalEvent { ATTRIBUTES_LOADED_FROM_STORAGE, ENCRYPTION_MIGRATION_DONE, MY_LARGE_SEGMENTS_UPDATED, + RULE_BASED_SEGMENTS_UPDATED, } diff --git a/src/main/java/io/split/android/client/service/executor/SplitTaskFactory.java b/src/main/java/io/split/android/client/service/executor/SplitTaskFactory.java index bf5225f03..eb9d973b7 100644 --- a/src/main/java/io/split/android/client/service/executor/SplitTaskFactory.java +++ b/src/main/java/io/split/android/client/service/executor/SplitTaskFactory.java @@ -1,5 +1,6 @@ package io.split.android.client.service.executor; +import io.split.android.client.dtos.RuleBasedSegment; import io.split.android.client.dtos.Split; import io.split.android.client.service.CleanUpDatabaseTask; import io.split.android.client.service.events.EventsRecorderTask; @@ -7,6 +8,7 @@ import io.split.android.client.service.rules.LoadRuleBasedSegmentsTask; import io.split.android.client.service.splits.FilterSplitsInCacheTask; import io.split.android.client.service.splits.LoadSplitsTask; +import io.split.android.client.service.splits.RuleBasedSegmentInPlaceUpdateTask; import io.split.android.client.service.splits.SplitInPlaceUpdateTask; import io.split.android.client.service.splits.SplitKillTask; import io.split.android.client.service.splits.SplitsSyncTask; @@ -37,4 +39,6 @@ public interface SplitTaskFactory extends TelemetryTaskFactory, ImpressionsTaskF CleanUpDatabaseTask createCleanUpDatabaseTask(long maxTimestamp); EncryptionMigrationTask createEncryptionMigrationTask(String sdkKey, SplitRoomDatabase splitRoomDatabase, boolean encryptionEnabled, SplitCipher splitCipher); + + RuleBasedSegmentInPlaceUpdateTask createRuleBasedSegmentUpdateTask(RuleBasedSegment ruleBasedSegment, long changeNumber); } diff --git a/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java b/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java index 67144c104..368f63ebc 100644 --- a/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java +++ b/src/main/java/io/split/android/client/service/executor/SplitTaskFactoryImpl.java @@ -16,6 +16,7 @@ import io.split.android.client.SplitClientConfig; import io.split.android.client.SplitFilter; import io.split.android.client.TestingConfig; +import io.split.android.client.dtos.RuleBasedSegment; import io.split.android.client.dtos.Split; import io.split.android.client.events.ISplitEventsManager; import io.split.android.client.service.CleanUpDatabaseTask; @@ -32,8 +33,10 @@ import io.split.android.client.service.impressions.unique.UniqueKeysRecorderTask; import io.split.android.client.service.impressions.unique.UniqueKeysRecorderTaskConfig; import io.split.android.client.service.rules.LoadRuleBasedSegmentsTask; +import io.split.android.client.service.rules.RuleBasedSegmentChangeProcessor; import io.split.android.client.service.splits.FilterSplitsInCacheTask; import io.split.android.client.service.splits.LoadSplitsTask; +import io.split.android.client.service.splits.RuleBasedSegmentInPlaceUpdateTask; import io.split.android.client.service.splits.SplitChangeProcessor; import io.split.android.client.service.splits.SplitInPlaceUpdateTask; import io.split.android.client.service.splits.SplitKillTask; @@ -64,6 +67,7 @@ public class SplitTaskFactoryImpl implements SplitTaskFactory { private final ISplitEventsManager mEventsManager; private final TelemetryTaskFactory mTelemetryTaskFactory; private final SplitChangeProcessor mSplitChangeProcessor; + private final RuleBasedSegmentChangeProcessor mRuleBasedSegmentChangeProcessor; private final TelemetryRuntimeProducer mTelemetryRuntimeProducer; private final List mFilters; @@ -85,6 +89,7 @@ public SplitTaskFactoryImpl(@NonNull SplitClientConfig splitClientConfig, mFlagsSpecFromConfig = flagsSpecFromConfig; mEventsManager = eventsManager; mSplitChangeProcessor = new SplitChangeProcessor(filters, flagSetsFilter); + mRuleBasedSegmentChangeProcessor = new RuleBasedSegmentChangeProcessor(); RuleBasedSegmentStorageProducer ruleBasedSegmentStorageProducer = mSplitsStorageContainer.getRuleBasedSegmentStorage(); TelemetryStorage telemetryStorage = mSplitsStorageContainer.getTelemetryStorage(); @@ -93,6 +98,7 @@ public SplitTaskFactoryImpl(@NonNull SplitClientConfig splitClientConfig, mSplitsSyncHelper = new SplitsSyncHelper(mSplitApiFacade.getSplitFetcher(), mSplitsStorageContainer.getSplitsStorage(), mSplitChangeProcessor, + mRuleBasedSegmentChangeProcessor, ruleBasedSegmentStorageProducer, mTelemetryRuntimeProducer, new ReconnectBackoffCounter(1, testingConfig.getCdnBackoffTime()), @@ -101,6 +107,7 @@ public SplitTaskFactoryImpl(@NonNull SplitClientConfig splitClientConfig, mSplitsSyncHelper = new SplitsSyncHelper(mSplitApiFacade.getSplitFetcher(), mSplitsStorageContainer.getSplitsStorage(), mSplitChangeProcessor, + mRuleBasedSegmentChangeProcessor, ruleBasedSegmentStorageProducer, mTelemetryRuntimeProducer, flagsSpecFromConfig); @@ -222,6 +229,11 @@ public EncryptionMigrationTask createEncryptionMigrationTask(String sdkKey, Spli return new EncryptionMigrationTask(sdkKey, splitRoomDatabase, encryptionEnabled, splitCipher); } + @Override + public RuleBasedSegmentInPlaceUpdateTask createRuleBasedSegmentUpdateTask(RuleBasedSegment ruleBasedSegment, long changeNumber) { + return new RuleBasedSegmentInPlaceUpdateTask(mSplitsStorageContainer.getRuleBasedSegmentStorage(), mRuleBasedSegmentChangeProcessor, mEventsManager, ruleBasedSegment, changeNumber); + } + @NonNull private TelemetryTaskFactory initializeTelemetryTaskFactory(@NonNull SplitClientConfig splitClientConfig, @Nullable Map filters, TelemetryStorage telemetryStorage) { final TelemetryTaskFactory mTelemetryTaskFactory; diff --git a/src/main/java/io/split/android/client/service/executor/SplitTaskType.java b/src/main/java/io/split/android/client/service/executor/SplitTaskType.java index 7c3a7e647..7842fea6d 100644 --- a/src/main/java/io/split/android/client/service/executor/SplitTaskType.java +++ b/src/main/java/io/split/android/client/service/executor/SplitTaskType.java @@ -9,4 +9,5 @@ public enum SplitTaskType { TELEMETRY_CONFIG_TASK, TELEMETRY_STATS_TASK, SAVE_UNIQUE_KEYS_TASK, UNIQUE_KEYS_RECORDER_TASK, MY_LARGE_SEGMENTS_UPDATE, LOAD_LOCAL_RULE_BASED_SEGMENTS, + RULE_BASED_SEGMENT_SYNC, } diff --git a/src/main/java/io/split/android/client/service/rules/ProcessedRuleBasedSegmentChange.java b/src/main/java/io/split/android/client/service/rules/ProcessedRuleBasedSegmentChange.java new file mode 100644 index 000000000..376c5c9ad --- /dev/null +++ b/src/main/java/io/split/android/client/service/rules/ProcessedRuleBasedSegmentChange.java @@ -0,0 +1,38 @@ +package io.split.android.client.service.rules; + +import java.util.Set; + +import io.split.android.client.dtos.RuleBasedSegment; + +public class ProcessedRuleBasedSegmentChange { + private final Set mActive; + private final Set mArchived; + private final long mChangeNumber; + private final long mUpdateTimestamp; + + public ProcessedRuleBasedSegmentChange(Set active, + Set archived, + long changeNumber, + long updateTimestamp) { + mActive = active; + mArchived = archived; + mChangeNumber = changeNumber; + mUpdateTimestamp = updateTimestamp; + } + + public Set getActive() { + return mActive; + } + + public Set getArchived() { + return mArchived; + } + + public long getChangeNumber() { + return mChangeNumber; + } + + public long getUpdateTimestamp() { + return mUpdateTimestamp; + } +} diff --git a/src/main/java/io/split/android/client/service/rules/RuleBasedSegmentChangeProcessor.java b/src/main/java/io/split/android/client/service/rules/RuleBasedSegmentChangeProcessor.java new file mode 100644 index 000000000..2f6d62fc5 --- /dev/null +++ b/src/main/java/io/split/android/client/service/rules/RuleBasedSegmentChangeProcessor.java @@ -0,0 +1,30 @@ +package io.split.android.client.service.rules; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import io.split.android.client.dtos.RuleBasedSegment; +import io.split.android.client.dtos.Status; + +public class RuleBasedSegmentChangeProcessor { + + public ProcessedRuleBasedSegmentChange process(List segments, long changeNumber) { + Set toAdd = new HashSet<>(); + Set toRemove = new HashSet<>(); + for (RuleBasedSegment segment : segments) { + if (segment.getStatus() == Status.ACTIVE) { + toAdd.add(segment); + } else { + toRemove.add(segment); + } + } + + return new ProcessedRuleBasedSegmentChange(toAdd, toRemove, changeNumber, System.currentTimeMillis()); + } + + public ProcessedRuleBasedSegmentChange process(RuleBasedSegment segment, long changeNumber) { + return process(Collections.singletonList(segment), changeNumber); + } +} diff --git a/src/main/java/io/split/android/client/service/splits/RuleBasedSegmentInPlaceUpdateTask.java b/src/main/java/io/split/android/client/service/splits/RuleBasedSegmentInPlaceUpdateTask.java new file mode 100644 index 000000000..7c20cf88e --- /dev/null +++ b/src/main/java/io/split/android/client/service/splits/RuleBasedSegmentInPlaceUpdateTask.java @@ -0,0 +1,57 @@ +package io.split.android.client.service.splits; + +import static io.split.android.client.utils.Utils.checkNotNull; + +import androidx.annotation.NonNull; + +import io.split.android.client.dtos.RuleBasedSegment; +import io.split.android.client.events.ISplitEventsManager; +import io.split.android.client.events.SplitInternalEvent; +import io.split.android.client.service.executor.SplitTask; +import io.split.android.client.service.executor.SplitTaskExecutionInfo; +import io.split.android.client.service.executor.SplitTaskType; +import io.split.android.client.service.rules.ProcessedRuleBasedSegmentChange; +import io.split.android.client.service.rules.RuleBasedSegmentChangeProcessor; +import io.split.android.client.storage.rbs.RuleBasedSegmentStorage; +import io.split.android.client.utils.logger.Logger; + +public class RuleBasedSegmentInPlaceUpdateTask implements SplitTask { + + private final RuleBasedSegmentStorage mRuleBasedSegmentStorage; + private final long mChangeNumber; + private final RuleBasedSegment mRuleBasedSegment; + private final RuleBasedSegmentChangeProcessor mChangeProcessor; + private final ISplitEventsManager mEventsManager; + + public RuleBasedSegmentInPlaceUpdateTask(@NonNull RuleBasedSegmentStorage ruleBasedSegmentStorage, + @NonNull RuleBasedSegmentChangeProcessor changeProcessor, + @NonNull ISplitEventsManager eventsManager, + @NonNull RuleBasedSegment ruleBasedSegment, + long changeNumber) { + mRuleBasedSegmentStorage = checkNotNull(ruleBasedSegmentStorage); + mRuleBasedSegment = checkNotNull(ruleBasedSegment); + mChangeProcessor = checkNotNull(changeProcessor); + mEventsManager = eventsManager; + mChangeNumber = changeNumber; + } + + @NonNull + @Override + public SplitTaskExecutionInfo execute() { + try { + ProcessedRuleBasedSegmentChange processedChange = mChangeProcessor.process(mRuleBasedSegment, mChangeNumber); + boolean triggerSdkUpdate = mRuleBasedSegmentStorage.update(processedChange.getActive(), processedChange.getArchived(), mChangeNumber); + + if (triggerSdkUpdate) { + mEventsManager.notifyInternalEvent(SplitInternalEvent.RULE_BASED_SEGMENTS_UPDATED); + } + + Logger.v("Updated rule based segment"); + return SplitTaskExecutionInfo.success(SplitTaskType.RULE_BASED_SEGMENT_SYNC); + } catch (Exception ex) { + Logger.e("Could not update rule based segment"); + + return SplitTaskExecutionInfo.error(SplitTaskType.RULE_BASED_SEGMENT_SYNC); + } + } +} diff --git a/src/main/java/io/split/android/client/service/splits/SplitsSyncHelper.java b/src/main/java/io/split/android/client/service/splits/SplitsSyncHelper.java index cdec5df48..f68306b64 100644 --- a/src/main/java/io/split/android/client/service/splits/SplitsSyncHelper.java +++ b/src/main/java/io/split/android/client/service/splits/SplitsSyncHelper.java @@ -8,16 +8,12 @@ import androidx.annotation.VisibleForTesting; import java.util.Collections; -import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; -import java.util.Set; import java.util.concurrent.TimeUnit; -import io.split.android.client.dtos.RuleBasedSegment; import io.split.android.client.dtos.RuleBasedSegmentChange; import io.split.android.client.dtos.SplitChange; -import io.split.android.client.dtos.Status; import io.split.android.client.dtos.TargetingRulesChange; import io.split.android.client.network.SplitHttpHeadersBuilder; import io.split.android.client.service.ServiceConstants; @@ -26,6 +22,8 @@ import io.split.android.client.service.http.HttpFetcher; import io.split.android.client.service.http.HttpFetcherException; import io.split.android.client.service.http.HttpStatus; +import io.split.android.client.service.rules.ProcessedRuleBasedSegmentChange; +import io.split.android.client.service.rules.RuleBasedSegmentChangeProcessor; import io.split.android.client.service.sseclient.BackoffCounter; import io.split.android.client.service.sseclient.ReconnectBackoffCounter; import io.split.android.client.storage.rbs.RuleBasedSegmentStorageProducer; @@ -44,6 +42,7 @@ public class SplitsSyncHelper { private final HttpFetcher mSplitFetcher; private final SplitsStorage mSplitsStorage; private final SplitChangeProcessor mSplitChangeProcessor; + private final RuleBasedSegmentChangeProcessor mRuleBasedSegmentChangeProcessor; private final RuleBasedSegmentStorageProducer mRuleBasedSegmentStorage; private final TelemetryRuntimeProducer mTelemetryRuntimeProducer; private final BackoffCounter mBackoffCounter; @@ -52,12 +51,14 @@ public class SplitsSyncHelper { public SplitsSyncHelper(@NonNull HttpFetcher splitFetcher, @NonNull SplitsStorage splitsStorage, @NonNull SplitChangeProcessor splitChangeProcessor, + @NonNull RuleBasedSegmentChangeProcessor ruleBasedSegmentChangeProcessor, @NonNull RuleBasedSegmentStorageProducer ruleBasedSegmentStorage, @NonNull TelemetryRuntimeProducer telemetryRuntimeProducer, @Nullable String flagsSpec) { this(splitFetcher, splitsStorage, splitChangeProcessor, + ruleBasedSegmentChangeProcessor, ruleBasedSegmentStorage, telemetryRuntimeProducer, new ReconnectBackoffCounter(1, ON_DEMAND_FETCH_BACKOFF_MAX_WAIT), @@ -68,6 +69,7 @@ public SplitsSyncHelper(@NonNull HttpFetcher splitFetcher, public SplitsSyncHelper(@NonNull HttpFetcher splitFetcher, @NonNull SplitsStorage splitsStorage, @NonNull SplitChangeProcessor splitChangeProcessor, + @NonNull RuleBasedSegmentChangeProcessor ruleBasedSegmentChangeProcessor, @NonNull RuleBasedSegmentStorageProducer ruleBasedSegmentStorage, @NonNull TelemetryRuntimeProducer telemetryRuntimeProducer, @NonNull BackoffCounter backoffCounter, @@ -75,6 +77,7 @@ public SplitsSyncHelper(@NonNull HttpFetcher splitFetcher, mSplitFetcher = checkNotNull(splitFetcher); mSplitsStorage = checkNotNull(splitsStorage); mSplitChangeProcessor = checkNotNull(splitChangeProcessor); + mRuleBasedSegmentChangeProcessor = checkNotNull(ruleBasedSegmentChangeProcessor); mRuleBasedSegmentStorage = checkNotNull(ruleBasedSegmentStorage); mTelemetryRuntimeProducer = checkNotNull(telemetryRuntimeProducer); mBackoffCounter = checkNotNull(backoffCounter); @@ -205,17 +208,8 @@ private void updateStorage(boolean clearBeforeUpdate, SplitChange splitChange, R } private void updateRbsStorage(RuleBasedSegmentChange ruleBasedSegmentChange) { - long changeNumber = ruleBasedSegmentChange.getTill(); - Set toAdd = new HashSet<>(); - Set toRemove = new HashSet<>(); - for (RuleBasedSegment segment : ruleBasedSegmentChange.getSegments()) { - if (segment.getStatus() == Status.ACTIVE) { - toAdd.add(segment); - } else { - toRemove.add(segment); - } - } - mRuleBasedSegmentStorage.update(toAdd, toRemove, changeNumber); + ProcessedRuleBasedSegmentChange change = mRuleBasedSegmentChangeProcessor.process(ruleBasedSegmentChange.getSegments(), ruleBasedSegmentChange.getTill()); + mRuleBasedSegmentStorage.update(change.getActive(), change.getArchived(), change.getChangeNumber()); } private void logError(String message) { diff --git a/src/main/java/io/split/android/client/service/sseclient/notifications/InstantUpdateChangeNotification.java b/src/main/java/io/split/android/client/service/sseclient/notifications/InstantUpdateChangeNotification.java new file mode 100644 index 000000000..cff8533d7 --- /dev/null +++ b/src/main/java/io/split/android/client/service/sseclient/notifications/InstantUpdateChangeNotification.java @@ -0,0 +1,62 @@ +package io.split.android.client.service.sseclient.notifications; + +import androidx.annotation.Nullable; + +import com.google.gson.annotations.SerializedName; + +import io.split.android.client.common.CompressionType; + +public abstract class InstantUpdateChangeNotification extends IncomingNotification { + + @SerializedName("changeNumber") + private long changeNumber; + + @SerializedName("pcn") + @Nullable + private Long previousChangeNumber; + + @SerializedName("d") + @Nullable + private String data; + + @SerializedName("c") + @Nullable + private Integer compressionType; + + InstantUpdateChangeNotification() { + + } + + InstantUpdateChangeNotification(long changeNumber) { + this.changeNumber = changeNumber; + } + + public long getChangeNumber() { + return changeNumber; + } + + @Nullable + public Long getPreviousChangeNumber() { + return previousChangeNumber; + } + + @Nullable + public String getData() { + return data; + } + + @Nullable + public CompressionType getCompressionType() { + if (compressionType != null) { + if (compressionType == 0) { + return CompressionType.NONE; + } else if (compressionType == 1) { + return CompressionType.GZIP; + } else if (compressionType == 2) { + return CompressionType.ZLIB; + } + } + + return null; + } +} diff --git a/src/main/java/io/split/android/client/service/sseclient/notifications/NotificationParser.java b/src/main/java/io/split/android/client/service/sseclient/notifications/NotificationParser.java index 3a4dbb8bc..c6e1f87a2 100644 --- a/src/main/java/io/split/android/client/service/sseclient/notifications/NotificationParser.java +++ b/src/main/java/io/split/android/client/service/sseclient/notifications/NotificationParser.java @@ -50,6 +50,11 @@ public SplitsChangeNotification parseSplitUpdate(String jsonData) throws JsonSyn return Json.fromJson(jsonData, SplitsChangeNotification.class); } + @NonNull + public RuleBasedSegmentChangeNotification parseRuleBasedSegmentUpdate(String notificationJson) { + return Json.fromJson(notificationJson, RuleBasedSegmentChangeNotification.class); + } + @NonNull public SplitKillNotification parseSplitKill(String jsonData) throws JsonSyntaxException { return Json.fromJson(jsonData, SplitKillNotification.class); diff --git a/src/main/java/io/split/android/client/service/sseclient/notifications/NotificationProcessor.java b/src/main/java/io/split/android/client/service/sseclient/notifications/NotificationProcessor.java index 6eef5c597..a5b2ff7fc 100644 --- a/src/main/java/io/split/android/client/service/sseclient/notifications/NotificationProcessor.java +++ b/src/main/java/io/split/android/client/service/sseclient/notifications/NotificationProcessor.java @@ -23,14 +23,14 @@ public class NotificationProcessor implements MySegmentsNotificationProcessorReg private final NotificationParser mNotificationParser; private final SplitTaskExecutor mSplitTaskExecutor; private final SplitTaskFactory mSplitTaskFactory; - private final BlockingQueue mSplitsUpdateNotificationsQueue; + private final BlockingQueue mSplitsUpdateNotificationsQueue; private final ConcurrentMap mMembershipsNotificationProcessors; public NotificationProcessor( @NonNull SplitTaskExecutor splitTaskExecutor, @NonNull SplitTaskFactory splitTaskFactory, @NonNull NotificationParser notificationParser, - @NonNull BlockingQueue splitsUpdateNotificationsQueue) { + @NonNull BlockingQueue splitsUpdateNotificationsQueue) { mSplitTaskExecutor = checkNotNull(splitTaskExecutor); mSplitTaskFactory = checkNotNull(splitTaskFactory); mNotificationParser = checkNotNull(notificationParser); @@ -45,6 +45,9 @@ public void process(IncomingNotification incomingNotification) { case SPLIT_UPDATE: processSplitUpdate(mNotificationParser.parseSplitUpdate(notificationJson)); break; + case RULE_BASED_SEGMENT_UPDATE: + processRuleBasedSegmentUpdate(mNotificationParser.parseRuleBasedSegmentUpdate(notificationJson)); + break; case SPLIT_KILL: processSplitKill(mNotificationParser.parseSplitKill(notificationJson)); break; @@ -75,7 +78,12 @@ public void unregisterMembershipsProcessor(String matchingKey) { } private void processSplitUpdate(SplitsChangeNotification notification) { - Logger.d("Received split change notification"); + Logger.d("Received feature flag change notification"); + mSplitsUpdateNotificationsQueue.offer(notification); + } + + private void processRuleBasedSegmentUpdate(RuleBasedSegmentChangeNotification notification) { + Logger.d("Received rule based segment change notification"); mSplitsUpdateNotificationsQueue.offer(notification); } diff --git a/src/main/java/io/split/android/client/service/sseclient/notifications/NotificationType.java b/src/main/java/io/split/android/client/service/sseclient/notifications/NotificationType.java index fb38d5859..c02a4fcfd 100644 --- a/src/main/java/io/split/android/client/service/sseclient/notifications/NotificationType.java +++ b/src/main/java/io/split/android/client/service/sseclient/notifications/NotificationType.java @@ -18,4 +18,7 @@ public enum NotificationType { MEMBERSHIPS_LS_UPDATE, @SerializedName("MEMBERSHIPS_MS_UPDATE") MEMBERSHIPS_MS_UPDATE, + + @SerializedName("RB_SEGMENT_UPDATE") + RULE_BASED_SEGMENT_UPDATE, } diff --git a/src/main/java/io/split/android/client/service/sseclient/notifications/RuleBasedSegmentChangeNotification.java b/src/main/java/io/split/android/client/service/sseclient/notifications/RuleBasedSegmentChangeNotification.java new file mode 100644 index 000000000..5eb1e68a4 --- /dev/null +++ b/src/main/java/io/split/android/client/service/sseclient/notifications/RuleBasedSegmentChangeNotification.java @@ -0,0 +1,8 @@ +package io.split.android.client.service.sseclient.notifications; + +public class RuleBasedSegmentChangeNotification extends InstantUpdateChangeNotification { + + public RuleBasedSegmentChangeNotification(long changeNumber) { + super(changeNumber); + } +} diff --git a/src/main/java/io/split/android/client/service/sseclient/notifications/SplitsChangeNotification.java b/src/main/java/io/split/android/client/service/sseclient/notifications/SplitsChangeNotification.java index 3c22888fe..912a612bc 100644 --- a/src/main/java/io/split/android/client/service/sseclient/notifications/SplitsChangeNotification.java +++ b/src/main/java/io/split/android/client/service/sseclient/notifications/SplitsChangeNotification.java @@ -1,62 +1,8 @@ package io.split.android.client.service.sseclient.notifications; -import androidx.annotation.Nullable; - -import com.google.gson.annotations.SerializedName; - -import io.split.android.client.common.CompressionType; - -public class SplitsChangeNotification extends IncomingNotification { - - @SerializedName("changeNumber") - private long changeNumber; - - @SerializedName("pcn") - @Nullable - private Long previousChangeNumber; - - @SerializedName("d") - @Nullable - private String data; - - @SerializedName("c") - @Nullable - private Integer compressionType; - - public SplitsChangeNotification() { - - } +public class SplitsChangeNotification extends InstantUpdateChangeNotification { public SplitsChangeNotification(long changeNumber) { - this.changeNumber = changeNumber; - } - - public long getChangeNumber() { - return changeNumber; - } - - @Nullable - public Long getPreviousChangeNumber() { - return previousChangeNumber; - } - - @Nullable - public String getData() { - return data; - } - - @Nullable - public CompressionType getCompressionType() { - if (compressionType != null) { - if (compressionType == 0) { - return CompressionType.NONE; - } else if (compressionType == 1) { - return CompressionType.GZIP; - } else if (compressionType == 2) { - return CompressionType.ZLIB; - } - } - - return null; + super(changeNumber); } } diff --git a/src/main/java/io/split/android/client/service/sseclient/reactor/SplitUpdatesWorker.java b/src/main/java/io/split/android/client/service/sseclient/reactor/SplitUpdatesWorker.java index ef932609a..b60032033 100644 --- a/src/main/java/io/split/android/client/service/sseclient/reactor/SplitUpdatesWorker.java +++ b/src/main/java/io/split/android/client/service/sseclient/reactor/SplitUpdatesWorker.java @@ -9,14 +9,18 @@ import java.util.concurrent.BlockingQueue; import io.split.android.client.common.CompressionUtilProvider; +import io.split.android.client.dtos.RuleBasedSegment; import io.split.android.client.dtos.Split; +import io.split.android.client.service.executor.SplitTask; import io.split.android.client.service.executor.SplitTaskExecutionInfo; import io.split.android.client.service.executor.SplitTaskExecutionListener; import io.split.android.client.service.executor.SplitTaskExecutionStatus; import io.split.android.client.service.executor.SplitTaskExecutor; import io.split.android.client.service.executor.SplitTaskFactory; -import io.split.android.client.service.sseclient.notifications.SplitsChangeNotification; +import io.split.android.client.service.sseclient.notifications.InstantUpdateChangeNotification; +import io.split.android.client.service.sseclient.notifications.NotificationType; import io.split.android.client.service.synchronizer.Synchronizer; +import io.split.android.client.storage.rbs.RuleBasedSegmentStorage; import io.split.android.client.storage.splits.SplitsStorage; import io.split.android.client.utils.Base64Util; import io.split.android.client.utils.CompressionUtil; @@ -28,24 +32,26 @@ public class SplitUpdatesWorker extends UpdateWorker { /*** * This class will be in charge of update splits when a new notification arrived. */ - - private final BlockingQueue mNotificationsQueue; + private final BlockingQueue mNotificationsQueue; private final Synchronizer mSynchronizer; private final SplitsStorage mSplitsStorage; + private final RuleBasedSegmentStorage mRuleBasedSegmentStorage; private final CompressionUtilProvider mCompressionUtilProvider; private final SplitTaskExecutor mSplitTaskExecutor; private final SplitTaskFactory mSplitTaskFactory; private final Base64Decoder mBase64Decoder; public SplitUpdatesWorker(@NonNull Synchronizer synchronizer, - @NonNull BlockingQueue notificationsQueue, + @NonNull BlockingQueue notificationsQueue, @NonNull SplitsStorage splitsStorage, + @NonNull RuleBasedSegmentStorage ruleBasedSegmentStorage, @NonNull CompressionUtilProvider compressionUtilProvider, @NonNull SplitTaskExecutor splitTaskExecutor, @NonNull SplitTaskFactory splitTaskFactory) { this(synchronizer, notificationsQueue, splitsStorage, + ruleBasedSegmentStorage, compressionUtilProvider, splitTaskExecutor, splitTaskFactory, @@ -54,8 +60,9 @@ public SplitUpdatesWorker(@NonNull Synchronizer synchronizer, @VisibleForTesting public SplitUpdatesWorker(@NonNull Synchronizer synchronizer, - @NonNull BlockingQueue notificationsQueue, + @NonNull BlockingQueue notificationsQueue, @NonNull SplitsStorage splitsStorage, + @NonNull RuleBasedSegmentStorage ruleBasedSegmentStorage, @NonNull CompressionUtilProvider compressionUtilProvider, @NonNull SplitTaskExecutor splitTaskExecutor, @NonNull SplitTaskFactory splitTaskFactory, @@ -64,6 +71,7 @@ public SplitUpdatesWorker(@NonNull Synchronizer synchronizer, mSynchronizer = checkNotNull(synchronizer); mNotificationsQueue = checkNotNull(notificationsQueue); mSplitsStorage = checkNotNull(splitsStorage); + mRuleBasedSegmentStorage = checkNotNull(ruleBasedSegmentStorage); mCompressionUtilProvider = checkNotNull(compressionUtilProvider); mSplitTaskExecutor = checkNotNull(splitTaskExecutor); mSplitTaskFactory = checkNotNull(splitTaskFactory); @@ -73,7 +81,7 @@ public SplitUpdatesWorker(@NonNull Synchronizer synchronizer, @Override protected void onWaitForNotificationLoop() throws InterruptedException { try { - SplitsChangeNotification notification = mNotificationsQueue.take(); + InstantUpdateChangeNotification notification = mNotificationsQueue.take(); Logger.d("A new notification to update feature flags has been received"); long storageChangeNumber = mSplitsStorage.getTill(); @@ -83,7 +91,7 @@ protected void onWaitForNotificationLoop() throws InterruptedException { } if (isLegacyNotification(notification) || isInvalidChangeNumber(notification, storageChangeNumber)) { - handleLegacyNotification(notification.getChangeNumber()); + handleLegacyNotification(notification); } else { handleNotification(notification); } @@ -93,48 +101,57 @@ protected void onWaitForNotificationLoop() throws InterruptedException { } } - private static boolean isInvalidChangeNumber(SplitsChangeNotification notification, long storageChangeNumber) { + private static boolean isInvalidChangeNumber(InstantUpdateChangeNotification notification, long storageChangeNumber) { return notification.getPreviousChangeNumber() == null || notification.getPreviousChangeNumber() == 0 || storageChangeNumber != notification.getPreviousChangeNumber(); } - private static boolean isLegacyNotification(SplitsChangeNotification notification) { + private static boolean isLegacyNotification(InstantUpdateChangeNotification notification) { return notification.getData() == null || notification.getCompressionType() == null; } - private void handleLegacyNotification(long changeNumber) { - mSynchronizer.synchronizeSplits(changeNumber); - Logger.d("Enqueuing polling task"); - } - - private void handleNotification(SplitsChangeNotification notification) { + private void handleNotification(InstantUpdateChangeNotification notification) { String decompressed = decompressData(notification.getData(), mCompressionUtilProvider.get(notification.getCompressionType())); if (decompressed == null) { - handleLegacyNotification(notification.getChangeNumber()); + handleLegacyNotification(notification); return; } try { - Split split = Json.fromJson(decompressed, Split.class); - - mSplitTaskExecutor.submit( - mSplitTaskFactory.createSplitsUpdateTask(split, notification.getChangeNumber()), - new SplitTaskExecutionListener() { - @Override - public void taskExecuted(@NonNull SplitTaskExecutionInfo taskInfo) { - if (taskInfo.getStatus() == SplitTaskExecutionStatus.ERROR) { - handleLegacyNotification(notification.getChangeNumber()); - } - } - }); + inPlaceUpdate(notification, decompressed); } catch (Exception e) { Logger.e("Could not parse feature flag"); - handleLegacyNotification(notification.getChangeNumber()); + handleLegacyNotification(notification); + } + } + + private void inPlaceUpdate(InstantUpdateChangeNotification notification, String decompressed) { + SplitTask updateTask = (notification.getType() == NotificationType.RULE_BASED_SEGMENT_UPDATE) ? + mSplitTaskFactory.createRuleBasedSegmentUpdateTask(Json.fromJson(decompressed, RuleBasedSegment.class), notification.getChangeNumber()) : + mSplitTaskFactory.createSplitsUpdateTask(Json.fromJson(decompressed, Split.class), notification.getChangeNumber()); + SplitTaskExecutionListener executionListener = new SplitTaskExecutionListener() { + @Override + public void taskExecuted(@NonNull SplitTaskExecutionInfo taskInfo) { + if (taskInfo.getStatus() == SplitTaskExecutionStatus.ERROR) { + handleLegacyNotification(notification); + } + } + }; + + mSplitTaskExecutor.submit(updateTask, executionListener); + } + + private void handleLegacyNotification(InstantUpdateChangeNotification notification) { + if (notification.getType() == NotificationType.RULE_BASED_SEGMENT_UPDATE) { + mSynchronizer.synchronizeRuleBasedSegments(notification.getChangeNumber()); + } else { + mSynchronizer.synchronizeSplits(notification.getChangeNumber()); } + Logger.d("Enqueuing polling task"); } @Nullable diff --git a/src/main/java/io/split/android/client/service/sseclient/sseclient/StreamingComponents.java b/src/main/java/io/split/android/client/service/sseclient/sseclient/StreamingComponents.java index fc05691aa..5117fd973 100644 --- a/src/main/java/io/split/android/client/service/sseclient/sseclient/StreamingComponents.java +++ b/src/main/java/io/split/android/client/service/sseclient/sseclient/StreamingComponents.java @@ -3,15 +3,15 @@ import java.util.concurrent.BlockingQueue; import io.split.android.client.service.sseclient.feedbackchannel.PushManagerEventBroadcaster; +import io.split.android.client.service.sseclient.notifications.InstantUpdateChangeNotification; import io.split.android.client.service.sseclient.notifications.NotificationParser; import io.split.android.client.service.sseclient.notifications.NotificationProcessor; -import io.split.android.client.service.sseclient.notifications.SplitsChangeNotification; import io.split.android.client.service.synchronizer.SyncGuardian; public class StreamingComponents { private PushNotificationManager mPushNotificationManager; - private BlockingQueue mSplitsUpdateNotificationQueue; + private BlockingQueue mSplitsUpdateNotificationQueue; private PushManagerEventBroadcaster mPushManagerEventBroadcaster; private NotificationParser mNotificationParser; private NotificationProcessor mNotificationProcessor; @@ -22,7 +22,7 @@ public StreamingComponents() { } public StreamingComponents(PushNotificationManager pushNotificationManager, - BlockingQueue splitsUpdateNotificationQueue, + BlockingQueue splitsUpdateNotificationQueue, NotificationParser notificationParser, NotificationProcessor notificationProcessor, SseAuthenticator sseAuthenticator, @@ -41,7 +41,7 @@ public PushNotificationManager getPushNotificationManager() { return mPushNotificationManager; } - public BlockingQueue getSplitsUpdateNotificationQueue() { + public BlockingQueue getSplitsUpdateNotificationQueue() { return mSplitsUpdateNotificationQueue; } diff --git a/src/main/java/io/split/android/client/service/synchronizer/Synchronizer.java b/src/main/java/io/split/android/client/service/synchronizer/Synchronizer.java index b16331b83..68bff6404 100644 --- a/src/main/java/io/split/android/client/service/synchronizer/Synchronizer.java +++ b/src/main/java/io/split/android/client/service/synchronizer/Synchronizer.java @@ -16,6 +16,8 @@ public interface Synchronizer extends SplitLifecycleAware { void synchronizeSplits(); + void synchronizeRuleBasedSegments(long changeNumber); + void synchronizeMySegments(); void startPeriodicFetching(); diff --git a/src/main/java/io/split/android/client/service/synchronizer/SynchronizerImpl.java b/src/main/java/io/split/android/client/service/synchronizer/SynchronizerImpl.java index 9d8fcd010..ddfd906e8 100644 --- a/src/main/java/io/split/android/client/service/synchronizer/SynchronizerImpl.java +++ b/src/main/java/io/split/android/client/service/synchronizer/SynchronizerImpl.java @@ -155,7 +155,12 @@ public void loadAndSynchronizeSplits() { @Override public void synchronizeSplits(long since) { - mFeatureFlagsSynchronizer.synchronize(since, null); // TODO + mFeatureFlagsSynchronizer.synchronize(since, null); + } + + @Override + public void synchronizeRuleBasedSegments(long changeNumber) { + mFeatureFlagsSynchronizer.synchronize(null, changeNumber); } @Override diff --git a/src/main/java/io/split/android/client/service/workmanager/splits/SplitsSyncWorker.java b/src/main/java/io/split/android/client/service/workmanager/splits/SplitsSyncWorker.java index d5f3f6d27..0d69c59f6 100644 --- a/src/main/java/io/split/android/client/service/workmanager/splits/SplitsSyncWorker.java +++ b/src/main/java/io/split/android/client/service/workmanager/splits/SplitsSyncWorker.java @@ -6,6 +6,7 @@ import androidx.annotation.WorkerThread; import androidx.work.WorkerParameters; +import io.split.android.client.service.rules.RuleBasedSegmentChangeProcessor; import io.split.android.client.service.workmanager.SplitWorker; public class SplitsSyncWorker extends SplitWorker { @@ -21,6 +22,7 @@ public SplitsSyncWorker(@NonNull Context context, new StorageProvider(getDatabase(), params.apiKey(), params.encryptionEnabled(), params.shouldRecordTelemetry()), new FetcherProvider(getHttpClient(), getEndPoint()), new SplitChangeProcessorProvider().provideSplitChangeProcessor(params.configuredFilterType(), params.configuredFilterValues()), + new RuleBasedSegmentChangeProcessor(), new SyncHelperProvider(), params.flagsSpec()); diff --git a/src/main/java/io/split/android/client/service/workmanager/splits/SplitsSyncWorkerTaskBuilder.java b/src/main/java/io/split/android/client/service/workmanager/splits/SplitsSyncWorkerTaskBuilder.java index 68d3466cd..4c76c5f01 100644 --- a/src/main/java/io/split/android/client/service/workmanager/splits/SplitsSyncWorkerTaskBuilder.java +++ b/src/main/java/io/split/android/client/service/workmanager/splits/SplitsSyncWorkerTaskBuilder.java @@ -3,6 +3,7 @@ import java.net.URISyntaxException; import io.split.android.client.service.executor.SplitTask; +import io.split.android.client.service.rules.RuleBasedSegmentChangeProcessor; import io.split.android.client.service.splits.SplitChangeProcessor; import io.split.android.client.service.splits.SplitsSyncHelper; import io.split.android.client.service.splits.SplitsSyncTask; @@ -19,18 +20,21 @@ class SplitsSyncWorkerTaskBuilder { private final StorageProvider mStorageProvider; private final FetcherProvider mFetcherProvider; private final SplitChangeProcessor mSplitChangeProcessor; + private final RuleBasedSegmentChangeProcessor mRuleBasedSegmentChangeProcessor; private final SyncHelperProvider mSplitsSyncHelperProvider; private final String mFlagsSpec; SplitsSyncWorkerTaskBuilder(StorageProvider storageProvider, FetcherProvider fetcherProvider, SplitChangeProcessor splitChangeProcessor, + RuleBasedSegmentChangeProcessor ruleBasedSegmentChangeProcessor, SyncHelperProvider splitsSyncHelperProvider, String flagsSpec) { mStorageProvider = storageProvider; mFetcherProvider = fetcherProvider; mSplitsSyncHelperProvider = splitsSyncHelperProvider; mSplitChangeProcessor = splitChangeProcessor; + mRuleBasedSegmentChangeProcessor = ruleBasedSegmentChangeProcessor; mFlagsSpec = flagsSpec; } @@ -45,6 +49,7 @@ SplitTask getTask() { mFetcherProvider.provideFetcher(splitsFilterQueryString), splitsStorage, mSplitChangeProcessor, + mRuleBasedSegmentChangeProcessor, ruleBasedSegmentStorageProducer, telemetryStorage, mFlagsSpec); diff --git a/src/main/java/io/split/android/client/service/workmanager/splits/SyncHelperProvider.java b/src/main/java/io/split/android/client/service/workmanager/splits/SyncHelperProvider.java index 7aa87c948..640949301 100644 --- a/src/main/java/io/split/android/client/service/workmanager/splits/SyncHelperProvider.java +++ b/src/main/java/io/split/android/client/service/workmanager/splits/SyncHelperProvider.java @@ -2,6 +2,7 @@ import io.split.android.client.dtos.TargetingRulesChange; import io.split.android.client.service.http.HttpFetcher; +import io.split.android.client.service.rules.RuleBasedSegmentChangeProcessor; import io.split.android.client.service.splits.SplitChangeProcessor; import io.split.android.client.service.splits.SplitsSyncHelper; import io.split.android.client.storage.rbs.RuleBasedSegmentStorageProducer; @@ -13,11 +14,14 @@ class SyncHelperProvider { SplitsSyncHelper provideSplitsSyncHelper(HttpFetcher splitsFetcher, SplitsStorage splitsStorage, SplitChangeProcessor splitChangeProcessor, + RuleBasedSegmentChangeProcessor ruleBasedSegmentChangeProcessor, RuleBasedSegmentStorageProducer ruleBasedSegmentStorage, TelemetryStorage telemetryStorage, String mFlagsSpec) { - return new SplitsSyncHelper(splitsFetcher, splitsStorage, + return new SplitsSyncHelper(splitsFetcher, + splitsStorage, splitChangeProcessor, + ruleBasedSegmentChangeProcessor, ruleBasedSegmentStorage, telemetryStorage, mFlagsSpec); diff --git a/src/test/java/io/split/android/client/service/SplitsSyncHelperTest.java b/src/test/java/io/split/android/client/service/SplitsSyncHelperTest.java index 26dd0b493..4b87049ca 100644 --- a/src/test/java/io/split/android/client/service/SplitsSyncHelperTest.java +++ b/src/test/java/io/split/android/client/service/SplitsSyncHelperTest.java @@ -3,6 +3,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyMap; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; @@ -28,6 +29,7 @@ import java.util.List; import java.util.Map; +import io.split.android.client.dtos.RuleBasedSegment; import io.split.android.client.dtos.RuleBasedSegmentChange; import io.split.android.client.dtos.SplitChange; import io.split.android.client.dtos.TargetingRulesChange; @@ -36,6 +38,7 @@ import io.split.android.client.service.executor.SplitTaskExecutionStatus; import io.split.android.client.service.http.HttpFetcher; import io.split.android.client.service.http.HttpFetcherException; +import io.split.android.client.service.rules.RuleBasedSegmentChangeProcessor; import io.split.android.client.service.splits.SplitChangeProcessor; import io.split.android.client.service.splits.SplitsSyncHelper; import io.split.android.client.service.sseclient.BackoffCounter; @@ -56,6 +59,8 @@ public class SplitsSyncHelperTest { TargetingRulesChange mTargetingRulesChange = TargetingRulesChange.create(SplitChange.create(-1, 1506703262916L, Collections.emptyList()), RuleBasedSegmentChange.create(-1, 262325L, Collections.singletonList(RuleBasedSegmentStorageImplTest.createRuleBasedSegment("rbs")))); @Spy SplitChangeProcessor mSplitChangeProcessor; + @Spy + RuleBasedSegmentChangeProcessor mRuleBasedSegmentChangeProcessor; @Mock private TelemetryRuntimeProducer mTelemetryRuntimeProducer; @Mock @@ -76,7 +81,7 @@ public void setup() { mDefaultParams = getSinceParams(-1, -1); mSecondFetchParams = getSinceParams(1506703262916L, 262325L); when(mRuleBasedSegmentStorageProducer.getChangeNumber()).thenReturn(-1L).thenReturn(262325L); - mSplitsSyncHelper = new SplitsSyncHelper(mSplitsFetcher, mSplitsStorage, mSplitChangeProcessor, mRuleBasedSegmentStorageProducer, mTelemetryRuntimeProducer, mBackoffCounter, "1.1"); + mSplitsSyncHelper = new SplitsSyncHelper(mSplitsFetcher, mSplitsStorage, mSplitChangeProcessor, mRuleBasedSegmentChangeProcessor, mRuleBasedSegmentStorageProducer, mTelemetryRuntimeProducer, mBackoffCounter, "1.1"); loadSplitChanges(); } @@ -105,6 +110,7 @@ public void correctSyncExecution() throws HttpFetcherException { verify(mSplitsFetcher, times(1)).execute(mDefaultParams, null); verify(mSplitsStorage, times(1)).update(any()); verify(mSplitChangeProcessor, times(1)).process(mTargetingRulesChange.getFeatureFlagsChange()); + verify(mRuleBasedSegmentChangeProcessor).process((List) any(), anyLong()); verify(mRuleBasedSegmentStorageProducer, times(1)).update(any(), any(), eq(262325L)); verify(mSplitsStorage, never()).clear(); verify(mRuleBasedSegmentStorageProducer, never()).clear(); @@ -221,8 +227,6 @@ public void performSplitFetchUntilStoredChangeNumberIsGreaterThanRequested() thr TargetingRulesChange firstSplitChange = getSplitChange(-1, 2); TargetingRulesChange secondSplitChange = getSplitChange(2, 4); TargetingRulesChange thirdSplitChange = getSplitChange(4, 4); - Map firstParams = getSinceParams(-1L); - Map secondParams = getSinceParams(2L); when(mSplitsStorage.getTill()).thenReturn(-1L, 2L, 4L); diff --git a/src/test/java/io/split/android/client/service/rules/RuleBasedSegmentChangeProcessorTest.java b/src/test/java/io/split/android/client/service/rules/RuleBasedSegmentChangeProcessorTest.java new file mode 100644 index 000000000..5cbdf45ef --- /dev/null +++ b/src/test/java/io/split/android/client/service/rules/RuleBasedSegmentChangeProcessorTest.java @@ -0,0 +1,41 @@ +package io.split.android.client.service.rules; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static io.split.android.client.storage.rbs.RuleBasedSegmentStorageImplTest.createRuleBasedSegment; + +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import io.split.android.client.dtos.RuleBasedSegment; +import io.split.android.client.dtos.Status; + +public class RuleBasedSegmentChangeProcessorTest { + + private RuleBasedSegmentChangeProcessor mProcessor; + + @Before + public void setUp() { + mProcessor = new RuleBasedSegmentChangeProcessor(); + } + + @Test + public void testProcess() { + List segments = new ArrayList<>(); + segments.add(createRuleBasedSegment("segment1", Status.ACTIVE)); + segments.add(createRuleBasedSegment("segment2", Status.ARCHIVED)); + segments.add(createRuleBasedSegment("segment3", Status.ACTIVE)); + + ProcessedRuleBasedSegmentChange result = mProcessor.process(segments, 123L); + + assertEquals(2, result.getActive().size()); + assertEquals(1, result.getArchived().size()); + assertEquals(123L, result.getChangeNumber()); + assertTrue(result.getActive().stream().map(RuleBasedSegment::getName).anyMatch("segment1"::equals)); + assertTrue(result.getActive().stream().map(RuleBasedSegment::getName).anyMatch("segment3"::equals)); + assertTrue(result.getArchived().stream().map(RuleBasedSegment::getName).anyMatch("segment2"::equals)); + } +} diff --git a/src/test/java/io/split/android/client/service/sseclient/NotificationProcessorTest.java b/src/test/java/io/split/android/client/service/sseclient/NotificationProcessorTest.java index 863d7805e..803a6cc72 100644 --- a/src/test/java/io/split/android/client/service/sseclient/NotificationProcessorTest.java +++ b/src/test/java/io/split/android/client/service/sseclient/NotificationProcessorTest.java @@ -21,6 +21,7 @@ import io.split.android.client.service.executor.SplitTaskFactory; import io.split.android.client.service.splits.SplitKillTask; import io.split.android.client.service.sseclient.notifications.IncomingNotification; +import io.split.android.client.service.sseclient.notifications.InstantUpdateChangeNotification; import io.split.android.client.service.sseclient.notifications.MembershipNotification; import io.split.android.client.service.sseclient.notifications.NotificationParser; import io.split.android.client.service.sseclient.notifications.NotificationProcessor; @@ -38,7 +39,7 @@ public class NotificationProcessorTest { @Mock private NotificationParser mNotificationParser; @Mock - private BlockingQueue mSplitsChangeQueue; + private BlockingQueue mSplitsChangeQueue; @Mock private IncomingNotification mIncomingNotification; private NotificationProcessor mNotificationProcessor; diff --git a/src/test/java/io/split/android/client/service/sseclient/SplitUpdateWorkerTest.java b/src/test/java/io/split/android/client/service/sseclient/SplitUpdateWorkerTest.java index 03de536c7..d06f2e53e 100644 --- a/src/test/java/io/split/android/client/service/sseclient/SplitUpdateWorkerTest.java +++ b/src/test/java/io/split/android/client/service/sseclient/SplitUpdateWorkerTest.java @@ -29,16 +29,18 @@ import io.split.android.client.service.executor.SplitTaskFactory; import io.split.android.client.service.executor.SplitTaskType; import io.split.android.client.service.splits.SplitInPlaceUpdateTask; +import io.split.android.client.service.sseclient.notifications.InstantUpdateChangeNotification; import io.split.android.client.service.sseclient.notifications.SplitsChangeNotification; import io.split.android.client.service.sseclient.reactor.SplitUpdatesWorker; import io.split.android.client.service.synchronizer.Synchronizer; +import io.split.android.client.storage.rbs.RuleBasedSegmentStorage; import io.split.android.client.storage.splits.SplitsStorage; import io.split.android.client.utils.CompressionUtil; import io.split.android.fake.SplitTaskExecutorStub; public class SplitUpdateWorkerTest { - BlockingQueue mNotificationsQueue; + BlockingQueue mNotificationsQueue; SplitUpdatesWorker mWorker; @@ -54,6 +56,8 @@ public class SplitUpdateWorkerTest { private SplitTaskFactory mSplitTaskFactory; @Mock private SplitUpdatesWorker.Base64Decoder mBase64Decoder; + @Mock + private RuleBasedSegmentStorage mRuleBasedSegmentStorage; private static final String TEST_SPLIT = "{\"trafficTypeName\":\"account\",\"name\":\"android_test_2\",\"trafficAllocation\":100,\"trafficAllocationSeed\":-1955610140,\"seed\":-633015570,\"status\":\"ACTIVE\",\"killed\":false,\"defaultTreatment\":\"off\",\"changeNumber\":1648733409158,\"algo\":2,\"configurations\":{},\"conditions\":[{\"conditionType\":\"ROLLOUT\",\"matcherGroup\":{\"combiner\":\"AND\",\"matchers\":[{\"keySelector\":{\"trafficType\":\"account\",\"attribute\":null},\"matcherType\":\"IN_SPLIT_TREATMENT\",\"negate\":false,\"userDefinedSegmentMatcherData\":null,\"whitelistMatcherData\":null,\"unaryNumericMatcherData\":null,\"betweenMatcherData\":null,\"booleanMatcherData\":null,\"dependencyMatcherData\":{\"split\":\"android_test_3\",\"treatments\":[\"on\"]},\"stringMatcherData\":null}]},\"partitions\":[{\"treatment\":\"on\",\"size\":100},{\"treatment\":\"off\",\"size\":0}],\"label\":\"in split android_test_3 treatment [on]\"},{\"conditionType\":\"ROLLOUT\",\"matcherGroup\":{\"combiner\":\"AND\",\"matchers\":[{\"keySelector\":{\"trafficType\":\"account\",\"attribute\":null},\"matcherType\":\"ALL_KEYS\",\"negate\":false,\"userDefinedSegmentMatcherData\":null,\"whitelistMatcherData\":null,\"unaryNumericMatcherData\":null,\"betweenMatcherData\":null,\"booleanMatcherData\":null,\"dependencyMatcherData\":null,\"stringMatcherData\":null}]},\"partitions\":[{\"treatment\":\"on\",\"size\":0},{\"treatment\":\"off\",\"size\":100}],\"label\":\"default rule\"}]}"; @@ -64,6 +68,7 @@ public void setup() { mWorker = new SplitUpdatesWorker(mSynchronizer, mNotificationsQueue, mSplitsStorage, + mRuleBasedSegmentStorage, mCompressionUtilProvider, mSplitTaskExecutor, mSplitTaskFactory, @@ -316,6 +321,7 @@ private void initWorkerWithStubExecutor() { mWorker = new SplitUpdatesWorker(mSynchronizer, mNotificationsQueue, mSplitsStorage, + mRuleBasedSegmentStorage, mCompressionUtilProvider, new SplitTaskExecutorStub(), mSplitTaskFactory, diff --git a/src/test/java/io/split/android/client/service/workmanager/splits/SplitsSyncWorkerTaskBuilderTest.java b/src/test/java/io/split/android/client/service/workmanager/splits/SplitsSyncWorkerTaskBuilderTest.java index 4178f3046..9220e74b0 100644 --- a/src/test/java/io/split/android/client/service/workmanager/splits/SplitsSyncWorkerTaskBuilderTest.java +++ b/src/test/java/io/split/android/client/service/workmanager/splits/SplitsSyncWorkerTaskBuilderTest.java @@ -7,6 +7,8 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import androidx.annotation.NonNull; + import org.junit.Before; import org.junit.Test; import org.mockito.MockedStatic; @@ -16,6 +18,7 @@ import io.split.android.client.dtos.TargetingRulesChange; import io.split.android.client.service.executor.SplitTask; import io.split.android.client.service.http.HttpFetcher; +import io.split.android.client.service.rules.RuleBasedSegmentChangeProcessor; import io.split.android.client.service.splits.SplitChangeProcessor; import io.split.android.client.service.splits.SplitsSyncHelper; import io.split.android.client.service.splits.SplitsSyncTask; @@ -28,6 +31,7 @@ public class SplitsSyncWorkerTaskBuilderTest { private StorageProvider mStorageProvider; private FetcherProvider mFetcherProvider; private SplitChangeProcessor mSplitChangeProcessor; + private RuleBasedSegmentChangeProcessor mRuleBasedSegmentChangeProcessor; private SyncHelperProvider mSplitsSyncHelperProvider; private SplitsStorage mSplitsStorage; private HttpFetcher mSplitsFetcher; @@ -44,23 +48,19 @@ public void setUp() throws URISyntaxException { mTelemetryStorage = mock(TelemetryStorage.class); mSplitsSyncHelperProvider = mock(SyncHelperProvider.class); mRuleBasedSegmentStorageProducer = mock(RuleBasedSegmentStorageProducer.class); + mRuleBasedSegmentChangeProcessor = mock(RuleBasedSegmentChangeProcessor.class); when(mSplitsStorage.getSplitsFilterQueryString()).thenReturn("filterQueryString"); when(mStorageProvider.provideSplitsStorage()).thenReturn(mSplitsStorage); when(mStorageProvider.provideRuleBasedSegmentStorage()).thenReturn(mRuleBasedSegmentStorageProducer); when(mStorageProvider.provideTelemetryStorage()).thenReturn(mTelemetryStorage); when(mFetcherProvider.provideFetcher("filterQueryString")).thenReturn(mSplitsFetcher); - when(mSplitsSyncHelperProvider.provideSplitsSyncHelper(any(), any(), any(), any(), any(), any())).thenReturn(mock(SplitsSyncHelper.class)); + when(mSplitsSyncHelperProvider.provideSplitsSyncHelper(any(), any(), any(), any(), any(), any(), any())).thenReturn(mock(SplitsSyncHelper.class)); } @Test public void getTaskUsesStorageProviderForSplitsStorage() { - SplitsSyncWorkerTaskBuilder builder = new SplitsSyncWorkerTaskBuilder( - mStorageProvider, - mFetcherProvider, - mSplitChangeProcessor, - mSplitsSyncHelperProvider, - null); + SplitsSyncWorkerTaskBuilder builder = getSplitsSyncWorkerTaskBuilder(null); builder.getTask(); @@ -69,12 +69,7 @@ public void getTaskUsesStorageProviderForSplitsStorage() { @Test public void getTaskUsesFetcherProviderForFetcher() throws URISyntaxException { - SplitsSyncWorkerTaskBuilder builder = new SplitsSyncWorkerTaskBuilder( - mStorageProvider, - mFetcherProvider, - mSplitChangeProcessor, - mSplitsSyncHelperProvider, - null); + SplitsSyncWorkerTaskBuilder builder = getSplitsSyncWorkerTaskBuilder(null); builder.getTask(); @@ -87,6 +82,7 @@ public void getTaskUsesStorageProviderForTelemetryStorage() { mStorageProvider, mFetcherProvider, mSplitChangeProcessor, + mRuleBasedSegmentChangeProcessor, mSplitsSyncHelperProvider, null); @@ -105,6 +101,7 @@ public void getTaskUsesSplitsSyncHelperProviderForSplitsSyncHelper() throws URIS mStorageProvider, mFetcherProvider, mSplitChangeProcessor, + mRuleBasedSegmentChangeProcessor, mSplitsSyncHelperProvider, "1.5"); @@ -115,6 +112,7 @@ public void getTaskUsesSplitsSyncHelperProviderForSplitsSyncHelper() throws URIS verify(mSplitsSyncHelperProvider).provideSplitsSyncHelper(mSplitsFetcher, mSplitsStorage, mSplitChangeProcessor, + mRuleBasedSegmentChangeProcessor, mRuleBasedSegmentStorageProducer, mTelemetryStorage, "1.5"); @@ -124,12 +122,7 @@ public void getTaskUsesSplitsSyncHelperProviderForSplitsSyncHelper() throws URIS public void getTaskReturnsNullWhenURISyntaxExceptionIsThrown() throws URISyntaxException { when(mFetcherProvider.provideFetcher("filterQueryString")).thenThrow(new URISyntaxException("test", "test")); - SplitsSyncWorkerTaskBuilder builder = new SplitsSyncWorkerTaskBuilder( - mStorageProvider, - mFetcherProvider, - mSplitChangeProcessor, - mSplitsSyncHelperProvider, - null); + SplitsSyncWorkerTaskBuilder builder = getSplitsSyncWorkerTaskBuilder(null); SplitTask task = builder.getTask(); @@ -140,19 +133,25 @@ public void getTaskReturnsNullWhenURISyntaxExceptionIsThrown() throws URISyntaxE public void getTaskUsesSplitSyncTaskStaticMethod() { try (MockedStatic mockedStatic = mockStatic(SplitsSyncTask.class)) { SplitsSyncHelper splitsSyncHelper = mock(SplitsSyncHelper.class); - when(mSplitsSyncHelperProvider.provideSplitsSyncHelper(any(), any(), any(), any(), any(), any())).thenReturn(splitsSyncHelper); + when(mSplitsSyncHelperProvider.provideSplitsSyncHelper(any(), any(), any(), any(), any(), any(), any())).thenReturn(splitsSyncHelper); when(mStorageProvider.provideRuleBasedSegmentStorage()).thenReturn(mRuleBasedSegmentStorageProducer); - SplitsSyncWorkerTaskBuilder builder = new SplitsSyncWorkerTaskBuilder( - mStorageProvider, - mFetcherProvider, - mSplitChangeProcessor, - mSplitsSyncHelperProvider, - "2.5"); + SplitsSyncWorkerTaskBuilder builder = getSplitsSyncWorkerTaskBuilder("2.5"); builder.getTask(); mockedStatic.verify(() -> SplitsSyncTask.buildForBackground(splitsSyncHelper, mSplitsStorage, mRuleBasedSegmentStorageProducer, "filterQueryString", mTelemetryStorage)); } } + + @NonNull + private SplitsSyncWorkerTaskBuilder getSplitsSyncWorkerTaskBuilder(String flagsSpec) { + return new SplitsSyncWorkerTaskBuilder( + mStorageProvider, + mFetcherProvider, + mSplitChangeProcessor, + mRuleBasedSegmentChangeProcessor, + mSplitsSyncHelperProvider, + flagsSpec); + } } diff --git a/src/test/java/io/split/android/client/storage/rbs/RuleBasedSegmentStorageImplTest.java b/src/test/java/io/split/android/client/storage/rbs/RuleBasedSegmentStorageImplTest.java index 1631531ac..6b8cdaa84 100644 --- a/src/test/java/io/split/android/client/storage/rbs/RuleBasedSegmentStorageImplTest.java +++ b/src/test/java/io/split/android/client/storage/rbs/RuleBasedSegmentStorageImplTest.java @@ -19,7 +19,6 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicLong; import io.split.android.client.dtos.Excluded; import io.split.android.client.dtos.RuleBasedSegment; @@ -231,12 +230,16 @@ public void loadLocalDelegatesToProducer() { verify(producer).loadLocal(); } - public static RuleBasedSegment createRuleBasedSegment(String name) { + public static RuleBasedSegment createRuleBasedSegment(String name, Status status) { return new RuleBasedSegment(name, "user", 1, - Status.ACTIVE, + status, new ArrayList<>(), new Excluded()); } + + public static RuleBasedSegment createRuleBasedSegment(String name) { + return createRuleBasedSegment(name, Status.ACTIVE); + } }