diff --git a/src/androidTest/java/helper/TestableSplitConfigBuilder.java b/src/androidTest/java/helper/TestableSplitConfigBuilder.java index 9c577162c..34449f445 100644 --- a/src/androidTest/java/helper/TestableSplitConfigBuilder.java +++ b/src/androidTest/java/helper/TestableSplitConfigBuilder.java @@ -2,6 +2,7 @@ import java.lang.reflect.Constructor; +import io.split.android.client.RolloutCacheConfiguration; import io.split.android.client.ServiceEndpoints; import io.split.android.client.SplitClientConfig; import io.split.android.client.SyncConfig; @@ -64,6 +65,7 @@ public class TestableSplitConfigBuilder { private String mPrefix = ""; private CertificatePinningConfiguration mCertificatePinningConfiguration; private long mImpressionsDedupeTimeInterval = ServiceConstants.DEFAULT_IMPRESSIONS_DEDUPE_TIME_INTERVAL; + private RolloutCacheConfiguration mRolloutCacheConfiguration = RolloutCacheConfiguration.builder().build(); public TestableSplitConfigBuilder() { mServiceEndpoints = ServiceEndpoints.builder().build(); @@ -274,6 +276,11 @@ public TestableSplitConfigBuilder impressionsDedupeTimeInterval(long impressions return this; } + public TestableSplitConfigBuilder rolloutCacheConfiguration(RolloutCacheConfiguration rolloutCacheConfiguration) { + this.mRolloutCacheConfiguration = rolloutCacheConfiguration; + return this; + } + public SplitClientConfig build() { Constructor constructor = SplitClientConfig.class.getDeclaredConstructors()[0]; constructor.setAccessible(true); @@ -329,7 +336,8 @@ public SplitClientConfig build() { mPrefix, mObserverCacheExpirationPeriod, mCertificatePinningConfiguration, - mImpressionsDedupeTimeInterval); + mImpressionsDedupeTimeInterval, + mRolloutCacheConfiguration); Logger.instance().setLevel(mLogLevel); return config; diff --git a/src/main/java/io/split/android/client/RolloutCacheConfiguration.java b/src/main/java/io/split/android/client/RolloutCacheConfiguration.java new file mode 100644 index 000000000..f1cd03cbd --- /dev/null +++ b/src/main/java/io/split/android/client/RolloutCacheConfiguration.java @@ -0,0 +1,69 @@ +package io.split.android.client; + +import io.split.android.client.service.ServiceConstants; +import io.split.android.client.utils.logger.Logger; + +public class RolloutCacheConfiguration { + + private final int mExpiration; + private final boolean mClearOnInit; + + private RolloutCacheConfiguration(int expiration, boolean clearOnInit) { + mExpiration = expiration; + mClearOnInit = clearOnInit; + } + + public int getExpiration() { + return mExpiration; + } + + public boolean clearOnInit() { + return mClearOnInit; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private static final int MIN_EXPIRATION_DAYS = 1; + + private int mExpiration = ServiceConstants.DEFAULT_ROLLOUT_CACHE_EXPIRATION; + private boolean mClearOnInit = false; + + private Builder() { + + } + + /** + * Set the expiration time for the rollout definitions cache, in days. Default is 10 days. + * @param expiration in days + * @return This builder + */ + public Builder expiration(int expiration) { + if (expiration < MIN_EXPIRATION_DAYS) { + Logger.w("Cache expiration must be at least 1 day. Using default value."); + mExpiration = ServiceConstants.DEFAULT_ROLLOUT_CACHE_EXPIRATION; + } else { + mExpiration = expiration; + } + + return this; + } + + /** + * Set if the rollout definitions cache should be cleared on initialization. Default is false. + * @param clearOnInit whether to clear cache on initialization. + * @return This builder + */ + public Builder clearOnInit(boolean clearOnInit) { + mClearOnInit = clearOnInit; + return this; + } + + public RolloutCacheConfiguration build() { + return new RolloutCacheConfiguration(mExpiration, mClearOnInit); + } + } +} diff --git a/src/main/java/io/split/android/client/SplitClientConfig.java b/src/main/java/io/split/android/client/SplitClientConfig.java index c2eff3e9b..b33567a32 100644 --- a/src/main/java/io/split/android/client/SplitClientConfig.java +++ b/src/main/java/io/split/android/client/SplitClientConfig.java @@ -131,6 +131,8 @@ public class SplitClientConfig { private final long mObserverCacheExpirationPeriod; private final CertificatePinningConfiguration mCertificatePinningConfiguration; private final long mImpressionsDedupeTimeInterval; + @NonNull + private final RolloutCacheConfiguration mRolloutCacheConfiguration; public static Builder builder() { return new Builder(); @@ -185,7 +187,8 @@ private SplitClientConfig(String endpoint, String prefix, long observerCacheExpirationPeriod, CertificatePinningConfiguration certificatePinningConfiguration, - long impressionsDedupeTimeInterval) { + long impressionsDedupeTimeInterval, + RolloutCacheConfiguration rolloutCacheConfiguration) { mEndpoint = endpoint; mEventsEndpoint = eventsEndpoint; mTelemetryEndpoint = telemetryEndpoint; @@ -243,6 +246,7 @@ private SplitClientConfig(String endpoint, mObserverCacheExpirationPeriod = observerCacheExpirationPeriod; mCertificatePinningConfiguration = certificatePinningConfiguration; mImpressionsDedupeTimeInterval = impressionsDedupeTimeInterval; + mRolloutCacheConfiguration = rolloutCacheConfiguration; } public String trafficType() { @@ -486,8 +490,8 @@ public long impressionsDedupeTimeInterval() { return mImpressionsDedupeTimeInterval; } - public boolean clearOnInit() { - return false; // TODO: to be implemented in the future + public RolloutCacheConfiguration rolloutCacheConfiguration() { + return mRolloutCacheConfiguration; } public static final class Builder { @@ -566,6 +570,8 @@ public static final class Builder { private long mImpressionsDedupeTimeInterval = ServiceConstants.DEFAULT_IMPRESSIONS_DEDUPE_TIME_INTERVAL; + private RolloutCacheConfiguration mRolloutCacheConfiguration = RolloutCacheConfiguration.builder().build(); + public Builder() { mServiceEndpoints = ServiceEndpoints.builder().build(); } @@ -1106,6 +1112,22 @@ public Builder impressionsDedupeTimeInterval(long impressionsDedupeTimeInterval) return this; } + /** + * Configuration for rollout definitions cache. + * + * @param rolloutCacheConfiguration Configuration object + * @return This builder + */ + public Builder rolloutCacheConfiguration(@NonNull RolloutCacheConfiguration rolloutCacheConfiguration) { + if (rolloutCacheConfiguration == null) { + Logger.w("Rollout cache configuration is null. Setting to default value."); + mRolloutCacheConfiguration = RolloutCacheConfiguration.builder().build(); + } else { + mRolloutCacheConfiguration = rolloutCacheConfiguration; + } + return this; + } + public SplitClientConfig build() { Logger.instance().setLevel(mLogLevel); @@ -1237,7 +1259,8 @@ public SplitClientConfig build() { mPrefix, mObserverCacheExpirationPeriod, mCertificatePinningConfiguration, - mImpressionsDedupeTimeInterval); + mImpressionsDedupeTimeInterval, + mRolloutCacheConfiguration); } private HttpProxy parseProxyHost(String proxyUri) { diff --git a/src/main/java/io/split/android/client/service/ServiceConstants.java b/src/main/java/io/split/android/client/service/ServiceConstants.java index 097b043b8..6f5c9f00c 100644 --- a/src/main/java/io/split/android/client/service/ServiceConstants.java +++ b/src/main/java/io/split/android/client/service/ServiceConstants.java @@ -11,6 +11,7 @@ public class ServiceConstants { public static final long MIN_INITIAL_DELAY = 5L; public static final int DEFAULT_RECORDS_PER_PUSH = 100; public static final long DEFAULT_SPLITS_CACHE_EXPIRATION_IN_SECONDS = TimeUnit.DAYS.toSeconds(10); // 10 days + public static final int DEFAULT_ROLLOUT_CACHE_EXPIRATION = 10; // 10 days public static final int MAX_ROWS_PER_QUERY = 100; diff --git a/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerConfig.java b/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerConfig.java deleted file mode 100644 index 1de13b297..000000000 --- a/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerConfig.java +++ /dev/null @@ -1,29 +0,0 @@ -package io.split.android.client.service.synchronizer; - -import androidx.annotation.VisibleForTesting; - -import io.split.android.client.SplitClientConfig; - -class RolloutCacheManagerConfig { - - private final long mCacheExpirationInDays; - private final boolean mClearOnInit; - - @VisibleForTesting - RolloutCacheManagerConfig(long cacheExpirationInDays, boolean clearOnInit) { - mCacheExpirationInDays = cacheExpirationInDays; - mClearOnInit = clearOnInit; - } - - static RolloutCacheManagerConfig from(SplitClientConfig splitClientConfig) { - return new RolloutCacheManagerConfig(splitClientConfig.cacheExpirationInSeconds(), splitClientConfig.clearOnInit()); - } - - long getCacheExpirationInDays() { - return mCacheExpirationInDays; - } - - boolean isClearOnInit() { - return mClearOnInit; - } -} diff --git a/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerImpl.java b/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerImpl.java index f211ae62b..cc025db81 100644 --- a/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerImpl.java +++ b/src/main/java/io/split/android/client/service/synchronizer/RolloutCacheManagerImpl.java @@ -8,6 +8,7 @@ import java.util.concurrent.TimeUnit; +import io.split.android.client.RolloutCacheConfiguration; import io.split.android.client.SplitClientConfig; import io.split.android.client.service.CleanUpDatabaseTask; import io.split.android.client.service.executor.SplitTask; @@ -27,7 +28,7 @@ public class RolloutCacheManagerImpl implements RolloutCacheManager, SplitTask { @NonNull private final GeneralInfoStorage mGeneralInfoStorage; @NonNull - private final RolloutCacheManagerConfig mConfig; + private final RolloutCacheConfiguration mConfig; @NonNull private final RolloutDefinitionsCache[] mStorages; @NonNull @@ -35,11 +36,12 @@ public class RolloutCacheManagerImpl implements RolloutCacheManager, SplitTask { @NonNull private final EncryptionMigrationTask mEncryptionMigrationTask; - public RolloutCacheManagerImpl(@NonNull SplitClientConfig splitClientConfig, @NonNull SplitStorageContainer storageContainer, + public RolloutCacheManagerImpl(@NonNull SplitClientConfig splitClientConfig, + @NonNull SplitStorageContainer storageContainer, @NonNull CleanUpDatabaseTask cleanUpDatabaseTask, @NonNull EncryptionMigrationTask encryptionMigrationTask) { this(storageContainer.getGeneralInfoStorage(), - RolloutCacheManagerConfig.from(splitClientConfig), + splitClientConfig.rolloutCacheConfiguration(), cleanUpDatabaseTask, encryptionMigrationTask, storageContainer.getSplitsStorage(), @@ -49,7 +51,7 @@ public RolloutCacheManagerImpl(@NonNull SplitClientConfig splitClientConfig, @No @VisibleForTesting RolloutCacheManagerImpl(@NonNull GeneralInfoStorage generalInfoStorage, - @NonNull RolloutCacheManagerConfig config, + @NonNull RolloutCacheConfiguration config, @NonNull CleanUpDatabaseTask clean, @NonNull EncryptionMigrationTask encryptionMigrationTask, @NonNull RolloutDefinitionsCache... storages) { @@ -99,10 +101,10 @@ private boolean validateExpiration() { long lastUpdateTimestamp = mGeneralInfoStorage.getSplitsUpdateTimestamp(); long daysSinceLastUpdate = TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis() - lastUpdateTimestamp); - if (daysSinceLastUpdate > mConfig.getCacheExpirationInDays()) { + if (daysSinceLastUpdate > mConfig.getExpiration()) { Logger.v("Clearing rollout definitions cache due to expiration"); return true; - } else if (mConfig.isClearOnInit()) { + } else if (mConfig.clearOnInit()) { long lastCacheClearTimestamp = mGeneralInfoStorage.getRolloutCacheLastClearTimestamp(); long daysSinceCacheClear = TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis() - lastCacheClearTimestamp); diff --git a/src/test/java/io/split/android/client/RolloutCacheConfigurationTest.java b/src/test/java/io/split/android/client/RolloutCacheConfigurationTest.java new file mode 100644 index 000000000..ad1760fe8 --- /dev/null +++ b/src/test/java/io/split/android/client/RolloutCacheConfigurationTest.java @@ -0,0 +1,41 @@ +package io.split.android.client; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +public class RolloutCacheConfigurationTest { + + @Test + public void defaultValues() { + RolloutCacheConfiguration config = RolloutCacheConfiguration.builder().build(); + assertEquals(10, config.getExpiration()); + assertFalse(config.clearOnInit()); + } + + @Test + public void expirationIsCorrectlySet() { + RolloutCacheConfiguration.Builder builder = RolloutCacheConfiguration.builder(); + builder.expiration(1); + RolloutCacheConfiguration config = builder.build(); + assertEquals(1, config.getExpiration()); + } + + @Test + public void clearOnInitIsCorrectlySet() { + RolloutCacheConfiguration.Builder builder = RolloutCacheConfiguration.builder(); + builder.clearOnInit(true); + RolloutCacheConfiguration config = builder.build(); + assertTrue(config.clearOnInit()); + } + + @Test + public void negativeExpirationIsSetToDefault() { + RolloutCacheConfiguration.Builder builder = RolloutCacheConfiguration.builder(); + builder.expiration(-1); + RolloutCacheConfiguration config = builder.build(); + assertEquals(10, config.getExpiration()); + } +} diff --git a/src/test/java/io/split/android/client/SplitClientConfigTest.java b/src/test/java/io/split/android/client/SplitClientConfigTest.java index 8f3351ccb..83564c640 100644 --- a/src/test/java/io/split/android/client/SplitClientConfigTest.java +++ b/src/test/java/io/split/android/client/SplitClientConfigTest.java @@ -3,6 +3,7 @@ import static junit.framework.Assert.assertFalse; import static junit.framework.TestCase.assertEquals; import static junit.framework.TestCase.assertNull; +import static junit.framework.TestCase.assertTrue; import androidx.annotation.NonNull; @@ -224,6 +225,37 @@ public void observerCacheExpirationPeriodMatchesDedupeTimeIntervalWhenDedupeTime assertEquals(TimeUnit.HOURS.toMillis(4), config3.observerCacheExpirationPeriod()); } + @Test + public void rolloutCacheConfigurationDefaults() { + RolloutCacheConfiguration config = SplitClientConfig.builder().build().rolloutCacheConfiguration(); + + assertEquals(10, config.getExpiration()); + assertFalse(config.clearOnInit()); + } + + @Test + public void rolloutCacheConfigurationExpirationIsCorrectlySet() { + RolloutCacheConfiguration config = SplitClientConfig.builder() + .rolloutCacheConfiguration(RolloutCacheConfiguration.builder().expiration(1).clearOnInit(true).build()) + .build().rolloutCacheConfiguration(); + + assertEquals(1, config.getExpiration()); + assertTrue(config.clearOnInit()); + } + + @Test + public void nullRolloutCacheConfigurationSetsDefault() { + Queue logMessages = getLogMessagesQueue(); + RolloutCacheConfiguration config = SplitClientConfig.builder() + .logLevel(SplitLogLevel.WARNING) + .rolloutCacheConfiguration(null) + .build().rolloutCacheConfiguration(); + + assertEquals(10, config.getExpiration()); + assertFalse(config.clearOnInit()); + assertEquals(1, logMessages.size()); + } + @NonNull private static Queue getLogMessagesQueue() { Queue logMessages = new LinkedList<>(); diff --git a/src/test/java/io/split/android/client/service/synchronizer/RolloutCacheManagerTest.kt b/src/test/java/io/split/android/client/service/synchronizer/RolloutCacheManagerTest.kt index 20e1b2c24..a473ed775 100644 --- a/src/test/java/io/split/android/client/service/synchronizer/RolloutCacheManagerTest.kt +++ b/src/test/java/io/split/android/client/service/synchronizer/RolloutCacheManagerTest.kt @@ -1,5 +1,6 @@ package io.split.android.client.service.synchronizer +import io.split.android.client.RolloutCacheConfiguration import io.split.android.client.service.CleanUpDatabaseTask import io.split.android.client.service.executor.SplitTaskExecutionListener import io.split.android.client.storage.RolloutDefinitionsCache @@ -36,7 +37,7 @@ class RolloutCacheManagerTest { @Test fun `validateCache calls listener`() { - mRolloutCacheManager = getCacheManager(10L, false) + mRolloutCacheManager = getCacheManager(10, false) val listener = mock(SplitTaskExecutionListener::class.java) mRolloutCacheManager.validateCache(listener) @@ -46,9 +47,9 @@ class RolloutCacheManagerTest { @Test fun `validateCache calls clear on storages when expiration is surpassed`() { - val mockedTimestamp = createMockedTimestamp(10L) + val mockedTimestamp = createMockedTimestamp(10) `when`(mGeneralInfoStorage.splitsUpdateTimestamp).thenReturn(mockedTimestamp) - mRolloutCacheManager = getCacheManager(9L, false) + mRolloutCacheManager = getCacheManager(9, false) mRolloutCacheManager.validateCache(mock(SplitTaskExecutionListener::class.java)) @@ -60,7 +61,7 @@ class RolloutCacheManagerTest { fun `validateCache does not call clear on storages when expiration is not surpassed and clearOnInit is false`() { val mockedTimestamp = createMockedTimestamp(1L) `when`(mGeneralInfoStorage.splitsUpdateTimestamp).thenReturn(mockedTimestamp) - mRolloutCacheManager = getCacheManager(10L, false) + mRolloutCacheManager = getCacheManager(10, false) mRolloutCacheManager.validateCache(mock(SplitTaskExecutionListener::class.java)) @@ -72,7 +73,7 @@ class RolloutCacheManagerTest { fun `validateCache calls clear on storages when expiration is not surpassed and clearOnInit is true`() { val mockedTimestamp = createMockedTimestamp(1L) `when`(mGeneralInfoStorage.splitsUpdateTimestamp).thenReturn(mockedTimestamp) - mRolloutCacheManager = getCacheManager(10L, true) + mRolloutCacheManager = getCacheManager(10, true) mRolloutCacheManager.validateCache(mock(SplitTaskExecutionListener::class.java)) @@ -85,7 +86,7 @@ class RolloutCacheManagerTest { val mockedTimestamp = createMockedTimestamp(1L) `when`(mGeneralInfoStorage.splitsUpdateTimestamp).thenReturn(mockedTimestamp) `when`(mGeneralInfoStorage.rolloutCacheLastClearTimestamp).thenReturn(0L).thenReturn(TimeUnit.HOURS.toMillis(TimeUnit.MILLISECONDS.toHours(System.currentTimeMillis()) - 1)) - mRolloutCacheManager = getCacheManager(10L, true) + mRolloutCacheManager = getCacheManager(10, true) mRolloutCacheManager.validateCache(mock(SplitTaskExecutionListener::class.java)) mRolloutCacheManager.validateCache(mock(SplitTaskExecutionListener::class.java)) @@ -99,7 +100,7 @@ class RolloutCacheManagerTest { val mockedTimestamp = createMockedTimestamp(1L) `when`(mGeneralInfoStorage.splitsUpdateTimestamp).thenReturn(mockedTimestamp) `when`(mGeneralInfoStorage.rolloutCacheLastClearTimestamp).thenReturn(0L).thenReturn(TimeUnit.HOURS.toMillis(TimeUnit.MILLISECONDS.toHours(System.currentTimeMillis()) - 1)) - mRolloutCacheManager = getCacheManager(10L, true) + mRolloutCacheManager = getCacheManager(10, true) val listener = mock(SplitTaskExecutionListener::class.java) `when`(mSplitsCache.clear()).thenThrow(RuntimeException("Exception during clear")) @@ -114,7 +115,7 @@ class RolloutCacheManagerTest { val mockedTimestamp = createMockedTimestamp(1L) `when`(mGeneralInfoStorage.splitsUpdateTimestamp).thenReturn(mockedTimestamp) `when`(mGeneralInfoStorage.rolloutCacheLastClearTimestamp).thenReturn(0L).thenReturn(TimeUnit.HOURS.toMillis(TimeUnit.MILLISECONDS.toHours(System.currentTimeMillis()) - 1)) - mRolloutCacheManager = getCacheManager(10L, true) + mRolloutCacheManager = getCacheManager(10, true) mRolloutCacheManager.validateCache(mock(SplitTaskExecutionListener::class.java)) @@ -126,7 +127,7 @@ class RolloutCacheManagerTest { val mockedTimestamp = createMockedTimestamp(1L) `when`(mGeneralInfoStorage.splitsUpdateTimestamp).thenReturn(mockedTimestamp) `when`(mGeneralInfoStorage.rolloutCacheLastClearTimestamp).thenReturn(0L).thenReturn(TimeUnit.HOURS.toMillis(TimeUnit.MILLISECONDS.toHours(System.currentTimeMillis()) - 1)) - mRolloutCacheManager = getCacheManager(10L, false) + mRolloutCacheManager = getCacheManager(10, false) mRolloutCacheManager.validateCache(mock(SplitTaskExecutionListener::class.java)) @@ -135,7 +136,7 @@ class RolloutCacheManagerTest { @Test fun `validateCache executes cleanUpDatabaseTask`() { - mRolloutCacheManager = getCacheManager(10L, false) + mRolloutCacheManager = getCacheManager(10, false) mRolloutCacheManager.validateCache(mock(SplitTaskExecutionListener::class.java)) @@ -144,15 +145,15 @@ class RolloutCacheManagerTest { @Test fun `validateCache executes encryptionMigrationTask`() { - mRolloutCacheManager = getCacheManager(10L, false) + mRolloutCacheManager = getCacheManager(10, false) mRolloutCacheManager.validateCache(mock(SplitTaskExecutionListener::class.java)) verify(mEncryptionMigrationTask).execute() } - private fun getCacheManager(expiration: Long, clearOnInit: Boolean): RolloutCacheManager { - return RolloutCacheManagerImpl(mGeneralInfoStorage, RolloutCacheManagerConfig(expiration, clearOnInit), mCleanUpDatabaseTask, mEncryptionMigrationTask, mSplitsCache, mSegmentsCache) + private fun getCacheManager(expiration: Int, clearOnInit: Boolean): RolloutCacheManager { + return RolloutCacheManagerImpl(mGeneralInfoStorage, RolloutCacheConfiguration.builder().expiration(expiration).clearOnInit(clearOnInit).build(), mCleanUpDatabaseTask, mEncryptionMigrationTask, mSplitsCache, mSegmentsCache) } private fun createMockedTimestamp(period: Long): Long {