From ba303d811a93eff5dadd83d6932a7de2d84a8316 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Tue, 25 Feb 2025 15:36:51 -0300 Subject: [PATCH 01/11] Persistent storage --- .../storage/rbs/RuleBasedSegmentSnapshot.java | 12 +- .../rbs/RuleBasedSegmentStorageImpl.java | 9 +- ...PersistentRuleBasedSegmentStorageImpl.java | 161 ++++++++++++++++++ .../rbs/RuleBasedSegmentStorageImplTest.java | 5 +- 4 files changed, 175 insertions(+), 12 deletions(-) create mode 100644 src/main/java/io/split/android/client/storage/rbs/SqLitePersistentRuleBasedSegmentStorageImpl.java diff --git a/src/main/java/io/split/android/client/storage/rbs/RuleBasedSegmentSnapshot.java b/src/main/java/io/split/android/client/storage/rbs/RuleBasedSegmentSnapshot.java index fdb618bcd..d62e31a42 100644 --- a/src/main/java/io/split/android/client/storage/rbs/RuleBasedSegmentSnapshot.java +++ b/src/main/java/io/split/android/client/storage/rbs/RuleBasedSegmentSnapshot.java @@ -1,21 +1,23 @@ package io.split.android.client.storage.rbs; -import java.util.Set; +import static io.split.android.client.utils.Utils.checkNotNull; + +import java.util.Map; import io.split.android.client.dtos.RuleBasedSegment; public class RuleBasedSegmentSnapshot { - private final Set mSegments; + private final Map mSegments; private final long mChangeNumber; - public RuleBasedSegmentSnapshot(Set segments, long changeNumber) { - mSegments = segments; + public RuleBasedSegmentSnapshot(Map segments, long changeNumber) { + mSegments = checkNotNull(segments); mChangeNumber = changeNumber; } - public Set getSegments() { + public Map getSegments() { return mSegments; } diff --git a/src/main/java/io/split/android/client/storage/rbs/RuleBasedSegmentStorageImpl.java b/src/main/java/io/split/android/client/storage/rbs/RuleBasedSegmentStorageImpl.java index 013092227..80b5759b3 100644 --- a/src/main/java/io/split/android/client/storage/rbs/RuleBasedSegmentStorageImpl.java +++ b/src/main/java/io/split/android/client/storage/rbs/RuleBasedSegmentStorageImpl.java @@ -6,6 +6,7 @@ import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; +import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -77,13 +78,11 @@ public boolean contains(@NonNull Set segmentNames) { @WorkerThread @Override - public void loadLocal() { + public synchronized void loadLocal() { RuleBasedSegmentSnapshot snapshot = mPersistentStorage.getSnapshot(); - Set segments = snapshot.getSegments(); + Map segments = snapshot.getSegments(); mChangeNumber = snapshot.getChangeNumber(); - for (RuleBasedSegment segment : segments) { - mInMemorySegments.put(segment.getName(), segment); - } + mInMemorySegments.putAll(segments); } @WorkerThread diff --git a/src/main/java/io/split/android/client/storage/rbs/SqLitePersistentRuleBasedSegmentStorageImpl.java b/src/main/java/io/split/android/client/storage/rbs/SqLitePersistentRuleBasedSegmentStorageImpl.java new file mode 100644 index 000000000..baf4c872d --- /dev/null +++ b/src/main/java/io/split/android/client/storage/rbs/SqLitePersistentRuleBasedSegmentStorageImpl.java @@ -0,0 +1,161 @@ +package io.split.android.client.storage.rbs; + +import static io.split.android.client.utils.Utils.checkNotNull; + +import androidx.annotation.NonNull; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Callable; + +import io.split.android.client.dtos.RuleBasedSegment; +import io.split.android.client.storage.cipher.SplitCipher; +import io.split.android.client.storage.db.SplitRoomDatabase; +import io.split.android.client.storage.db.rbs.RuleBasedSegmentDao; +import io.split.android.client.storage.db.rbs.RuleBasedSegmentEntity; +import io.split.android.client.storage.general.GeneralInfoStorage; +import io.split.android.client.utils.Json; +import io.split.android.client.utils.logger.Logger; + +class SqLitePersistentRuleBasedSegmentStorageImpl implements PersistentRuleBasedSegmentStorage { + + private final RuleBasedSegmentDao mDao; + private final SplitRoomDatabase mDatabase; + private final GeneralInfoStorage mGeneralInfoStorage; + private final SplitCipher mCipher; + + public SqLitePersistentRuleBasedSegmentStorageImpl(SplitCipher cipher, + SplitRoomDatabase database, + GeneralInfoStorage generalInfoStorage) { + mCipher = checkNotNull(cipher); + mDatabase = checkNotNull(database); + mDao = database.ruleBasedSegmentDao(); + mGeneralInfoStorage = checkNotNull(generalInfoStorage); + } + + @Override + public RuleBasedSegmentSnapshot getSnapshot() { + return mDatabase.runInTransaction(new SnapshotLoader(mDao, mCipher, mGeneralInfoStorage)); + } + + @Override + public void update(Set toAdd, Set toRemove, long changeNumber) { + mDatabase.runInTransaction(new Updater(mCipher, mDao, mGeneralInfoStorage, toAdd, toRemove, changeNumber)); + } + + @Override + public void clear() { + mDatabase.runInTransaction(new Runnable() { + @Override + public void run() { + try { + mDao.deleteAll(); + mGeneralInfoStorage.setRbsChangeNumber(-1); + } catch (Exception e) { + Logger.e("Error clearing RBS: " + e.getLocalizedMessage()); + throw e; + } + } + }); + } + + static final class SnapshotLoader implements Callable { + + private final RuleBasedSegmentDao mDao; + private final SplitCipher mCipher; + private final GeneralInfoStorage mGeneralInfoStorage; + + public SnapshotLoader(RuleBasedSegmentDao dao, SplitCipher cipher, GeneralInfoStorage generalInfoStorage) { + mDao = checkNotNull(dao); + mCipher = checkNotNull(cipher); + mGeneralInfoStorage = checkNotNull(generalInfoStorage); + } + + @Override + public RuleBasedSegmentSnapshot call() { + try { + long changeNumber = mGeneralInfoStorage.getFlagsChangeNumber(); + List entities = mDao.getAll(); + Map segments = convertToDTOs(entities); + + return new RuleBasedSegmentSnapshot(segments, changeNumber); + } catch (Exception e) { + Logger.e("Error loading RBS from persistent storage", e.getLocalizedMessage()); + throw e; + } + } + + private Map convertToDTOs(List entities) { + Map segments = new HashMap<>(); + if (entities != null) { + for (RuleBasedSegmentEntity entity : entities) { + String name = mCipher.decrypt(entity.getName()); + String body = mCipher.encrypt(entity.getBody()); + if (name == null || body == null) { + continue; + } + + RuleBasedSegment ruleBasedSegment = Json.fromJson(body, RuleBasedSegment.class); + segments.put(name, ruleBasedSegment); + } + } + return segments; + } + } + + static final class Updater implements Runnable { + + @NonNull + private final SplitCipher mCipher; + @NonNull + private final GeneralInfoStorage mGeneralInfoStorage; + @NonNull + private final RuleBasedSegmentDao mDao; + @NonNull + private final Set mToAdd; + @NonNull + private final Set mToRemove; + private final long mChangeNumber; + + public Updater(@NonNull SplitCipher cipher, + @NonNull RuleBasedSegmentDao dao, + @NonNull GeneralInfoStorage generalInfoStorage, + @NonNull Set toAdd, + @NonNull Set toRemove, + long changeNumber) { + mCipher = checkNotNull(cipher); + mDao = checkNotNull(dao); + mGeneralInfoStorage = checkNotNull(generalInfoStorage); + mToAdd = checkNotNull(toAdd); + mToRemove = checkNotNull(toRemove); + mChangeNumber = changeNumber; + } + + @Override + public void run() { + try { + List toDelete = new ArrayList<>(); + for (RuleBasedSegment segment : mToRemove) { + toDelete.add(mCipher.encrypt(segment.getName())); + } + + List toAdd = new ArrayList<>(); + for (RuleBasedSegment segment : mToAdd) { + String name = mCipher.encrypt(segment.getName()); + String body = mCipher.encrypt(Json.toJson(segment)); + toAdd.add(new RuleBasedSegmentEntity(name, body, System.currentTimeMillis())); + } + + mDao.delete(toDelete); + mDao.insert(toAdd); + mGeneralInfoStorage.setRbsChangeNumber(mChangeNumber); + } catch (Exception e) { + Logger.e("Error updating RBS: " + e.getLocalizedMessage()); + throw e; + } + } + } +} 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 5af577c6a..4596bf921 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 @@ -16,6 +16,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; +import java.util.Map; import java.util.Set; import io.split.android.client.dtos.Excluded; @@ -159,7 +160,7 @@ public void updateReturnsTrueWhenThereWereAddedSegments() { @Test public void loadLocalGetsSnapshotFromPersistentStorage() { - when(mPersistentStorage.getSnapshot()).thenReturn(new RuleBasedSegmentSnapshot(Set.of(), 1)); + when(mPersistentStorage.getSnapshot()).thenReturn(new RuleBasedSegmentSnapshot(Map.of(), 1)); storage.loadLocal(); verify(mPersistentStorage).getSnapshot(); @@ -167,7 +168,7 @@ public void loadLocalGetsSnapshotFromPersistentStorage() { @Test public void loadLocalPopulatesValues() { - RuleBasedSegmentSnapshot snapshot = new RuleBasedSegmentSnapshot(Set.of(createRuleBasedSegment("segment1")), + RuleBasedSegmentSnapshot snapshot = new RuleBasedSegmentSnapshot(Map.of("segment1", createRuleBasedSegment("segment1")), 1); when(mPersistentStorage.getSnapshot()).thenReturn(snapshot); From 1ade063b46c99c488ee808d16bdfa9c635d994c9 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Tue, 25 Feb 2025 16:24:51 -0300 Subject: [PATCH 02/11] WIP --- .../android/client/storage/rbs/Clearer.java | 27 +++ .../PersistentRuleBasedSegmentStorage.java | 5 + .../client/storage/rbs/SnapshotLoader.java | 60 +++++++ ...LitePersistentRuleBasedSegmentStorage.java | 43 +++++ ...PersistentRuleBasedSegmentStorageImpl.java | 161 ------------------ ...istentRuleBasedSegmentStorageProvider.java | 19 +++ .../android/client/storage/rbs/Updater.java | 70 ++++++++ ...PersistentRuleBasedSegmentStorageTest.java | 6 + 8 files changed, 230 insertions(+), 161 deletions(-) create mode 100644 src/main/java/io/split/android/client/storage/rbs/Clearer.java create mode 100644 src/main/java/io/split/android/client/storage/rbs/SnapshotLoader.java create mode 100644 src/main/java/io/split/android/client/storage/rbs/SqLitePersistentRuleBasedSegmentStorage.java delete mode 100644 src/main/java/io/split/android/client/storage/rbs/SqLitePersistentRuleBasedSegmentStorageImpl.java create mode 100644 src/main/java/io/split/android/client/storage/rbs/SqLitePersistentRuleBasedSegmentStorageProvider.java create mode 100644 src/main/java/io/split/android/client/storage/rbs/Updater.java create mode 100644 src/test/java/io/split/android/client/storage/rbs/SqLitePersistentRuleBasedSegmentStorageTest.java diff --git a/src/main/java/io/split/android/client/storage/rbs/Clearer.java b/src/main/java/io/split/android/client/storage/rbs/Clearer.java new file mode 100644 index 000000000..30327f124 --- /dev/null +++ b/src/main/java/io/split/android/client/storage/rbs/Clearer.java @@ -0,0 +1,27 @@ +package io.split.android.client.storage.rbs; + +import io.split.android.client.storage.db.rbs.RuleBasedSegmentDao; +import io.split.android.client.storage.general.GeneralInfoStorage; +import io.split.android.client.utils.logger.Logger; + +class Clearer implements Runnable { + + private final RuleBasedSegmentDao mDao; + private final GeneralInfoStorage mGeneralInfoStorage; + + public Clearer(RuleBasedSegmentDao dao, GeneralInfoStorage generalInfoStorage) { + mDao = dao; + mGeneralInfoStorage = generalInfoStorage; + } + + @Override + public void run() { + try { + mDao.deleteAll(); + mGeneralInfoStorage.setRbsChangeNumber(-1); + } catch (Exception e) { + Logger.e("Error clearing RBS: " + e.getLocalizedMessage()); + throw e; + } + } +} diff --git a/src/main/java/io/split/android/client/storage/rbs/PersistentRuleBasedSegmentStorage.java b/src/main/java/io/split/android/client/storage/rbs/PersistentRuleBasedSegmentStorage.java index c5329bf07..e2bd35634 100644 --- a/src/main/java/io/split/android/client/storage/rbs/PersistentRuleBasedSegmentStorage.java +++ b/src/main/java/io/split/android/client/storage/rbs/PersistentRuleBasedSegmentStorage.java @@ -11,4 +11,9 @@ public interface PersistentRuleBasedSegmentStorage { void update(Set toAdd, Set toRemove, long changeNumber); void clear(); + + interface Provider { + + PersistentRuleBasedSegmentStorage get(); + } } diff --git a/src/main/java/io/split/android/client/storage/rbs/SnapshotLoader.java b/src/main/java/io/split/android/client/storage/rbs/SnapshotLoader.java new file mode 100644 index 000000000..11cea6182 --- /dev/null +++ b/src/main/java/io/split/android/client/storage/rbs/SnapshotLoader.java @@ -0,0 +1,60 @@ +package io.split.android.client.storage.rbs; + +import static io.split.android.client.utils.Utils.checkNotNull; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; + +import io.split.android.client.dtos.RuleBasedSegment; +import io.split.android.client.storage.cipher.SplitCipher; +import io.split.android.client.storage.db.rbs.RuleBasedSegmentDao; +import io.split.android.client.storage.db.rbs.RuleBasedSegmentEntity; +import io.split.android.client.storage.general.GeneralInfoStorage; +import io.split.android.client.utils.Json; +import io.split.android.client.utils.logger.Logger; + +final class SnapshotLoader implements Callable { + + private final RuleBasedSegmentDao mDao; + private final SplitCipher mCipher; + private final GeneralInfoStorage mGeneralInfoStorage; + + SnapshotLoader(RuleBasedSegmentDao dao, SplitCipher cipher, GeneralInfoStorage generalInfoStorage) { + mDao = checkNotNull(dao); + mCipher = checkNotNull(cipher); + mGeneralInfoStorage = checkNotNull(generalInfoStorage); + } + + @Override + public RuleBasedSegmentSnapshot call() { + try { + long changeNumber = mGeneralInfoStorage.getFlagsChangeNumber(); + List entities = mDao.getAll(); + Map segments = convertToDTOs(entities); + + return new RuleBasedSegmentSnapshot(segments, changeNumber); + } catch (Exception e) { + Logger.e("Error loading RBS from persistent storage", e.getLocalizedMessage()); + throw e; + } + } + + private Map convertToDTOs(List entities) { + Map segments = new HashMap<>(); + if (entities != null) { + for (RuleBasedSegmentEntity entity : entities) { + String name = mCipher.decrypt(entity.getName()); + String body = mCipher.encrypt(entity.getBody()); + if (name == null || body == null) { + continue; + } + + RuleBasedSegment ruleBasedSegment = Json.fromJson(body, RuleBasedSegment.class); + segments.put(name, ruleBasedSegment); + } + } + return segments; + } +} diff --git a/src/main/java/io/split/android/client/storage/rbs/SqLitePersistentRuleBasedSegmentStorage.java b/src/main/java/io/split/android/client/storage/rbs/SqLitePersistentRuleBasedSegmentStorage.java new file mode 100644 index 000000000..63d09f09b --- /dev/null +++ b/src/main/java/io/split/android/client/storage/rbs/SqLitePersistentRuleBasedSegmentStorage.java @@ -0,0 +1,43 @@ +package io.split.android.client.storage.rbs; + +import static io.split.android.client.utils.Utils.checkNotNull; + +import java.util.Set; + +import io.split.android.client.dtos.RuleBasedSegment; +import io.split.android.client.storage.cipher.SplitCipher; +import io.split.android.client.storage.db.SplitRoomDatabase; +import io.split.android.client.storage.db.rbs.RuleBasedSegmentDao; +import io.split.android.client.storage.general.GeneralInfoStorage; + +class SqLitePersistentRuleBasedSegmentStorage implements PersistentRuleBasedSegmentStorage { + + private final RuleBasedSegmentDao mDao; + private final SplitRoomDatabase mDatabase; + private final GeneralInfoStorage mGeneralInfoStorage; + private final SplitCipher mCipher; + + public SqLitePersistentRuleBasedSegmentStorage(SplitCipher cipher, + SplitRoomDatabase database, + GeneralInfoStorage generalInfoStorage) { + mCipher = checkNotNull(cipher); + mDatabase = checkNotNull(database); + mDao = database.ruleBasedSegmentDao(); + mGeneralInfoStorage = checkNotNull(generalInfoStorage); + } + + @Override + public RuleBasedSegmentSnapshot getSnapshot() { + return mDatabase.runInTransaction(new SnapshotLoader(mDao, mCipher, mGeneralInfoStorage)); + } + + @Override + public void update(Set toAdd, Set toRemove, long changeNumber) { + mDatabase.runInTransaction(new Updater(mCipher, mDao, mGeneralInfoStorage, toAdd, toRemove, changeNumber)); + } + + @Override + public void clear() { + mDatabase.runInTransaction(new Clearer(mDao, mGeneralInfoStorage)); + } +} diff --git a/src/main/java/io/split/android/client/storage/rbs/SqLitePersistentRuleBasedSegmentStorageImpl.java b/src/main/java/io/split/android/client/storage/rbs/SqLitePersistentRuleBasedSegmentStorageImpl.java deleted file mode 100644 index baf4c872d..000000000 --- a/src/main/java/io/split/android/client/storage/rbs/SqLitePersistentRuleBasedSegmentStorageImpl.java +++ /dev/null @@ -1,161 +0,0 @@ -package io.split.android.client.storage.rbs; - -import static io.split.android.client.utils.Utils.checkNotNull; - -import androidx.annotation.NonNull; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.Callable; - -import io.split.android.client.dtos.RuleBasedSegment; -import io.split.android.client.storage.cipher.SplitCipher; -import io.split.android.client.storage.db.SplitRoomDatabase; -import io.split.android.client.storage.db.rbs.RuleBasedSegmentDao; -import io.split.android.client.storage.db.rbs.RuleBasedSegmentEntity; -import io.split.android.client.storage.general.GeneralInfoStorage; -import io.split.android.client.utils.Json; -import io.split.android.client.utils.logger.Logger; - -class SqLitePersistentRuleBasedSegmentStorageImpl implements PersistentRuleBasedSegmentStorage { - - private final RuleBasedSegmentDao mDao; - private final SplitRoomDatabase mDatabase; - private final GeneralInfoStorage mGeneralInfoStorage; - private final SplitCipher mCipher; - - public SqLitePersistentRuleBasedSegmentStorageImpl(SplitCipher cipher, - SplitRoomDatabase database, - GeneralInfoStorage generalInfoStorage) { - mCipher = checkNotNull(cipher); - mDatabase = checkNotNull(database); - mDao = database.ruleBasedSegmentDao(); - mGeneralInfoStorage = checkNotNull(generalInfoStorage); - } - - @Override - public RuleBasedSegmentSnapshot getSnapshot() { - return mDatabase.runInTransaction(new SnapshotLoader(mDao, mCipher, mGeneralInfoStorage)); - } - - @Override - public void update(Set toAdd, Set toRemove, long changeNumber) { - mDatabase.runInTransaction(new Updater(mCipher, mDao, mGeneralInfoStorage, toAdd, toRemove, changeNumber)); - } - - @Override - public void clear() { - mDatabase.runInTransaction(new Runnable() { - @Override - public void run() { - try { - mDao.deleteAll(); - mGeneralInfoStorage.setRbsChangeNumber(-1); - } catch (Exception e) { - Logger.e("Error clearing RBS: " + e.getLocalizedMessage()); - throw e; - } - } - }); - } - - static final class SnapshotLoader implements Callable { - - private final RuleBasedSegmentDao mDao; - private final SplitCipher mCipher; - private final GeneralInfoStorage mGeneralInfoStorage; - - public SnapshotLoader(RuleBasedSegmentDao dao, SplitCipher cipher, GeneralInfoStorage generalInfoStorage) { - mDao = checkNotNull(dao); - mCipher = checkNotNull(cipher); - mGeneralInfoStorage = checkNotNull(generalInfoStorage); - } - - @Override - public RuleBasedSegmentSnapshot call() { - try { - long changeNumber = mGeneralInfoStorage.getFlagsChangeNumber(); - List entities = mDao.getAll(); - Map segments = convertToDTOs(entities); - - return new RuleBasedSegmentSnapshot(segments, changeNumber); - } catch (Exception e) { - Logger.e("Error loading RBS from persistent storage", e.getLocalizedMessage()); - throw e; - } - } - - private Map convertToDTOs(List entities) { - Map segments = new HashMap<>(); - if (entities != null) { - for (RuleBasedSegmentEntity entity : entities) { - String name = mCipher.decrypt(entity.getName()); - String body = mCipher.encrypt(entity.getBody()); - if (name == null || body == null) { - continue; - } - - RuleBasedSegment ruleBasedSegment = Json.fromJson(body, RuleBasedSegment.class); - segments.put(name, ruleBasedSegment); - } - } - return segments; - } - } - - static final class Updater implements Runnable { - - @NonNull - private final SplitCipher mCipher; - @NonNull - private final GeneralInfoStorage mGeneralInfoStorage; - @NonNull - private final RuleBasedSegmentDao mDao; - @NonNull - private final Set mToAdd; - @NonNull - private final Set mToRemove; - private final long mChangeNumber; - - public Updater(@NonNull SplitCipher cipher, - @NonNull RuleBasedSegmentDao dao, - @NonNull GeneralInfoStorage generalInfoStorage, - @NonNull Set toAdd, - @NonNull Set toRemove, - long changeNumber) { - mCipher = checkNotNull(cipher); - mDao = checkNotNull(dao); - mGeneralInfoStorage = checkNotNull(generalInfoStorage); - mToAdd = checkNotNull(toAdd); - mToRemove = checkNotNull(toRemove); - mChangeNumber = changeNumber; - } - - @Override - public void run() { - try { - List toDelete = new ArrayList<>(); - for (RuleBasedSegment segment : mToRemove) { - toDelete.add(mCipher.encrypt(segment.getName())); - } - - List toAdd = new ArrayList<>(); - for (RuleBasedSegment segment : mToAdd) { - String name = mCipher.encrypt(segment.getName()); - String body = mCipher.encrypt(Json.toJson(segment)); - toAdd.add(new RuleBasedSegmentEntity(name, body, System.currentTimeMillis())); - } - - mDao.delete(toDelete); - mDao.insert(toAdd); - mGeneralInfoStorage.setRbsChangeNumber(mChangeNumber); - } catch (Exception e) { - Logger.e("Error updating RBS: " + e.getLocalizedMessage()); - throw e; - } - } - } -} diff --git a/src/main/java/io/split/android/client/storage/rbs/SqLitePersistentRuleBasedSegmentStorageProvider.java b/src/main/java/io/split/android/client/storage/rbs/SqLitePersistentRuleBasedSegmentStorageProvider.java new file mode 100644 index 000000000..eb5a279b6 --- /dev/null +++ b/src/main/java/io/split/android/client/storage/rbs/SqLitePersistentRuleBasedSegmentStorageProvider.java @@ -0,0 +1,19 @@ +package io.split.android.client.storage.rbs; + +import io.split.android.client.storage.cipher.SplitCipher; +import io.split.android.client.storage.db.SplitRoomDatabase; +import io.split.android.client.storage.general.GeneralInfoStorage; + +public class SqLitePersistentRuleBasedSegmentStorageProvider implements PersistentRuleBasedSegmentStorage.Provider { + + private final SqLitePersistentRuleBasedSegmentStorage mPersistentStorage; + + public SqLitePersistentRuleBasedSegmentStorageProvider(SplitCipher cipher, SplitRoomDatabase database, GeneralInfoStorage generalInfoStorage) { + mPersistentStorage = new SqLitePersistentRuleBasedSegmentStorage(cipher, database, generalInfoStorage); + } + + @Override + public PersistentRuleBasedSegmentStorage get() { + return mPersistentStorage; + } +} diff --git a/src/main/java/io/split/android/client/storage/rbs/Updater.java b/src/main/java/io/split/android/client/storage/rbs/Updater.java new file mode 100644 index 000000000..375d03968 --- /dev/null +++ b/src/main/java/io/split/android/client/storage/rbs/Updater.java @@ -0,0 +1,70 @@ +package io.split.android.client.storage.rbs; + +import static io.split.android.client.utils.Utils.checkNotNull; + +import androidx.annotation.NonNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import io.split.android.client.dtos.RuleBasedSegment; +import io.split.android.client.storage.cipher.SplitCipher; +import io.split.android.client.storage.db.rbs.RuleBasedSegmentDao; +import io.split.android.client.storage.db.rbs.RuleBasedSegmentEntity; +import io.split.android.client.storage.general.GeneralInfoStorage; +import io.split.android.client.utils.Json; +import io.split.android.client.utils.logger.Logger; + +final class Updater implements Runnable { + + @NonNull + private final SplitCipher mCipher; + @NonNull + private final GeneralInfoStorage mGeneralInfoStorage; + @NonNull + private final RuleBasedSegmentDao mDao; + @NonNull + private final Set mToAdd; + @NonNull + private final Set mToRemove; + private final long mChangeNumber; + + Updater(@NonNull SplitCipher cipher, + @NonNull RuleBasedSegmentDao dao, + @NonNull GeneralInfoStorage generalInfoStorage, + @NonNull Set toAdd, + @NonNull Set toRemove, + long changeNumber) { + mCipher = checkNotNull(cipher); + mDao = checkNotNull(dao); + mGeneralInfoStorage = checkNotNull(generalInfoStorage); + mToAdd = checkNotNull(toAdd); + mToRemove = checkNotNull(toRemove); + mChangeNumber = changeNumber; + } + + @Override + public void run() { + try { + List toDelete = new ArrayList<>(); + for (RuleBasedSegment segment : mToRemove) { + toDelete.add(mCipher.encrypt(segment.getName())); + } + + List toAdd = new ArrayList<>(); + for (RuleBasedSegment segment : mToAdd) { + String name = mCipher.encrypt(segment.getName()); + String body = mCipher.encrypt(Json.toJson(segment)); + toAdd.add(new RuleBasedSegmentEntity(name, body, System.currentTimeMillis())); + } + + mDao.delete(toDelete); + mDao.insert(toAdd); + mGeneralInfoStorage.setRbsChangeNumber(mChangeNumber); + } catch (Exception e) { + Logger.e("Error updating RBS: " + e.getLocalizedMessage()); + throw e; + } + } +} \ No newline at end of file diff --git a/src/test/java/io/split/android/client/storage/rbs/SqLitePersistentRuleBasedSegmentStorageTest.java b/src/test/java/io/split/android/client/storage/rbs/SqLitePersistentRuleBasedSegmentStorageTest.java new file mode 100644 index 000000000..540548ff2 --- /dev/null +++ b/src/test/java/io/split/android/client/storage/rbs/SqLitePersistentRuleBasedSegmentStorageTest.java @@ -0,0 +1,6 @@ +package io.split.android.client.storage.rbs; + +public class SqLitePersistentRuleBasedSegmentStorageTest { + + +} From 2b2b4249f21dec3e5acbd688017abd5422203f22 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Tue, 25 Feb 2025 16:49:51 -0300 Subject: [PATCH 03/11] SqLite persistent storage tests --- src/sharedTest/java/helper/TestingHelper.java | 15 ++- ...PersistentRuleBasedSegmentStorageTest.java | 105 +++++++++++++++++- 2 files changed, 116 insertions(+), 4 deletions(-) diff --git a/src/sharedTest/java/helper/TestingHelper.java b/src/sharedTest/java/helper/TestingHelper.java index 2d70138d2..af190706d 100644 --- a/src/sharedTest/java/helper/TestingHelper.java +++ b/src/sharedTest/java/helper/TestingHelper.java @@ -1,5 +1,8 @@ package helper; +import static java.lang.Thread.sleep; + +import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; import java.util.concurrent.BlockingQueue; @@ -12,8 +15,6 @@ import io.split.android.client.events.SplitEventTask; import io.split.android.client.utils.logger.Logger; -import static java.lang.Thread.sleep; - public class TestingHelper { public static final String COUNTERS_REFRESH_RATE_SECS_NAME = "COUNTERS_REFRESH_RATE_SECS"; @@ -120,4 +121,14 @@ public static KeyImpression newImpression(String feature, String key) { impression.time = 100; return impression; } + + public static Object getFieldValue(Object object, String fieldName) { + try { + Field field = object.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + return field.get(object); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException("Error accessing field: " + fieldName, e); + } + } } diff --git a/src/test/java/io/split/android/client/storage/rbs/SqLitePersistentRuleBasedSegmentStorageTest.java b/src/test/java/io/split/android/client/storage/rbs/SqLitePersistentRuleBasedSegmentStorageTest.java index 540548ff2..fc29bc70e 100644 --- a/src/test/java/io/split/android/client/storage/rbs/SqLitePersistentRuleBasedSegmentStorageTest.java +++ b/src/test/java/io/split/android/client/storage/rbs/SqLitePersistentRuleBasedSegmentStorageTest.java @@ -1,6 +1,107 @@ package io.split.android.client.storage.rbs; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThrows; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static helper.TestingHelper.getFieldValue; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +import java.util.HashSet; +import java.util.Set; + +import io.split.android.client.dtos.RuleBasedSegment; +import io.split.android.client.storage.cipher.SplitCipher; +import io.split.android.client.storage.db.SplitRoomDatabase; +import io.split.android.client.storage.db.rbs.RuleBasedSegmentDao; +import io.split.android.client.storage.general.GeneralInfoStorage; + public class SqLitePersistentRuleBasedSegmentStorageTest { - -} + private SplitCipher mCipher; + private SplitRoomDatabase mDatabase; + private RuleBasedSegmentDao mDao; + private GeneralInfoStorage mGeneralInfoStorage; + + private SqLitePersistentRuleBasedSegmentStorage storage; + + @Before + public void setUp() { + mCipher = mock(SplitCipher.class); + mDatabase = mock(SplitRoomDatabase.class); + mDao = mock(RuleBasedSegmentDao.class); + mGeneralInfoStorage = mock(GeneralInfoStorage.class); + when(mDatabase.ruleBasedSegmentDao()).thenReturn(mDao); + + storage = new SqLitePersistentRuleBasedSegmentStorage(mCipher, mDatabase, mGeneralInfoStorage); + } + + @Test + public void getSnapshotBuildsAndRunsSnapshotLoaderInstanceInTransaction() { + RuleBasedSegmentSnapshot expectedSnapshot = mock(RuleBasedSegmentSnapshot.class); + when(mDatabase.runInTransaction((SnapshotLoader) any())).thenReturn(expectedSnapshot); + + RuleBasedSegmentSnapshot result = storage.getSnapshot(); + + ArgumentCaptor captor = ArgumentCaptor.forClass(SnapshotLoader.class); + verify(mDatabase).runInTransaction(captor.capture()); + SnapshotLoader snapshotLoader = captor.getValue(); + assertSame(mDao, getFieldValue(snapshotLoader, "mDao")); + assertSame(mCipher, getFieldValue(snapshotLoader, "mCipher")); + assertSame(mGeneralInfoStorage, getFieldValue(snapshotLoader, "mGeneralInfoStorage")); + assertSame(expectedSnapshot, result); + } + + @Test + public void updateBuildsAndRunsUpdaterInstanceInTransaction() { + Set toAdd = new HashSet<>(); + Set toRemove = new HashSet<>(); + long changeNumber = 123L; + + storage.update(toAdd, toRemove, changeNumber); + + ArgumentCaptor captor = ArgumentCaptor.forClass(Updater.class); + verify(mDatabase).runInTransaction(captor.capture()); + Updater updater = captor.getValue(); + assertSame(mCipher, getFieldValue(updater, "mCipher")); + assertSame(mDao, getFieldValue(updater, "mDao")); + assertSame(mGeneralInfoStorage, getFieldValue(updater, "mGeneralInfoStorage")); + assertSame(toAdd, getFieldValue(updater, "mToAdd")); + assertSame(toRemove, getFieldValue(updater, "mToRemove")); + assertSame(changeNumber, getFieldValue(updater, "mChangeNumber")); + } + + @Test + public void clearBuildsAndRunsClearerInstanceInTransaction() { + storage.clear(); + + ArgumentCaptor captor = ArgumentCaptor.forClass(Clearer.class); + verify(mDatabase).runInTransaction(captor.capture()); + Clearer clearer = captor.getValue(); + assertSame(mDao, getFieldValue(clearer, "mDao")); + assertSame(mGeneralInfoStorage, getFieldValue(clearer, "mGeneralInfoStorage")); + } + + @Test + public void cipherCannotBeNull() { + assertThrows(NullPointerException.class, + () -> new SqLitePersistentRuleBasedSegmentStorage(null, mDatabase, mGeneralInfoStorage)); + } + + @Test + public void databaseCannotBeNull() { + assertThrows(NullPointerException.class, + () -> new SqLitePersistentRuleBasedSegmentStorage(mCipher, null, mGeneralInfoStorage)); + } + + @Test + public void generalInfoStorageCannotBeNull() { + assertThrows(NullPointerException.class, + () -> new SqLitePersistentRuleBasedSegmentStorage(mCipher, mDatabase, null)); + } +} \ No newline at end of file From e292005276b899a285e41aff299d20a186a0f248 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Tue, 25 Feb 2025 17:29:05 -0300 Subject: [PATCH 04/11] Tests --- .../client/storage/rbs/SnapshotLoader.java | 2 +- .../rbs/RuleBasedSegmentStorageImplTest.java | 2 +- .../storage/rbs/SnapshotLoaderTest.java | 118 ++++++++++++++++++ .../client/storage/rbs/UpdaterTest.java | 101 +++++++++++++++ 4 files changed, 221 insertions(+), 2 deletions(-) create mode 100644 src/test/java/io/split/android/client/storage/rbs/SnapshotLoaderTest.java create mode 100644 src/test/java/io/split/android/client/storage/rbs/UpdaterTest.java diff --git a/src/main/java/io/split/android/client/storage/rbs/SnapshotLoader.java b/src/main/java/io/split/android/client/storage/rbs/SnapshotLoader.java index 11cea6182..716ba00fd 100644 --- a/src/main/java/io/split/android/client/storage/rbs/SnapshotLoader.java +++ b/src/main/java/io/split/android/client/storage/rbs/SnapshotLoader.java @@ -46,7 +46,7 @@ private Map convertToDTOs(List if (entities != null) { for (RuleBasedSegmentEntity entity : entities) { String name = mCipher.decrypt(entity.getName()); - String body = mCipher.encrypt(entity.getBody()); + String body = mCipher.decrypt(entity.getBody()); if (name == null || body == null) { continue; } 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 4596bf921..d19b02225 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 @@ -193,7 +193,7 @@ public void clearCallsClearOnPersistentStorage() { verify(mPersistentStorage).clear(); } - private static RuleBasedSegment createRuleBasedSegment(String name) { + static RuleBasedSegment createRuleBasedSegment(String name) { return new RuleBasedSegment(name, "user", 1, diff --git a/src/test/java/io/split/android/client/storage/rbs/SnapshotLoaderTest.java b/src/test/java/io/split/android/client/storage/rbs/SnapshotLoaderTest.java new file mode 100644 index 000000000..da61849c6 --- /dev/null +++ b/src/test/java/io/split/android/client/storage/rbs/SnapshotLoaderTest.java @@ -0,0 +1,118 @@ +package io.split.android.client.storage.rbs; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThrows; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.junit.Before; +import org.junit.Test; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import io.split.android.client.dtos.RuleBasedSegment; +import io.split.android.client.storage.cipher.SplitCipher; +import io.split.android.client.storage.db.rbs.RuleBasedSegmentDao; +import io.split.android.client.storage.db.rbs.RuleBasedSegmentEntity; +import io.split.android.client.storage.general.GeneralInfoStorage; + +public class SnapshotLoaderTest { + + private RuleBasedSegmentDao mDao; + private SplitCipher mCipher; + private GeneralInfoStorage mGeneralInfoStorage; + private SnapshotLoader mSnapshotLoader; + + @Before + public void setUp() { + mDao = mock(RuleBasedSegmentDao.class); + mCipher = mock(SplitCipher.class); + mGeneralInfoStorage = mock(GeneralInfoStorage.class); + mSnapshotLoader = new SnapshotLoader(mDao, mCipher, mGeneralInfoStorage); + } + + @Test + public void callReturnsCorrectSnapshotWithDecryptedSegments() throws Exception { + long expectedChangeNumber = 123L; + when(mGeneralInfoStorage.getFlagsChangeNumber()).thenReturn(expectedChangeNumber); + + RuleBasedSegmentEntity entity1 = new RuleBasedSegmentEntity("segment1", "encryptedBody1", System.currentTimeMillis()); + RuleBasedSegmentEntity entity2 = new RuleBasedSegmentEntity("segment2", "encryptedBody2", System.currentTimeMillis()); + List entities = Arrays.asList(entity1, entity2); + when(mDao.getAll()).thenReturn(entities); + + when(mCipher.decrypt("segment1")).thenReturn("segment1"); + when(mCipher.decrypt("segment2")).thenReturn("segment2"); + when(mCipher.decrypt("encryptedBody1")).thenAnswer(invocation -> "{ \"name\": \"segment1\", \"trafficTypeName\": \"user\", \"changeNumber\": 1 }"); + when(mCipher.decrypt("encryptedBody2")).thenAnswer(invocation -> "{ \"name\": \"segment2\", \"trafficTypeName\": \"user\", \"changeNumber\": 2 }"); + + RuleBasedSegmentSnapshot result = mSnapshotLoader.call(); + + assertNotNull(result); + assertEquals(expectedChangeNumber, result.getChangeNumber()); + + Map segments = result.getSegments(); + assertEquals(2, segments.size()); + RuleBasedSegment rbs1 = segments.get("segment1"); + assertNotNull(rbs1); + assertEquals("segment1", rbs1.getName()); + assertEquals("user", rbs1.getTrafficTypeName()); + assertEquals(1, rbs1.getChangeNumber()); + + RuleBasedSegment rbs2 = segments.get("segment2"); + assertNotNull(rbs2); + assertEquals("segment2", rbs2.getName()); + assertEquals("user", rbs2.getTrafficTypeName()); + assertEquals(2, rbs2.getChangeNumber()); + } + + @Test + public void callGetsChangeNumberFromGeneralInfoStorage() { + mSnapshotLoader.call(); + + verify(mGeneralInfoStorage).getFlagsChangeNumber(); + } + + @Test + public void callGetsAllSegmentsFromDao() { + mSnapshotLoader.call(); + + verify(mDao).getAll(); + } + + @Test + public void callDecryptsNameAndBodyFromEntity() { + when(mDao.getAll()).thenReturn(Arrays.asList( + new RuleBasedSegmentEntity("segment1", "encryptedBody1", System.currentTimeMillis()), + new RuleBasedSegmentEntity("segment2", "encryptedBody2", System.currentTimeMillis()))); + + mSnapshotLoader.call(); + + verify(mCipher).decrypt("segment1"); + verify(mCipher).decrypt("segment2"); + verify(mCipher).decrypt("encryptedBody1"); + verify(mCipher).decrypt("encryptedBody2"); + } + + @Test + public void constructorThrowsNullPointerExceptionWhenDaoIsNull() { + assertThrows(NullPointerException.class, + () -> new SnapshotLoader(null, mCipher, mGeneralInfoStorage)); + } + + @Test + public void constructorThrowsNullPointerExceptionWhenCipherIsNull() { + assertThrows(NullPointerException.class, + () -> new SnapshotLoader(mDao, null, mGeneralInfoStorage)); + } + + @Test + public void constructorThrowsNullPointerExceptionWhenGeneralInfoStorageIsNull() { + assertThrows(NullPointerException.class, + () -> new SnapshotLoader(mDao, mCipher, null)); + } +} \ No newline at end of file diff --git a/src/test/java/io/split/android/client/storage/rbs/UpdaterTest.java b/src/test/java/io/split/android/client/storage/rbs/UpdaterTest.java new file mode 100644 index 000000000..d7820b313 --- /dev/null +++ b/src/test/java/io/split/android/client/storage/rbs/UpdaterTest.java @@ -0,0 +1,101 @@ +package io.split.android.client.storage.rbs; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static io.split.android.client.storage.rbs.RuleBasedSegmentStorageImplTest.createRuleBasedSegment; + +import androidx.annotation.NonNull; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentMatcher; + +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Set; + +import io.split.android.client.dtos.RuleBasedSegment; +import io.split.android.client.storage.cipher.SplitCipher; +import io.split.android.client.storage.db.rbs.RuleBasedSegmentDao; +import io.split.android.client.storage.db.rbs.RuleBasedSegmentEntity; +import io.split.android.client.storage.general.GeneralInfoStorage; + +public class UpdaterTest { + + private SplitCipher mCipher; + private RuleBasedSegmentDao mDao; + private GeneralInfoStorage mGeneralInfoStorage; + private Updater mUpdater; + + @Before + public void setUp() { + mCipher = mock(SplitCipher.class); + mDao = mock(RuleBasedSegmentDao.class); + mGeneralInfoStorage = mock(GeneralInfoStorage.class); + } + + @Test + public void runEncryptsRemovedSegmentNamesBeforeSendingToDao() { + Set toRemove = Set.of( + createRuleBasedSegment("segment1"), createRuleBasedSegment("segment2")); + when(mCipher.encrypt(any())).thenAnswer(invocation -> "encrypted_" + invocation.getArgument(0)); + mUpdater = createUpdater(Collections.emptySet(), toRemove, 10); + + mUpdater.run(); + + verify(mCipher).encrypt("segment1"); + verify(mCipher).encrypt("segment2"); + verify(mDao).delete(argThat(new ArgumentMatcher>() { + @Override + public boolean matches(List argument) { + return argument.size() == 2 && + argument.contains("encrypted_segment1") && + argument.contains("encrypted_segment2"); + } + })); + } + + @Test + public void runEncryptsAddedSegmentNamesBeforeSendingToDao() { + Set toAdd = Set.of( + createRuleBasedSegment("segment1"), createRuleBasedSegment("segment2")); + when(mCipher.encrypt(any())).thenAnswer(invocation -> "encrypted_" + invocation.getArgument(0)); + mUpdater = createUpdater(toAdd, Collections.emptySet(), 10); + + mUpdater.run(); + + verify(mCipher).encrypt("segment1"); + verify(mCipher).encrypt("segment2"); + verify(mDao).insert(argThat(new ArgumentMatcher>() { + @Override + public boolean matches(List argument) { + argument.sort(Comparator.comparing(RuleBasedSegmentEntity::getName)); + RuleBasedSegmentEntity ruleBasedSegmentEntity = argument.get(0); + RuleBasedSegmentEntity ruleBasedSegmentEntity1 = argument.get(1); + return argument.size() == 2 && + ruleBasedSegmentEntity.getName().equals("encrypted_segment1") && + ruleBasedSegmentEntity1.getName().equals("encrypted_segment2") && + ruleBasedSegmentEntity.getBody().startsWith("encrypted_") && + ruleBasedSegmentEntity1.getBody().startsWith("encrypted_"); + } + })); + } + + @Test + public void runUpdatesChangeNumber() { + mUpdater = createUpdater(Collections.emptySet(), Collections.emptySet(), 10); + + mUpdater.run(); + + verify(mGeneralInfoStorage).setRbsChangeNumber(10); + } + + @NonNull + private Updater createUpdater(Set toAdd, Set toRemove, long changeNumber) { + return new Updater(mCipher, mDao, mGeneralInfoStorage, toAdd, toRemove, changeNumber); + } +} From bae1f83294121755c4c81a6c5ccfe38ce7288405 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Tue, 25 Feb 2025 17:37:26 -0300 Subject: [PATCH 05/11] Fixes --- .../android/client/storage/rbs/Clearer.java | 6 +++-- .../android/client/storage/rbs/Updater.java | 2 +- .../storage/rbs/SnapshotLoaderTest.java | 2 +- ...PersistentRuleBasedSegmentStorageTest.java | 2 +- ...SegmentsPersistentStorageProviderTest.java | 22 +++++++++++++++++++ 5 files changed, 29 insertions(+), 5 deletions(-) create mode 100644 src/test/java/io/split/android/client/storage/rbs/SqLiteRuleBasedSegmentsPersistentStorageProviderTest.java diff --git a/src/main/java/io/split/android/client/storage/rbs/Clearer.java b/src/main/java/io/split/android/client/storage/rbs/Clearer.java index 30327f124..1e0ab4a9d 100644 --- a/src/main/java/io/split/android/client/storage/rbs/Clearer.java +++ b/src/main/java/io/split/android/client/storage/rbs/Clearer.java @@ -1,5 +1,7 @@ package io.split.android.client.storage.rbs; +import static io.split.android.client.utils.Utils.checkNotNull; + import io.split.android.client.storage.db.rbs.RuleBasedSegmentDao; import io.split.android.client.storage.general.GeneralInfoStorage; import io.split.android.client.utils.logger.Logger; @@ -10,8 +12,8 @@ class Clearer implements Runnable { private final GeneralInfoStorage mGeneralInfoStorage; public Clearer(RuleBasedSegmentDao dao, GeneralInfoStorage generalInfoStorage) { - mDao = dao; - mGeneralInfoStorage = generalInfoStorage; + mDao = checkNotNull(dao); + mGeneralInfoStorage = checkNotNull(generalInfoStorage); } @Override diff --git a/src/main/java/io/split/android/client/storage/rbs/Updater.java b/src/main/java/io/split/android/client/storage/rbs/Updater.java index 375d03968..93fb76ac4 100644 --- a/src/main/java/io/split/android/client/storage/rbs/Updater.java +++ b/src/main/java/io/split/android/client/storage/rbs/Updater.java @@ -67,4 +67,4 @@ public void run() { throw e; } } -} \ No newline at end of file +} diff --git a/src/test/java/io/split/android/client/storage/rbs/SnapshotLoaderTest.java b/src/test/java/io/split/android/client/storage/rbs/SnapshotLoaderTest.java index da61849c6..50bc36d81 100644 --- a/src/test/java/io/split/android/client/storage/rbs/SnapshotLoaderTest.java +++ b/src/test/java/io/split/android/client/storage/rbs/SnapshotLoaderTest.java @@ -115,4 +115,4 @@ public void constructorThrowsNullPointerExceptionWhenGeneralInfoStorageIsNull() assertThrows(NullPointerException.class, () -> new SnapshotLoader(mDao, mCipher, null)); } -} \ No newline at end of file +} diff --git a/src/test/java/io/split/android/client/storage/rbs/SqLitePersistentRuleBasedSegmentStorageTest.java b/src/test/java/io/split/android/client/storage/rbs/SqLitePersistentRuleBasedSegmentStorageTest.java index fc29bc70e..13c646e0c 100644 --- a/src/test/java/io/split/android/client/storage/rbs/SqLitePersistentRuleBasedSegmentStorageTest.java +++ b/src/test/java/io/split/android/client/storage/rbs/SqLitePersistentRuleBasedSegmentStorageTest.java @@ -104,4 +104,4 @@ public void generalInfoStorageCannotBeNull() { assertThrows(NullPointerException.class, () -> new SqLitePersistentRuleBasedSegmentStorage(mCipher, mDatabase, null)); } -} \ No newline at end of file +} diff --git a/src/test/java/io/split/android/client/storage/rbs/SqLiteRuleBasedSegmentsPersistentStorageProviderTest.java b/src/test/java/io/split/android/client/storage/rbs/SqLiteRuleBasedSegmentsPersistentStorageProviderTest.java new file mode 100644 index 000000000..a6cd2dee8 --- /dev/null +++ b/src/test/java/io/split/android/client/storage/rbs/SqLiteRuleBasedSegmentsPersistentStorageProviderTest.java @@ -0,0 +1,22 @@ +package io.split.android.client.storage.rbs; + +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; + +import org.junit.Test; + +import io.split.android.client.storage.cipher.SplitCipher; +import io.split.android.client.storage.db.SplitRoomDatabase; +import io.split.android.client.storage.general.GeneralInfoStorage; + +public class SqLiteRuleBasedSegmentsPersistentStorageProviderTest { + + @Test + public void providesSqLiteImplementation() { + PersistentRuleBasedSegmentStorage.Provider provider = + new SqLitePersistentRuleBasedSegmentStorageProvider(mock(SplitCipher.class), mock(SplitRoomDatabase.class), mock(GeneralInfoStorage.class)); + PersistentRuleBasedSegmentStorage persistentRuleBasedSegmentStorage = provider.get(); + + assertTrue(persistentRuleBasedSegmentStorage instanceof SqLitePersistentRuleBasedSegmentStorage); + } +} From c1c96dd2ae5c91f3c000803b29bfb5811123d5a3 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Tue, 25 Feb 2025 17:42:42 -0300 Subject: [PATCH 06/11] Add exception handling --- .../split/android/client/storage/rbs/SnapshotLoader.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/split/android/client/storage/rbs/SnapshotLoader.java b/src/main/java/io/split/android/client/storage/rbs/SnapshotLoader.java index 716ba00fd..16ddb1075 100644 --- a/src/main/java/io/split/android/client/storage/rbs/SnapshotLoader.java +++ b/src/main/java/io/split/android/client/storage/rbs/SnapshotLoader.java @@ -51,8 +51,12 @@ private Map convertToDTOs(List continue; } - RuleBasedSegment ruleBasedSegment = Json.fromJson(body, RuleBasedSegment.class); - segments.put(name, ruleBasedSegment); + try { + RuleBasedSegment ruleBasedSegment = Json.fromJson(body, RuleBasedSegment.class); + segments.put(name, ruleBasedSegment); + } catch (Exception e) { + Logger.e("Error parsing RBS with name " + name + ": " + e.getLocalizedMessage()); + } } } return segments; From ad2332297075ecc376479a45cf5d5d2e70ec7318 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Tue, 25 Feb 2025 17:54:02 -0300 Subject: [PATCH 07/11] More tests --- .../android/client/storage/rbs/Updater.java | 22 +++++++++++++---- .../client/storage/rbs/UpdaterTest.java | 24 +++++++++++++++++++ 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/split/android/client/storage/rbs/Updater.java b/src/main/java/io/split/android/client/storage/rbs/Updater.java index 93fb76ac4..12e5c3fb2 100644 --- a/src/main/java/io/split/android/client/storage/rbs/Updater.java +++ b/src/main/java/io/split/android/client/storage/rbs/Updater.java @@ -49,14 +49,28 @@ public void run() { try { List toDelete = new ArrayList<>(); for (RuleBasedSegment segment : mToRemove) { - toDelete.add(mCipher.encrypt(segment.getName())); + String encryptedName = mCipher.encrypt(segment.getName()); + if (encryptedName != null) { + toDelete.add(encryptedName); + } } List toAdd = new ArrayList<>(); for (RuleBasedSegment segment : mToAdd) { - String name = mCipher.encrypt(segment.getName()); - String body = mCipher.encrypt(Json.toJson(segment)); - toAdd.add(new RuleBasedSegmentEntity(name, body, System.currentTimeMillis())); + if (segment == null) { + continue; + } + + try { + String name = mCipher.encrypt(segment.getName()); + String body = mCipher.encrypt(Json.toJson(segment)); + if (name == null || body == null) { + continue; + } + toAdd.add(new RuleBasedSegmentEntity(name, body, System.currentTimeMillis())); + } catch (Exception e) { + Logger.e("Error parsing RBS with name " + segment.getName() + ": " + e.getLocalizedMessage()); + } } mDao.delete(toDelete); diff --git a/src/test/java/io/split/android/client/storage/rbs/UpdaterTest.java b/src/test/java/io/split/android/client/storage/rbs/UpdaterTest.java index d7820b313..edb779f98 100644 --- a/src/test/java/io/split/android/client/storage/rbs/UpdaterTest.java +++ b/src/test/java/io/split/android/client/storage/rbs/UpdaterTest.java @@ -1,6 +1,7 @@ package io.split.android.client.storage.rbs; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -94,6 +95,29 @@ public void runUpdatesChangeNumber() { verify(mGeneralInfoStorage).setRbsChangeNumber(10); } + @Test + public void runDoesNotUpdateSegmentIfEncryptedNameIsNull() { + Set toAdd = Set.of( + createRuleBasedSegment("segment1"), createRuleBasedSegment("segment2")); + Set toRemove = Set.of( + createRuleBasedSegment("segment3"), createRuleBasedSegment("segment4")); + when(mCipher.encrypt(anyString())).thenReturn(null); + when(mCipher.encrypt(argThat(argument -> argument.contains("segment1")))).thenReturn("encrypted_segment1"); + when(mCipher.encrypt("segment3")).thenReturn("encrypted_segment3"); + mUpdater = createUpdater(toAdd, toRemove, 10); + + mUpdater.run(); + + verify(mCipher).encrypt("segment1"); + verify(mCipher).encrypt("segment2"); + verify(mCipher).encrypt("segment3"); + verify(mCipher).encrypt("segment4"); + verify(mDao).delete(argThat(argument -> argument.size() == 1 && + argument.get(0).equals("encrypted_segment3"))); + verify(mDao).insert(argThat((ArgumentMatcher>) argument -> argument.size() == 1 && + argument.get(0).getName().equals("encrypted_segment1"))); + } + @NonNull private Updater createUpdater(Set toAdd, Set toRemove, long changeNumber) { return new Updater(mCipher, mDao, mGeneralInfoStorage, toAdd, toRemove, changeNumber); From 2fc3f2ed8c09435396012774ab74cb3a14e75860 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Tue, 25 Feb 2025 18:08:20 -0300 Subject: [PATCH 08/11] Add nullability annotations --- .../client/storage/rbs/RuleBasedSegmentSnapshot.java | 4 +++- .../io/split/android/client/storage/rbs/SnapshotLoader.java | 6 +++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/split/android/client/storage/rbs/RuleBasedSegmentSnapshot.java b/src/main/java/io/split/android/client/storage/rbs/RuleBasedSegmentSnapshot.java index d62e31a42..69f050398 100644 --- a/src/main/java/io/split/android/client/storage/rbs/RuleBasedSegmentSnapshot.java +++ b/src/main/java/io/split/android/client/storage/rbs/RuleBasedSegmentSnapshot.java @@ -2,6 +2,8 @@ import static io.split.android.client.utils.Utils.checkNotNull; +import androidx.annotation.NonNull; + import java.util.Map; import io.split.android.client.dtos.RuleBasedSegment; @@ -12,7 +14,7 @@ public class RuleBasedSegmentSnapshot { private final long mChangeNumber; - public RuleBasedSegmentSnapshot(Map segments, long changeNumber) { + public RuleBasedSegmentSnapshot(@NonNull Map segments, long changeNumber) { mSegments = checkNotNull(segments); mChangeNumber = changeNumber; } diff --git a/src/main/java/io/split/android/client/storage/rbs/SnapshotLoader.java b/src/main/java/io/split/android/client/storage/rbs/SnapshotLoader.java index 16ddb1075..d56a54a66 100644 --- a/src/main/java/io/split/android/client/storage/rbs/SnapshotLoader.java +++ b/src/main/java/io/split/android/client/storage/rbs/SnapshotLoader.java @@ -2,6 +2,9 @@ import static io.split.android.client.utils.Utils.checkNotNull; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import java.util.HashMap; import java.util.List; import java.util.Map; @@ -41,7 +44,8 @@ public RuleBasedSegmentSnapshot call() { } } - private Map convertToDTOs(List entities) { + @NonNull + private Map convertToDTOs(@Nullable List entities) { Map segments = new HashMap<>(); if (entities != null) { for (RuleBasedSegmentEntity entity : entities) { From a8bff33f4bf7cb667ccdcfdd60c39e282dbe8128 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 27 Feb 2025 12:50:06 -0300 Subject: [PATCH 09/11] Prep for ParserCommons --- .../android/client/dtos/MatcherType.java | 5 ++- .../LocalhostRuleBasedSegmentsStorage.java | 44 +++++++++++++++++++ ...lhostRuleBasedSegmentsStorageProvider.java | 21 +++++++++ .../client/storage/db/StorageFactory.java | 12 +++++ .../LazyRuleBasedSegmentStorageProvider.java | 28 ++++++++++++ .../rbs/RuleBasedSegmentStorageProvider.java | 9 ++++ 6 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 src/main/java/io/split/android/client/localhost/LocalhostRuleBasedSegmentsStorage.java create mode 100644 src/main/java/io/split/android/client/localhost/LocalhostRuleBasedSegmentsStorageProvider.java create mode 100644 src/main/java/io/split/android/client/storage/rbs/LazyRuleBasedSegmentStorageProvider.java create mode 100644 src/main/java/io/split/android/client/storage/rbs/RuleBasedSegmentStorageProvider.java diff --git a/src/main/java/io/split/android/client/dtos/MatcherType.java b/src/main/java/io/split/android/client/dtos/MatcherType.java index 580e97f2e..75d5325b7 100644 --- a/src/main/java/io/split/android/client/dtos/MatcherType.java +++ b/src/main/java/io/split/android/client/dtos/MatcherType.java @@ -60,5 +60,8 @@ public enum MatcherType { @SerializedName("BETWEEN_SEMVER") BETWEEN_SEMVER, @SerializedName("IN_LIST_SEMVER") - IN_LIST_SEMVER + IN_LIST_SEMVER, + + @SerializedName("IN_RULE_BASED_SEGMENT") + IN_RULE_BASED_SEGMENT, } diff --git a/src/main/java/io/split/android/client/localhost/LocalhostRuleBasedSegmentsStorage.java b/src/main/java/io/split/android/client/localhost/LocalhostRuleBasedSegmentsStorage.java new file mode 100644 index 000000000..137b1f6b0 --- /dev/null +++ b/src/main/java/io/split/android/client/localhost/LocalhostRuleBasedSegmentsStorage.java @@ -0,0 +1,44 @@ +package io.split.android.client.localhost; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.Set; + +import io.split.android.client.dtos.RuleBasedSegment; +import io.split.android.client.storage.rbs.RuleBasedSegmentStorage; +import io.split.android.engine.experiments.ParsedRuleBasedSegment; + +public class LocalhostRuleBasedSegmentsStorage implements RuleBasedSegmentStorage { + + @Nullable + @Override + public ParsedRuleBasedSegment get(String segmentName, String matchingKey) { + return null; + } + + @Override + public boolean update(@NonNull Set toAdd, @NonNull Set toRemove, long changeNumber) { + return false; + } + + @Override + public long getChangeNumber() { + return -1; + } + + @Override + public boolean contains(@NonNull Set segmentNames) { + return false; + } + + @Override + public void loadLocal() { + // no-op + } + + @Override + public void clear() { + // no-op + } +} diff --git a/src/main/java/io/split/android/client/localhost/LocalhostRuleBasedSegmentsStorageProvider.java b/src/main/java/io/split/android/client/localhost/LocalhostRuleBasedSegmentsStorageProvider.java new file mode 100644 index 000000000..2b5b10e10 --- /dev/null +++ b/src/main/java/io/split/android/client/localhost/LocalhostRuleBasedSegmentsStorageProvider.java @@ -0,0 +1,21 @@ +package io.split.android.client.localhost; + +import androidx.annotation.NonNull; + +import io.split.android.client.storage.rbs.RuleBasedSegmentStorage; +import io.split.android.client.storage.rbs.RuleBasedSegmentStorageProvider; + +class LocalhostRuleBasedSegmentsStorageProvider implements RuleBasedSegmentStorageProvider { + + private final RuleBasedSegmentStorage mRuleBasedSegmentStorage; + + LocalhostRuleBasedSegmentsStorageProvider(@NonNull RuleBasedSegmentStorage ruleBasedSegmentStorage) { + mRuleBasedSegmentStorage = ruleBasedSegmentStorage; + } + + @NonNull + @Override + public RuleBasedSegmentStorage get() { + return mRuleBasedSegmentStorage; + } +} diff --git a/src/main/java/io/split/android/client/storage/db/StorageFactory.java b/src/main/java/io/split/android/client/storage/db/StorageFactory.java index 2960f73f7..45b7b18bf 100644 --- a/src/main/java/io/split/android/client/storage/db/StorageFactory.java +++ b/src/main/java/io/split/android/client/storage/db/StorageFactory.java @@ -30,6 +30,10 @@ import io.split.android.client.storage.mysegments.MySegmentsStorageContainer; import io.split.android.client.storage.mysegments.MySegmentsStorageContainerImpl; import io.split.android.client.storage.mysegments.SqLitePersistentMySegmentsStorage; +import io.split.android.client.storage.rbs.LazyRuleBasedSegmentStorageProvider; +import io.split.android.client.storage.rbs.PersistentRuleBasedSegmentStorage; +import io.split.android.client.storage.rbs.RuleBasedSegmentStorageProvider; +import io.split.android.client.storage.rbs.SqLitePersistentRuleBasedSegmentStorageProvider; import io.split.android.client.storage.splits.PersistentSplitsStorage; import io.split.android.client.storage.splits.SplitsStorage; import io.split.android.client.storage.splits.SplitsStorageImpl; @@ -154,4 +158,12 @@ public static PersistentImpressionsObserverCacheStorage getImpressionsObserverCa public static GeneralInfoStorage getGeneralInfoStorage(SplitRoomDatabase splitRoomDatabase) { return new GeneralInfoStorageImpl(splitRoomDatabase.generalInfoDao()); } + + public static PersistentRuleBasedSegmentStorage getPersistentRuleBasedSegmentStorage(SplitRoomDatabase splitRoomDatabase, SplitCipher splitCipher, GeneralInfoStorage generalInfoStorage) { + return new SqLitePersistentRuleBasedSegmentStorageProvider(splitCipher, splitRoomDatabase, generalInfoStorage).get(); + } + + public static RuleBasedSegmentStorageProvider getRuleBasedSegmentStorageProvider() { + return new LazyRuleBasedSegmentStorageProvider(); + } } diff --git a/src/main/java/io/split/android/client/storage/rbs/LazyRuleBasedSegmentStorageProvider.java b/src/main/java/io/split/android/client/storage/rbs/LazyRuleBasedSegmentStorageProvider.java new file mode 100644 index 000000000..26977735d --- /dev/null +++ b/src/main/java/io/split/android/client/storage/rbs/LazyRuleBasedSegmentStorageProvider.java @@ -0,0 +1,28 @@ +package io.split.android.client.storage.rbs; + +import androidx.annotation.NonNull; + +import java.util.concurrent.atomic.AtomicReference; + +import io.split.android.client.utils.logger.Logger; + +public class LazyRuleBasedSegmentStorageProvider implements RuleBasedSegmentStorageProvider { + + private final AtomicReference mRuleBasedSegmentStorageRef = new AtomicReference<>(); + + public void set(@NonNull RuleBasedSegmentStorage ruleBasedSegmentStorage) { + if (!mRuleBasedSegmentStorageRef.compareAndSet(null, ruleBasedSegmentStorage)) { + Logger.w("RuleBasedSegmentStorage already set in LazyRuleBasedSegmentStorageProvider"); + } + } + + @NonNull + @Override + public RuleBasedSegmentStorage get() { + RuleBasedSegmentStorage storage = mRuleBasedSegmentStorageRef.get(); + if (storage == null) { + throw new IllegalStateException("RuleBasedSegmentStorage not set in LazyRuleBasedSegmentStorageProvider"); + } + return storage; + } +} diff --git a/src/main/java/io/split/android/client/storage/rbs/RuleBasedSegmentStorageProvider.java b/src/main/java/io/split/android/client/storage/rbs/RuleBasedSegmentStorageProvider.java new file mode 100644 index 000000000..2ccb00c69 --- /dev/null +++ b/src/main/java/io/split/android/client/storage/rbs/RuleBasedSegmentStorageProvider.java @@ -0,0 +1,9 @@ +package io.split.android.client.storage.rbs; + +import androidx.annotation.NonNull; + +public interface RuleBasedSegmentStorageProvider { + + @NonNull + RuleBasedSegmentStorage get(); +} From 537ed77beef717b33182511c089ea1cc34add395 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 27 Feb 2025 12:57:39 -0300 Subject: [PATCH 10/11] Tests for lazy storage provider --- ...lhostRuleBasedSegmentsStorageProvider.java | 3 +- .../LazyRuleBasedSegmentStorageProvider.java | 9 ++---- .../rbs/RuleBasedSegmentStorageProvider.java | 4 +-- ...zyRuleBasedSegmentStorageProviderTest.java | 29 +++++++++++++++++++ 4 files changed, 35 insertions(+), 10 deletions(-) create mode 100644 src/test/java/io/split/android/client/storage/rbs/LazyRuleBasedSegmentStorageProviderTest.java diff --git a/src/main/java/io/split/android/client/localhost/LocalhostRuleBasedSegmentsStorageProvider.java b/src/main/java/io/split/android/client/localhost/LocalhostRuleBasedSegmentsStorageProvider.java index 2b5b10e10..9c4ae9e77 100644 --- a/src/main/java/io/split/android/client/localhost/LocalhostRuleBasedSegmentsStorageProvider.java +++ b/src/main/java/io/split/android/client/localhost/LocalhostRuleBasedSegmentsStorageProvider.java @@ -12,8 +12,7 @@ class LocalhostRuleBasedSegmentsStorageProvider implements RuleBasedSegmentStora LocalhostRuleBasedSegmentsStorageProvider(@NonNull RuleBasedSegmentStorage ruleBasedSegmentStorage) { mRuleBasedSegmentStorage = ruleBasedSegmentStorage; } - - @NonNull + @Override public RuleBasedSegmentStorage get() { return mRuleBasedSegmentStorage; diff --git a/src/main/java/io/split/android/client/storage/rbs/LazyRuleBasedSegmentStorageProvider.java b/src/main/java/io/split/android/client/storage/rbs/LazyRuleBasedSegmentStorageProvider.java index 26977735d..9a68ebb2b 100644 --- a/src/main/java/io/split/android/client/storage/rbs/LazyRuleBasedSegmentStorageProvider.java +++ b/src/main/java/io/split/android/client/storage/rbs/LazyRuleBasedSegmentStorageProvider.java @@ -1,6 +1,7 @@ package io.split.android.client.storage.rbs; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import java.util.concurrent.atomic.AtomicReference; @@ -16,13 +17,9 @@ public void set(@NonNull RuleBasedSegmentStorage ruleBasedSegmentStorage) { } } - @NonNull + @Nullable @Override public RuleBasedSegmentStorage get() { - RuleBasedSegmentStorage storage = mRuleBasedSegmentStorageRef.get(); - if (storage == null) { - throw new IllegalStateException("RuleBasedSegmentStorage not set in LazyRuleBasedSegmentStorageProvider"); - } - return storage; + return mRuleBasedSegmentStorageRef.get(); } } diff --git a/src/main/java/io/split/android/client/storage/rbs/RuleBasedSegmentStorageProvider.java b/src/main/java/io/split/android/client/storage/rbs/RuleBasedSegmentStorageProvider.java index 2ccb00c69..4c4393a24 100644 --- a/src/main/java/io/split/android/client/storage/rbs/RuleBasedSegmentStorageProvider.java +++ b/src/main/java/io/split/android/client/storage/rbs/RuleBasedSegmentStorageProvider.java @@ -1,9 +1,9 @@ package io.split.android.client.storage.rbs; -import androidx.annotation.NonNull; +import androidx.annotation.Nullable; public interface RuleBasedSegmentStorageProvider { - @NonNull + @Nullable RuleBasedSegmentStorage get(); } diff --git a/src/test/java/io/split/android/client/storage/rbs/LazyRuleBasedSegmentStorageProviderTest.java b/src/test/java/io/split/android/client/storage/rbs/LazyRuleBasedSegmentStorageProviderTest.java new file mode 100644 index 000000000..01b84b6a7 --- /dev/null +++ b/src/test/java/io/split/android/client/storage/rbs/LazyRuleBasedSegmentStorageProviderTest.java @@ -0,0 +1,29 @@ +package io.split.android.client.storage.rbs; + +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.mockito.Mockito.mock; + +import org.junit.Test; + +public class LazyRuleBasedSegmentStorageProviderTest { + + @Test + public void refCanOnlyBeSetOnce() { + LazyRuleBasedSegmentStorageProvider provider = new LazyRuleBasedSegmentStorageProvider(); + RuleBasedSegmentStorage firstInstance = mock(RuleBasedSegmentStorage.class); + RuleBasedSegmentStorage secondInstance = mock(RuleBasedSegmentStorage.class); + provider.set(firstInstance); + provider.set(secondInstance); + + assertSame(firstInstance, provider.get()); + assertNotSame(secondInstance, provider.get()); + } + + @Test + public void getReturnsNullWhenSetHasNotBeenCalled() { + LazyRuleBasedSegmentStorageProvider provider = new LazyRuleBasedSegmentStorageProvider(); + assertNull(provider.get()); + } +} From 7519aca029781caa0104ce42a559f442a732d846 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 27 Feb 2025 14:09:39 -0300 Subject: [PATCH 11/11] Fix impl --- .../client/localhost/LocalhostRuleBasedSegmentsStorage.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/io/split/android/client/localhost/LocalhostRuleBasedSegmentsStorage.java b/src/main/java/io/split/android/client/localhost/LocalhostRuleBasedSegmentsStorage.java index 137b1f6b0..468022148 100644 --- a/src/main/java/io/split/android/client/localhost/LocalhostRuleBasedSegmentsStorage.java +++ b/src/main/java/io/split/android/client/localhost/LocalhostRuleBasedSegmentsStorage.java @@ -7,13 +7,12 @@ import io.split.android.client.dtos.RuleBasedSegment; import io.split.android.client.storage.rbs.RuleBasedSegmentStorage; -import io.split.android.engine.experiments.ParsedRuleBasedSegment; public class LocalhostRuleBasedSegmentsStorage implements RuleBasedSegmentStorage { @Nullable @Override - public ParsedRuleBasedSegment get(String segmentName, String matchingKey) { + public RuleBasedSegment get(String segmentName) { return null; }