├── .gitignore
├── .travis.yml
├── LICENSE.txt
├── README.md
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── reactive_cache
├── build.gradle
└── src
│ ├── main
│ └── java
│ │ └── io
│ │ └── reactivecache2
│ │ ├── ActionsList.java
│ │ ├── ExceptionAdapter.java
│ │ ├── Provider.java
│ │ ├── ProviderGroup.java
│ │ ├── ProviderGroupList.java
│ │ ├── ProviderList.java
│ │ └── ReactiveCache.java
│ └── test
│ └── java
│ └── io
│ └── reactivecache2
│ ├── ActionsListTest.java
│ ├── ExceptionAdapterTest.java
│ ├── Jolyglot$.java
│ ├── Mock.java
│ ├── ProviderGroupListTest.java
│ ├── ProviderGroupTest.java
│ ├── ProviderListTest.java
│ ├── ProviderTest.java
│ ├── ReactiveCacheBuilderValidationTest.java
│ ├── ReactiveCacheTest.java
│ ├── UsageTest.java
│ ├── common
│ └── BaseTestEvictingTask.java
│ ├── encript
│ ├── EncryptGroupTest.java
│ └── EncryptTest.java
│ ├── expiration
│ ├── EvictExpirableRecordsGroupTest.java
│ ├── EvictExpirableRecordsTest.java
│ ├── EvictExpiredRecordsGroupTest.java
│ └── EvictExpiredRecordsTest.java
│ └── migration
│ └── MigrationsTest.java
├── sample
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── io
│ │ └── victoralbertos
│ │ └── reactivecache
│ │ └── ApplicationTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── io
│ │ │ └── victoralbertos
│ │ │ └── reactivecache
│ │ │ └── MainActivity.java
│ └── res
│ │ ├── layout
│ │ ├── activity_main.xml
│ │ └── content_main.xml
│ │ ├── menu
│ │ └── menu_main.xml
│ │ ├── mipmap-hdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-mdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxxhdpi
│ │ └── ic_launcher.png
│ │ ├── values-v21
│ │ └── styles.xml
│ │ ├── values-w820dp
│ │ └── dimens.xml
│ │ └── values
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── io
│ └── victoralbertos
│ └── reactivecache
│ └── ExampleUnitTest.java
├── settings.gradle
└── table_built_in_functions.md
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.ap_
4 |
5 | # Files for the Dalvik VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # Generated files
12 | bin/
13 | gen/
14 |
15 | # Gradle files
16 | .gradle/
17 | **/build/
18 |
19 | # Local configuration file (sdk path, etc)
20 | local.properties
21 |
22 | # Proguard folder generated by Eclipse
23 | proguard/
24 |
25 | # Log Files
26 | *.log
27 |
28 | # Android Studio
29 | .navigation/
30 | .idea/
31 | **/*.iml
32 |
33 | # Android Studio captures folder
34 | captures/
35 |
36 | # .DS_Store files
37 | **/.DS_Store
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: android
2 |
3 | jdk: oraclejdk8
4 |
5 | script: "./gradlew reactive_cache:test"
6 |
7 | android:
8 | components:
9 | - tools
10 | - platform-tools
11 | - build-tools-24.0.0
12 | - android-24
13 | - extra-android-support
14 | - extra-android-m2repository
15 | - extra-google-m2repository
16 |
17 | # Additional components
18 | #- extra-google-google_play_services
19 | #- addon-google_apis-google-19
20 |
21 | # Specify at least one system image, if you need to run emulator(s) during your tests
22 | #- sys-img-armeabi-v7a-android-19
23 | #- sys-img-x86-android-17
24 |
25 | before_install:
26 | - export JAVA7_HOME=/usr/lib/jvm/java-7-oracle
27 | - export JAVA8_HOME=/usr/lib/jvm/java-8-oracle
28 | - export JAVA_HOME=$JAVA7_HOME
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright 2016 Víctor Albertos
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | :warning: This repository is no longer mantained consider using [Room](https://developer.android.com/topic/libraries/architecture/room) as an alternative :warning:
2 |
3 | [](https://travis-ci.org/VictorAlbertos/ReactiveCache)
4 | [](http://android-arsenal.com/details/1/4002)
5 |
6 | # ReactiveCache
7 | The act of caching data with **ReactiveCache** is just another transformation in the reactive chain. ReactiveCache's API exposes both `Single`, `SingleTransformer` and `Completable` **reactive types** to gracefully merge the caching actions with the data stream.
8 |
9 | ## Features
10 | * A **dual cache** based on both memory and disk layers.
11 | * **Automatic deserialization-serialization** for custom `Types`, `List`, `Map` and `Array`.
12 | * **Pagination**
13 | * A **lifetime** system to expire data on specific time lapses.
14 | * Data **encryption**.
15 | * Customizable disk **cache size limit**.
16 | * **Migrations** to evict data by `Type` between releases.
17 | * A complete set of [**built-in functions**](#built-in) to perform **write operations easily** using `List`, such as `addFirst`, `evictLast`, `addAll` and so on.
18 |
19 | ## SetUp
20 | Add to top level *gradle.build* file
21 |
22 | ```gradle
23 | allprojects {
24 | repositories {
25 | maven { url "https://jitpack.io" }
26 | }
27 | }
28 | ```
29 |
30 | Add to app module *gradle.build* file
31 | ```gradle
32 | dependencies {
33 | compile 'com.github.VictorAlbertos:ReactiveCache:1.1.3-2.x'
34 | compile 'com.github.VictorAlbertos.Jolyglot:gson:0.0.4'
35 | compile 'io.reactivex.rxjava2:rxjava:2.0.4'
36 | }
37 | ```
38 |
39 | ## Usage
40 |
41 | ### ReactiveCache
42 | Create a **single instace** of `ReactiveCache` for your entire application. The builder offers some [additional configurations](#config_reactive_cache).
43 |
44 | ```java
45 | ReactiveCache reactiveCache = new ReactiveCache.Builder()
46 | .using(application.getFilesDir(), new GsonSpeaker());
47 | ```
48 |
49 | **`evictAll()`** returns a `Completable` which evicts the cached data for every provider:
50 |
51 | ```java
52 | cacheProvider.evictAll()
53 | ```
54 |
55 | ### Provider
56 |
57 | Call `reactiveCache#provider()` to create a `Provider` to manage cache operations. The builder offers some [additional configurations](#config_providers).
58 |
59 | ```java
60 | Provider> cacheProvider =
61 | reactiveCache.>provider()
62 | .withKey("models");
63 | ```
64 |
65 | **`replace()`** returns a `SingleTransformer` which replaces the `provider` data with the item emitted from the `Single` source. If the source throws an exception, calling `replace()` doesn't evict the `provider` data.
66 |
67 | ```java
68 | api.getModels()
69 | .compose(cacheProvider.replace())
70 | ```
71 |
72 | **`read()`** returns an `Single` which emits the `provider` data. If there isn't any data available, throws an exception.
73 |
74 | ```java
75 | cacheProvider.read()
76 | ```
77 |
78 | **`readWithLoader()`** returns a `SingleTransformer` which emits the `provider` data. If there isn't any data available, it subscribes to the `Single` source to cache and emit its item.
79 |
80 | ```java
81 | api.getModels()
82 | .compose(cacheProvider.readWithLoader())
83 | ```
84 |
85 | **`evict()`** returns a `Completable` which evicts the `provider` data:
86 |
87 | ```java
88 | cacheProvider.evict()
89 | ```
90 |
91 |
92 | ### ProviderGroup
93 |
94 | Call `reactiveCache#providerGroup()` to create a `ProviderGroup` to manage cache operations with pagination support. The builder offers some [additional configurations](#config_providers).
95 |
96 |
97 | ```java
98 | ProviderGroup> cacheProvider =
99 | reactiveCache.>providerGroup()
100 | .withKey("modelsPaginated");
101 | ```
102 |
103 | `ProviderGroup` exposes the same methods as `Provider` but requesting a key as an argument. That way the scope of the `provider` data in every operation is constrained to the data associated with the key.
104 |
105 | ```java
106 | api.getModels(group)
107 | .compose(cacheProvider.replace(group))
108 |
109 | cacheProvider.read(group)
110 |
111 | api.getModels(group)
112 | .compose(cacheProvider.readWithLoader())
113 |
114 | cacheProvider.evict(group)
115 | ```
116 |
117 | `evict()` is an overloaded method to evict the `provider` data for the entire collection of groups.
118 |
119 | ```java
120 | cacheProvider.evict()
121 | ```
122 |
123 | ## Built-in functions for writing operations
124 |
125 | When the data is encoded as type `List`, you may use `ProviderList` and `ProviderGroupList`. Both clases inherit from their base clase (`Provider` and `ProviderGroup` respectively), so -[besides exposing all their base funcionality](#provider)- they offer a supletory api to perform write operations.
126 |
127 | Call `reactiveCache#providerList()` to create a `ProviderList`.
128 |
129 | ```java
130 | ProviderList cacheProvider =
131 | reactiveCache.providerList()
132 | .withKey("models");
133 | ```
134 |
135 | Or call `reactiveCache#providerGroupList()` to create a `ProviderGroupList`.
136 |
137 | ```java
138 | ProviderGroupList cacheProviderGroup =
139 | reactiveCache.providerGroupList()
140 | .withKey("modelsPaginated");
141 | ```
142 |
143 | Both **`cacheProvider.entries()`** and **`cacheProviderGroup.entries(group)`** return an `ActionsList` instance which allows to easily operate with the cached data thought a whole set of functions.
144 |
145 | ```java
146 | ActionsList actions = cacheProvider.entries();
147 | ```
148 |
149 | ```java
150 | ActionsList actions = cacheProviderGroup.entries(group);
151 | ```
152 |
153 | Every function exposed through `actions` return a `Completable` which must be subscribed to in order to consume the action. Follow some examples:
154 |
155 | ```java
156 | actions.addFirst(new Model())
157 |
158 | //Add a new element at 5 position
159 | actions.add((position, count) -> position == 5, new Model())
160 |
161 | //Evict first element if the cache has already 300 records
162 | actions.evictFirst(count -> count > 300)
163 |
164 | //Update the element with id 5
165 | actions.update(model -> model.getId() == 5, model -> {
166 | mock.setActive();
167 | return mock;
168 | })
169 |
170 | //Update all inactive modelds
171 | actions.updateIterable(model -> model.isInactive(), model -> {
172 | model.setActive();
173 | return mock;
174 | })
175 | ```
176 |
177 | [This table](https://github.com/VictorAlbertos/ReactiveCache/blob/2.x/table_built_in_functions.md) summarizes the available functions.
178 |
179 | ## Use cases
180 |
181 | Next examples illustrate how to use **ReactiveCache** on the *data layer* for client **Android** applications. They follow the *well-known* [repository pattern](http://fernandocejas.com/2014/09/03/architecting-android-the-clean-way/) in order to deal with data coming from a remote repository *(server)* and a local one *(ReactiveCache)*.
182 |
183 | ### Simple user session.
184 | ```java
185 | class UserRepository {
186 | private final Provider cacheProvider;
187 | private final ApiUser api;
188 |
189 | UserRepository(ApiUser api, ReactiveCache reactiveCache) {
190 | this.api = api;
191 | this.cacheProvider = reactiveCache.provider()
192 | .withKey("user");
193 | }
194 |
195 | Single login(String email) {
196 | return api.loginUser(email)
197 | .compose(cacheProvider.replace());
198 | }
199 |
200 | Single isLogged() {
201 | return cacheProvider.read()
202 | .map(user -> true)
203 | .onErrorReturn(observer -> false);
204 | }
205 |
206 | Single profile() {
207 | return cacheProvider.read();
208 | }
209 |
210 | Completable updateUserName(String name) {
211 | return cacheProvider.read()
212 | .map(user -> {
213 | user.setName(name);
214 | return user;
215 | })
216 | .compose(cacheProvider.replace())
217 | .toCompletable();
218 | }
219 |
220 | Completable logout() {
221 | return api.logout().andThen(cacheProvider.evict());
222 | }
223 | }
224 | ```
225 |
226 | ### Adding and removing tasks.
227 | ```java
228 | class TasksRepository {
229 | private final ProviderList cacheProvider;
230 | private final ApiTasks api;
231 |
232 | TasksRepository(ApiTasks api, ReactiveCache reactiveCache) {
233 | this.api = api;
234 | this.cacheProvider = reactiveCache.providerList()
235 | .withKey("tasks");
236 | }
237 |
238 | Single>> tasks(boolean refresh) {
239 | return refresh ? api.tasks().compose(cacheProvider.replaceAsReply())
240 | : api.tasks().compose(cacheProvider.readWithLoaderAsReply());
241 | }
242 |
243 | Completable addTask(String name, String desc) {
244 | return api.addTask(1, name, desc)
245 | .andThen(cacheProvider.entries()
246 | .addFirst(new Task(1, name, desc)));
247 | }
248 |
249 | Completable removeTask(int id) {
250 | return api.removeTask(id)
251 | .andThen(cacheProvider.entries()
252 | .evict((position, count, element) -> element.getId() == id));
253 | }
254 | }
255 | ```
256 |
257 | ### Paginated feed of events.
258 | ```java
259 | class EventsRepository {
260 | private final ProviderGroup> cacheProvider;
261 | private final ApiEvents apiEvents;
262 |
263 | EventsRepository(ApiEvents apiEvents, ReactiveCache reactiveCache) {
264 | this.apiEvents = apiEvents;
265 | this.cacheProvider = reactiveCache.>providerGroup()
266 | .withKey("events");
267 | }
268 |
269 | Single>> events(boolean refresh, int page) {
270 | if (refresh) {
271 | return apiEvents.events(page)
272 | .compose(cacheProvider.replaceAsReply(page));
273 | }
274 |
275 | return apiEvents.events(page)
276 | .compose(cacheProvider.readWithLoaderAsReply(page));
277 | }
278 | }
279 | ```
280 |
281 | ## Configuration
282 |
283 |
284 | ### ReactiveCache
285 |
286 | When building `ReactiveCache` the next global configurations are available thought the builder:
287 |
288 | * **`diskCacheSize(int)`** sets the max memory in megabytes for all the cached data on disk. *Default value is 100*.
289 |
290 | * **`encrypt(String)`** sets the key to be used for encrypting the data on those providers as such configured.
291 |
292 | * **`useExpiredDataWhenNoLoaderAvailable()`** if invoked, ReactiveCache dispatches records already expired instead of throwing.
293 |
294 | * **`migrations(List)`** every `MigrationCache` expects a version number and a `Class[]` to check what cached data matches with these classes to evict it from disk. Use `MigrationCache` for those `Type` which have added new fields between app releases.
295 |
296 | ```java
297 | ReactiveCache reactiveCache = new ReactiveCache.Builder()
298 | .diskCacheSize(100)
299 | .encrypt("myStrongKey1234")
300 | .useExpiredDataWhenNoLoaderAvailable()
301 | .migrations(Arrays.asList(
302 | new MigrationCache(1, new Class[] {Model.class}),
303 | new MigrationCache(1, new Class[] {Model1.class})))
304 | .using(application.getFilesDir(), new GsonSpeaker());
305 | ```
306 |
307 | ### Config provider
308 |
309 | When building `Provider`, `ProviderList`, `ProviderGroup` or `ProviderGroupList` the next configuration is available thought the builder:
310 |
311 | * **`encrypt(boolean)`** when true, the data cached by this `provider` is encrypted using the key specified in `ReactiveCache#encript(key)`. *Default value is false*.
312 |
313 | * **`expirable(boolean)`** when false, the data cached by this `provider` is not eligible to be expired if not enough space remains on disk. *Default value is true*.
314 |
315 | * **`lifeCache(long, TimeUnit)`** sets the amount of time before the data would be expired. *By default the data has no life time*.
316 |
317 | ```java
318 | Provider cacheModel = reactiveCache.provider()
319 | .encrypt(true)
320 | .expirable(false)
321 | .lifeCache(60, TimeUnit.MINUTES)
322 | .withKey("model");
323 | ```
324 |
325 | ## Author
326 |
327 | **Víctor Albertos**
328 |
329 | *
330 | *
331 | *
332 |
333 |
334 | ## Another author's libraries using RxJava:
335 | * [Mockery](https://github.com/VictorAlbertos/Mockery): Android and Java library for mocking and testing networking layers with built-in support for Retrofit.
336 | * [RxCache](https://github.com/VictorAlbertos/RxCache): Reactive caching library for Android and Java. (ReactiveCache uses internally the core from RxCache).
337 | * [RxActivityResult](https://github.com/VictorAlbertos/RxActivityResult): A reactive-tiny-badass-vindictive library to break with the OnActivityResult implementation as it breaks the observables chain.
338 | * [RxFcm](https://github.com/VictorAlbertos/RxFcm): RxJava extension for Android Firebase Cloud Messaging (aka fcm).
339 | * [RxSocialConnect](https://github.com/VictorAlbertos/RxSocialConnect-Android): OAuth RxJava extension for Android.
340 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:2.3.0'
9 |
10 | // NOTE: Do not place your application dependencies here; they belong
11 | // in the individual module build.gradle files
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | maven { url "https://jitpack.io" }
18 | mavenLocal()
19 | jcenter()
20 | }
21 | }
22 |
23 | task clean(type: Delete) {
24 | delete rootProject.buildDir
25 | }
26 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/VictorAlbertos/ReactiveCache/0053adb4dd3526a437aed048f7ccaf8ae6a29b61/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Mar 08 17:44:02 CET 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/reactive_cache/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'java'
2 | apply plugin: 'me.tatarka.retrolambda'
3 | apply plugin: 'maven'
4 | group='com.github.VictorAlbertos'
5 | version='1.1.1-2.x'
6 |
7 | sourceCompatibility = 1.8
8 | targetCompatibility = 1.8
9 |
10 | buildscript {
11 | repositories {
12 | mavenCentral()
13 | jcenter()
14 | }
15 |
16 | dependencies {
17 | classpath "me.tatarka:gradle-retrolambda:3.5.0"
18 | }
19 | }
20 |
21 | task sourcesJar(type: Jar, dependsOn: classes) {
22 | classifier = 'sources'
23 | from sourceSets.main.allSource
24 | }
25 |
26 | task javadocJar(type: Jar, dependsOn: javadoc) {
27 | classifier = 'javadoc'
28 | from javadoc.destinationDir
29 | }
30 |
31 | artifacts {
32 | archives sourcesJar
33 | archives javadocJar
34 | }
35 |
36 | dependencies {
37 | compile 'com.github.VictorAlbertos.RxCache:core:1.8.3-2.x'
38 | compile 'com.github.VictorAlbertos.Jolyglot:api:0.0.4'
39 |
40 | retrolambdaConfig 'net.orfjackal.retrolambda:retrolambda:2.5.0'
41 |
42 | testCompile 'com.github.VictorAlbertos.Jolyglot:gson:0.0.4'
43 | testCompile 'com.github.VictorAlbertos.Jolyglot:jackson:0.0.4'
44 | testCompile 'com.github.VictorAlbertos.Jolyglot:moshi:0.0.4'
45 | testCompile 'junit:junit:4.12'
46 | }
47 |
48 | retrolambda {
49 | jdk System.getenv("JAVA8_HOME")
50 | oldJdk System.getenv("JAVA7_HOME")
51 | }
52 |
--------------------------------------------------------------------------------
/reactive_cache/src/main/java/io/reactivecache2/ActionsList.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 Victor Albertos
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.reactivecache2;
18 |
19 | import io.reactivex.Completable;
20 | import io.reactivex.Single;
21 | import java.util.Arrays;
22 | import java.util.Collections;
23 | import java.util.List;
24 |
25 | /**
26 | * Provides a set of entries in order to perform write operations on lists with providers in a more
27 | * easy and safely way.
28 | */
29 | public class ActionsList {
30 | protected Single> cache;
31 | protected final Evict evict;
32 |
33 | ActionsList(Evict evict, Single> cache) {
34 | this.evict = evict;
35 | this.cache = cache;
36 | }
37 |
38 | /**
39 | * Static accessor to create an instance of Actions in order follow the "builder" pattern.
40 | *
41 | * @param evict The implementation of Evict interface which allow to persists the changes.
42 | * @param cache An single which is the result of calling the provider without evicting its data.
43 | * @param the type of the element of the list to be processed.
44 | * @return an instance of Actions.
45 | */
46 | static ActionsList with(Evict evict, Single> cache) {
47 | return new ActionsList<>(evict, cache);
48 | }
49 |
50 | /**
51 | * Func2 will be called for every iteration until its condition returns true. When true, the
52 | * element is added to the cache at the position of the current iteration.
53 | *
54 | * @param func2 exposes the position of the current iteration and the count of elements in the
55 | * cache.
56 | * @param element the object to addOrUpdate to the cache.
57 | * @return Completable
58 | */
59 | public Completable add(Func2 func2, T element) {
60 | return addAll(func2, Arrays.asList(element));
61 | }
62 |
63 | /**
64 | * Add the object at the first position of the cache.
65 | *
66 | * @param element the object to addOrUpdate to the cache.
67 | * @return Completable
68 | */
69 | public Completable addFirst(T element) {
70 | return addAll((position, count) -> position == 0, Arrays.asList(element));
71 | }
72 |
73 | /**
74 | * Add the object at the last position of the cache.
75 | *
76 | * @param element the object to addOrUpdate to the cache.
77 | * @return Completable
78 | */
79 | public Completable addLast(T element) {
80 | return addAll((position, count) -> position == count, Arrays.asList(element));
81 | }
82 |
83 | /**
84 | * Add the objects at the first position of the cache.
85 | *
86 | * @param elements the objects to addOrUpdate to the cache.
87 | * @return Completable
88 | */
89 | public Completable addAllFirst(List elements) {
90 | return addAll((position, count) -> position == 0, elements);
91 | }
92 |
93 | /**
94 | * Add the objects at the last position of the cache.
95 | *
96 | * @param elements the objects to addOrUpdate to the cache.
97 | * @return Completable
98 | */
99 | public Completable addAllLast(List elements) {
100 | return addAll((position, count) -> position == count, elements);
101 | }
102 |
103 | /**
104 | * Func2 will be called for every iteration until its condition returns true. When true, the
105 | * elements are added to the cache at the position of the current iteration.
106 | *
107 | * @param func2 exposes the position of the current iteration and the count of elements in the
108 | * cache.
109 | * @param elements the objects to addOrUpdate to the cache.
110 | * @return Completable
111 | */
112 | public Completable addAll(final Func2 func2, final List elements) {
113 | return evict.call(cache.map(items -> {
114 | int count = items.size();
115 |
116 | for (int position = 0; position <= count; position++) {
117 | if (func2.call(position, count)) {
118 | items.addAll(position, elements);
119 | break;
120 | }
121 | }
122 |
123 | return items;
124 | })).toCompletable();
125 | }
126 |
127 | /**
128 | * Evict object at the first position of the cache
129 | *
130 | * @return Completable
131 | */
132 | public Completable evictFirst() {
133 | return evict((position, count, element) -> position == 0);
134 | }
135 |
136 | /**
137 | * Evict as much objects as requested by n param starting from the first position.
138 | *
139 | * @param n the amount of elements to evict.
140 | * @return Completable
141 | */
142 | public Completable evictFirstN(final int n) {
143 | return evictFirstN(count -> true, n);
144 | }
145 |
146 | /**
147 | * Evict object at the last position of the cache.
148 | *
149 | * @return Completable
150 | */
151 | public Completable evictLast() {
152 | return evict((position, count, element) -> position == count - 1);
153 | }
154 |
155 | /**
156 | * Evict as much objects as requested by n param starting from the last position.
157 | *
158 | * @param n the amount of elements to evict.
159 | * @return Completable
160 | */
161 | public Completable evictLastN(final int n) {
162 | return evictLastN(count -> true, n);
163 | }
164 |
165 | /**
166 | * Evict object at the first position of the cache.
167 | *
168 | * @param func1Count exposes the count of elements in the cache.
169 | * @return Completable
170 | */
171 | public Completable evictFirst(final Func1Count func1Count) {
172 | return evict((position, count, element) -> position == 0 && func1Count.call(count));
173 | }
174 |
175 | /**
176 | * Evict as much objects as requested by n param starting from the first position.
177 | *
178 | * @param func1Count exposes the count of elements in the cache.
179 | * @param n the amount of elements to evict.
180 | * @return Completable
181 | */
182 | public Completable evictFirstN(final Func1Count func1Count, final int n) {
183 | return evictIterable((position, count, element) -> position < n && func1Count.call(count));
184 | }
185 |
186 | /**
187 | * Evict object at the last position of the cache.
188 | *
189 | * @param func1Count exposes the count of elements in the cache.
190 | * @return Completable
191 | */
192 | public Completable evictLast(final Func1Count func1Count) {
193 | return evict((position, count, element) -> position == count - 1 && func1Count.call(count));
194 | }
195 |
196 | private boolean startToEvict;
197 |
198 | /**
199 | * Evict as much objects as requested by n param starting from the last position.
200 | *
201 | * @param func1Count exposes the count of elements in the cache.
202 | * @param n the amount of elements to evict.
203 | * @return Completable
204 | */
205 | public Completable evictLastN(final Func1Count func1Count, final int n) {
206 | startToEvict = false;
207 | return evictIterable((position, count, element) -> {
208 | if (!startToEvict) startToEvict = count - position == n;
209 |
210 | if (startToEvict) {
211 | return count - position <= n && func1Count.call(count);
212 | } else {
213 | return false;
214 | }
215 | });
216 | }
217 |
218 | /**
219 | * Func1Element will be called for every iteration until its condition returns true. When true,
220 | * the element of the current iteration is evicted from the cache.
221 | *
222 | * @param func1 exposes the element of the current iteration.
223 | * @return Completable
224 | */
225 | public Completable evict(final Func1 func1) {
226 | return evict((position, count, element) -> func1.call(element));
227 | }
228 |
229 | /**
230 | * Func3 will be called for every iteration until its condition returns true. When true, the
231 | * element of the current iteration is evicted from the cache.
232 | *
233 | * @param func3 exposes the position of the current iteration, the count of elements in the cache
234 | * and the element of the current iteration.
235 | * @return Completable
236 | */
237 | public Completable evict(final Func3 func3) {
238 | return evict.call(cache.map(elements -> {
239 | int count = elements.size();
240 |
241 | for (int position = 0; position < count; position++) {
242 | if (func3.call(position, count, elements.get(position))) {
243 | elements.remove(position);
244 | break;
245 | }
246 | }
247 |
248 | return elements;
249 | })).toCompletable();
250 | }
251 |
252 | /**
253 | * Evict elements from the cache starting from the first position until its count is equal to the
254 | * value specified in n param.
255 | *
256 | * @param n the amount of elements to keep from evict.
257 | * @return Completable
258 | */
259 | public Completable evictAllKeepingFirstN(final int n) {
260 | return evictIterable((position, count, element) -> {
261 | int positionToStartEvicting = count - (count - n);
262 | return position >= positionToStartEvicting;
263 | });
264 | }
265 |
266 | /**
267 | * Evict elements from the cache starting from the last position until its count is equal to the
268 | * value specified in n param.
269 | *
270 | * @param n the amount of elements to keep from evict.
271 | * @return Completable
272 | */
273 | public Completable evictAllKeepingLastN(final int n) {
274 | return evictIterable((position, count, element) -> {
275 | int elementsToEvict = count - n;
276 | return position < elementsToEvict;
277 | });
278 | }
279 |
280 | /**
281 | * Func3 will be called for every iteration. When true, the element of the current iteration is
282 | * evicted from the cache.
283 | *
284 | * @param func3 exposes the position of the current iteration, the count of elements in the cache
285 | * and the element of the current iteration.
286 | * @return Completable
287 | */
288 | public Completable evictIterable(final Func3 func3) {
289 | return evict.call(cache.map(elements -> {
290 | int count = elements.size();
291 |
292 | for (int position = 0; position < count; position++) {
293 | if (func3.call(position, count, elements.get(position))) {
294 | elements.set(position, null);
295 | }
296 | }
297 |
298 | elements.removeAll(Collections.singleton(null));
299 | return elements;
300 | })).toCompletable();
301 | }
302 |
303 | /**
304 | * Func1Element will be called for every iteration until its condition returns true. When true,
305 | * the element of the current iteration is updated.
306 | *
307 | * @param func1 exposes the element of the current iteration.
308 | * @param replace exposes the original element and expects back the one modified.
309 | * @return Completable
310 | */
311 | public Completable update(final Func1 func1, Replace replace) {
312 | return update((position, count, element) -> func1.call(element), replace);
313 | }
314 |
315 | /**
316 | * Func3 will be called for every iteration until its condition returns true. When true, the
317 | * element of the current iteration is updated.
318 | *
319 | * @param func3 exposes the position of the current iteration, the count of elements in the cache
320 | * and the element of the current iteration.
321 | * @param replace exposes the original element and expects back the one modified.
322 | * @return Completable
323 | */
324 | public Completable update(final Func3 func3, final Replace replace) {
325 | return evict.call(cache.map(elements -> {
326 | int count = elements.size();
327 |
328 | for (int position = 0; position < count; position++) {
329 | if (func3.call(position, count, elements.get(position))) {
330 | elements.set(position, replace.call(elements.get(position)));
331 | break;
332 | }
333 | }
334 |
335 | return elements;
336 | })).toCompletable();
337 | }
338 |
339 | /**
340 | * Func1Element will be called for every. When true, the element of the current iteration is
341 | * updated.
342 | *
343 | * @param func1 exposes the element of the current iteration.
344 | * @param replace exposes the original element and expects back the one modified.
345 | * @return Completable
346 | */
347 | public Completable updateIterable(final Func1 func1, Replace replace) {
348 | return updateIterable((position, count, element) -> func1.call(element), replace);
349 | }
350 |
351 | /**
352 | * Func3 will be called for every iteration. When true, the element of the current iteration is
353 | * updated.
354 | *
355 | * @param func3 exposes the position of the current iteration, the count of elements in the cache
356 | * and the element of the current iteration.
357 | * @param replace exposes the original element and expects back the one modified.
358 | * @return Completable
359 | */
360 | public Completable updateIterable(final Func3 func3, final Replace replace) {
361 | return evict.call(cache.map(elements -> {
362 | int count = elements.size();
363 |
364 | for (int position = 0; position < count; position++) {
365 | if (func3.call(position, count, elements.get(position))) {
366 | elements.set(position, replace.call(elements.get(position)));
367 | }
368 | }
369 |
370 | return elements;
371 | })).toCompletable();
372 | }
373 |
374 | public interface Evict {
375 | Single> call(final Single> elements);
376 | }
377 |
378 | public interface Func1Count {
379 | boolean call(final int count);
380 | }
381 |
382 | public interface Func1 {
383 | boolean call(final T element);
384 | }
385 |
386 | public interface Func2 {
387 | boolean call(final int position, final int count);
388 | }
389 |
390 | public interface Func3 {
391 | boolean call(final int position, final int count, final T element);
392 | }
393 |
394 | public interface Replace {
395 | T call(T element);
396 | }
397 | }
398 |
--------------------------------------------------------------------------------
/reactive_cache/src/main/java/io/reactivecache2/ExceptionAdapter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Victor Albertos
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.reactivecache2;
18 |
19 | import io.reactivex.Completable;
20 | import io.reactivex.Observable;
21 | import io.reactivex.Single;
22 | import io.reactivex.exceptions.CompositeException;
23 | import io.rx_cache2.RxCacheException;
24 | import java.util.ArrayList;
25 | import java.util.List;
26 |
27 | final class ExceptionAdapter {
28 |
29 | Completable completeOnRxCacheLoaderError(Throwable error) {
30 | if (error instanceof CompositeException) {
31 | for (Throwable e: ((CompositeException) error).getExceptions()) {
32 | if (e instanceof RxCacheException) return Completable.complete();
33 | }
34 | }
35 |
36 | if (error instanceof RxCacheException) return Completable.complete();
37 |
38 | return Completable.error(error);
39 | }
40 |
41 | Observable placeholderLoader() {
42 | return Observable.error(new PlaceHolderLoader());
43 | }
44 |
45 | Single stripPlaceholderLoaderException(Throwable error) {
46 | if (!(error instanceof CompositeException)) return Single.error(error);
47 |
48 | return Single.fromObservable(Observable.just(((CompositeException)error).getExceptions())
49 | .flatMapIterable(errors -> errors)
50 | .filter(e -> !(e instanceof PlaceHolderLoader))
51 | .toList().toObservable()
52 | .flatMap(curatedErrors -> {
53 | if (curatedErrors.size() == 1) return Observable.error(curatedErrors.get(0));
54 | else return Observable.error(new CompositeException(curatedErrors));
55 | }));
56 | }
57 |
58 |
59 | Single> emptyListIfRxCacheException(Throwable error) {
60 | if (error instanceof RxCacheException) return Single.just(new ArrayList());
61 | else return Single.error(error);
62 | }
63 |
64 | static class PlaceHolderLoader extends Exception {
65 |
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/reactive_cache/src/main/java/io/reactivecache2/Provider.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Victor Albertos
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.reactivecache2;
18 |
19 | import io.reactivex.Completable;
20 | import io.reactivex.Observable;
21 | import io.reactivex.Single;
22 | import io.reactivex.SingleTransformer;
23 | import io.rx_cache2.ConfigProvider;
24 | import io.rx_cache2.EvictDynamicKey;
25 | import io.rx_cache2.Reply;
26 | import io.rx_cache2.internal.ProcessorProviders;
27 | import java.util.concurrent.TimeUnit;
28 |
29 | /**
30 | * Entry point to manage cache CRUD operations.
31 | *
32 | * @param The type of the data to persist.
33 | */
34 | public class Provider {
35 | private final ProviderBuilder builder;
36 | protected final ExceptionAdapter exceptionAdapter;
37 |
38 | Provider(ProviderBuilder builder) {
39 | this.builder = builder;
40 | this.exceptionAdapter = new ExceptionAdapter();
41 | }
42 |
43 | /**
44 | * Evict all the cached data for this provider.
45 | */
46 | public final Completable evict() {
47 | return Completable.defer(() ->
48 | Completable.fromObservable(builder.processorProviders
49 | .process(getConfigProvider(Observable.error(new RuntimeException()),
50 | new EvictDynamicKey(true), false, false)))
51 | .onErrorResumeNext(exceptionAdapter::completeOnRxCacheLoaderError)
52 | );
53 | }
54 |
55 | /**
56 | * Replace the cached data by the element emitted from the loader.
57 | */
58 | public final SingleTransformer replace() {
59 | return loader ->
60 | loader.flatMap(data -> Single.fromObservable(builder.processorProviders
61 | .process(getConfigProvider(Observable.just(data),
62 | new EvictDynamicKey(true), false, null))));
63 | }
64 |
65 | /**
66 | * Read from cache and throw if no data is available.
67 | */
68 | public final Single read() {
69 | return Single.defer(() ->
70 | Single.fromObservable(builder.processorProviders
71 | .process(getConfigProvider(exceptionAdapter.placeholderLoader(),
72 | new EvictDynamicKey(false), false, null))))
73 | .onErrorResumeNext(exceptionAdapter::stripPlaceholderLoaderException);
74 | }
75 |
76 | /**
77 | * Read from cache but if there is not data available then read from the loader and cache its
78 | * element.
79 | */
80 | public final SingleTransformer readWithLoader() {
81 | return loader ->
82 | Single.fromObservable(builder.processorProviders
83 | .process(getConfigProvider(loader.toObservable(),
84 | new EvictDynamicKey(false), false, null)));
85 | }
86 |
87 | /**
88 | * Same as {@link Provider#replace()} but wrap the data in a Reply object for debug purposes.
89 | */
90 | public final SingleTransformer> replaceAsReply() {
91 | return loader ->
92 | loader.flatMap(data -> Single.fromObservable(builder.processorProviders
93 | .process(getConfigProvider(Observable.just(data), new EvictDynamicKey(true), true,
94 | null))));
95 | }
96 |
97 | /**
98 | * Same as {@link Provider#readWithLoader()} but wrap the data in a Reply object for debug
99 | * purposes.
100 | */
101 | public final SingleTransformer> readWithLoaderAsReply() {
102 | return loader -> Single.fromObservable(builder.processorProviders
103 | .process(getConfigProvider(loader.toObservable(), new EvictDynamicKey(false), true, null)));
104 | }
105 |
106 | private ConfigProvider getConfigProvider(Observable loader,
107 | EvictDynamicKey evict, boolean detailResponse, Boolean useExpiredDataIfNotLoaderAvailable) {
108 | Long lifeTime = builder.timeUnit != null ?
109 | builder.timeUnit.toMillis(builder.duration) : null;
110 |
111 | return new ConfigProvider(builder.key, useExpiredDataIfNotLoaderAvailable, lifeTime,
112 | detailResponse,
113 | builder.expirable, builder.encrypted, builder.key,
114 | "", loader, evict);
115 | }
116 |
117 | public static class ProviderBuilder {
118 | protected String key;
119 | private boolean encrypted, expirable;
120 | private Long duration;
121 | private TimeUnit timeUnit;
122 | private final ProcessorProviders processorProviders;
123 |
124 | ProviderBuilder(ProcessorProviders processorProviders) {
125 | this.encrypted = false;
126 | this.expirable = true;
127 | this.processorProviders = processorProviders;
128 | }
129 |
130 | /**
131 | * If called, this provider encrypts its data as long as ReactiveCache has been configured with
132 | * an encryption key.
133 | */
134 | public ProviderBuilder encrypt(boolean encrypt) {
135 | this.encrypted = encrypt;
136 | return this;
137 | }
138 |
139 | /**
140 | * Make the data associated with this provider eligible to be expired if not enough space
141 | * remains on disk. By default is true.
142 | */
143 | public ProviderBuilder expirable(boolean expirable) {
144 | this.expirable = expirable;
145 | return this;
146 | }
147 |
148 | /**
149 | * Set the amount of time before the data would be evicted. If life cache is not configured, the
150 | * data will be never evicted unless it is required explicitly using {@link Provider#evict()} or
151 | * {@link Provider#replace()}
152 | */
153 | public ProviderBuilder lifeCache(long duration, TimeUnit timeUnit) {
154 | this.duration = duration;
155 | this.timeUnit = timeUnit;
156 | return this;
157 | }
158 |
159 | /**
160 | * Set the key for the provider.
161 | */
162 | public > R withKey(Object key) {
163 | this.key = key.toString();
164 | return (R) new Provider<>(this);
165 | }
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/reactive_cache/src/main/java/io/reactivecache2/ProviderGroup.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Victor Albertos
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.reactivecache2;
18 |
19 | import io.reactivex.Completable;
20 | import io.reactivex.Observable;
21 | import io.reactivex.Single;
22 | import io.reactivex.SingleTransformer;
23 | import io.rx_cache2.ConfigProvider;
24 | import io.rx_cache2.EvictDynamicKey;
25 | import io.rx_cache2.EvictDynamicKeyGroup;
26 | import io.rx_cache2.Reply;
27 | import io.rx_cache2.internal.ProcessorProviders;
28 | import java.util.concurrent.TimeUnit;
29 |
30 | /**
31 | * Entry point to manage cache CRUD operations with groups.
32 | *
33 | * @param The type of the data to persist.
34 | */
35 | public class ProviderGroup {
36 | private final ProviderBuilder builder;
37 | protected final ExceptionAdapter exceptionAdapter;
38 |
39 | ProviderGroup(ProviderBuilder builder) {
40 | this.builder = builder;
41 | this.exceptionAdapter = new ExceptionAdapter();
42 | }
43 |
44 | /**
45 | * Evict all the cached data for this provider.
46 | */
47 | public final Completable evict() {
48 | return Completable.defer(() ->
49 | Completable.fromObservable(builder.processorProviders
50 | .process(getConfigProvider(Observable.error(new RuntimeException()), "",
51 | new EvictDynamicKey(true), false, false)))
52 | .onErrorResumeNext(exceptionAdapter::completeOnRxCacheLoaderError)
53 | );
54 | }
55 |
56 | /**
57 | * Evict the cached data by group.
58 | */
59 | public final Completable evict(final Object group) {
60 | return Completable.defer(() ->
61 | Completable.fromObservable(builder.processorProviders
62 | .process(getConfigProvider(Observable.error(new RuntimeException()), group.toString(),
63 | new EvictDynamicKeyGroup(true), false, false)))
64 | .onErrorResumeNext(exceptionAdapter::completeOnRxCacheLoaderError)
65 | );
66 | }
67 |
68 | /**
69 | * Replace the cached data by group based on the element emitted from the loader.
70 | */
71 | public final SingleTransformer replace(final Object group) {
72 | return loader ->
73 | loader.flatMap(data -> Single.fromObservable(builder.processorProviders
74 | .process(getConfigProvider(Observable.just(data), group.toString(),
75 | new EvictDynamicKeyGroup(true), false, null))));
76 | }
77 |
78 | /**
79 | * Read from cache by group and throw if no data is available.
80 | */
81 | public final Single read(final Object group) {
82 | return Single.defer(() ->
83 | Single.fromObservable(builder.processorProviders
84 | .process(getConfigProvider(exceptionAdapter.placeholderLoader(), group.toString(),
85 | new EvictDynamicKeyGroup(false), false, null))))
86 | .onErrorResumeNext(exceptionAdapter::stripPlaceholderLoaderException);
87 | }
88 |
89 | /**
90 | * Read from cache by group but if there is not data available then read from the loader and cache
91 | * its element.
92 | */
93 | public final SingleTransformer readWithLoader(final Object group) {
94 | return loader -> Single.fromObservable(builder.processorProviders
95 | .process(getConfigProvider(loader.toObservable(), group.toString(),
96 | new EvictDynamicKeyGroup(false), false, null)));
97 | }
98 |
99 | /**
100 | * Same as {@link ProviderGroup#replace(Object)} but wrap the data in a Reply object for debug
101 | * purposes.
102 | */
103 | public final SingleTransformer> replaceAsReply(final Object group) {
104 | return loader ->
105 | loader.flatMap(data -> Single.fromObservable(builder.processorProviders
106 | .process(getConfigProvider(Observable.just(data), group.toString(),
107 | new EvictDynamicKeyGroup(true), true, null))));
108 | }
109 |
110 | /**
111 | * Same as {@link ProviderGroup#readWithLoader(Object)} but wrap the data in a Reply object for
112 | * debug purposes.
113 | */
114 | public final SingleTransformer> readWithLoaderAsReply(final Object group) {
115 | return loader -> Single.fromObservable(builder.processorProviders
116 | .process(getConfigProvider(loader.toObservable(), group.toString(), new EvictDynamicKeyGroup(false),
117 | true, null)));
118 | }
119 |
120 | private ConfigProvider getConfigProvider(Observable loader, String group,
121 | EvictDynamicKey evict, boolean detailResponse, Boolean useExpiredDataIfNotLoaderAvailable) {
122 | Long lifeTime = builder.timeUnit != null ?
123 | builder.timeUnit.toMillis(builder.duration) : null;
124 |
125 | return new ConfigProvider(builder.key, useExpiredDataIfNotLoaderAvailable, lifeTime,
126 | detailResponse,
127 | builder.expirable, builder.encrypted, builder.key,
128 | group, loader, evict);
129 | }
130 |
131 | public static class ProviderBuilder {
132 | protected String key;
133 | private boolean encrypted, expirable;
134 | private Long duration;
135 | private TimeUnit timeUnit;
136 | private final ProcessorProviders processorProviders;
137 |
138 | ProviderBuilder(ProcessorProviders processorProviders) {
139 | this.encrypted = false;
140 | this.expirable = true;
141 | this.processorProviders = processorProviders;
142 | }
143 |
144 | /**
145 | * Same as {@link Provider.ProviderBuilder#encrypt(boolean)}
146 | */
147 | public ProviderBuilder encrypt(boolean encrypt) {
148 | this.encrypted = encrypt;
149 | return this;
150 | }
151 |
152 | /**
153 | * Same as {@link Provider.ProviderBuilder#expirable(boolean)}
154 | */
155 | public ProviderBuilder expirable(boolean expirable) {
156 | this.expirable = expirable;
157 | return this;
158 | }
159 |
160 | /**
161 | * Same as {@link Provider.ProviderBuilder#lifeCache(long, TimeUnit)}
162 | */
163 | public ProviderBuilder lifeCache(long duration, TimeUnit timeUnit) {
164 | this.duration = duration;
165 | this.timeUnit = timeUnit;
166 | return this;
167 | }
168 |
169 | /**
170 | * Same as {@link Provider.ProviderBuilder#withKey(Object)}
171 | */
172 | public > R withKey(Object key) {
173 | this.key = key.toString();
174 | return (R) new ProviderGroup<>(this);
175 | }
176 | }
177 | }
--------------------------------------------------------------------------------
/reactive_cache/src/main/java/io/reactivecache2/ProviderGroupList.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 Victor Albertos
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.reactivecache2;
18 |
19 | import io.rx_cache2.internal.ProcessorProviders;
20 | import java.util.List;
21 |
22 | public final class ProviderGroupList extends ProviderGroup> {
23 | ProviderGroupList(ProviderBuilder> builder) {
24 | super(builder);
25 | }
26 |
27 | public ActionsList entries(Object group) {
28 | return ActionsList.with(elements -> elements.compose(replace(group)),
29 | read(group).onErrorResumeNext(exceptionAdapter::emptyListIfRxCacheException));
30 | }
31 |
32 | public static class ProviderBuilderList extends ProviderBuilder> {
33 | ProviderBuilderList(ProcessorProviders processorProviders) {
34 | super(processorProviders);
35 | }
36 |
37 | @Override public >> R withKey(Object key) {
38 | this.key = key.toString();
39 | return (R) new ProviderGroupList<>(this);
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/reactive_cache/src/main/java/io/reactivecache2/ProviderList.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 Victor Albertos
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.reactivecache2;
18 |
19 | import io.rx_cache2.internal.ProcessorProviders;
20 | import java.util.List;
21 |
22 | public final class ProviderList extends Provider> {
23 | ProviderList(ProviderBuilder> builder) {
24 | super(builder);
25 | }
26 |
27 | public ActionsList entries() {
28 | return ActionsList.with(elements -> elements.compose(replace()),
29 | read().onErrorResumeNext(exceptionAdapter::emptyListIfRxCacheException));
30 | }
31 |
32 | public static class ProviderBuilderList extends ProviderBuilder> {
33 | ProviderBuilderList(ProcessorProviders processorProviders) {
34 | super(processorProviders);
35 | }
36 |
37 | @Override public >> R withKey(Object key) {
38 | this.key = key.toString();
39 | return (R) new ProviderList<>(this);
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/reactive_cache/src/main/java/io/reactivecache2/ReactiveCache.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Victor Albertos
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.reactivecache2;
18 |
19 | import io.reactivex.Completable;
20 | import io.rx_cache2.MigrationCache;
21 | import io.rx_cache2.internal.DaggerRxCacheComponent;
22 | import io.rx_cache2.internal.Locale;
23 | import io.rx_cache2.internal.ProcessorProviders;
24 | import io.rx_cache2.internal.RxCacheModule;
25 | import io.victoralbertos.jolyglot.JolyglotGenerics;
26 | import java.io.File;
27 | import java.security.InvalidParameterException;
28 | import java.util.List;
29 |
30 | public final class ReactiveCache {
31 | private final ProcessorProviders processorProviders;
32 |
33 | private ReactiveCache(Builder builder) {
34 | this.processorProviders = DaggerRxCacheComponent.builder()
35 | .rxCacheModule(new RxCacheModule(builder.cacheDirectory,
36 | builder.useExpiredDataIfLoaderNotAvailable, builder.diskCacheSize,
37 | builder.encryptKey, builder.migrationsCache,
38 | builder.jolyglot))
39 | .build().providers();
40 | }
41 |
42 | /**
43 | * Return a {@link Provider.ProviderBuilder} to build the provider.
44 | *
45 | * @param the type of data to be cached.
46 | */
47 | public Provider.ProviderBuilder provider() {
48 | return new Provider.ProviderBuilder<>(processorProviders);
49 | }
50 |
51 | /**
52 | * Return a {@link ProviderList.ProviderBuilderList} to build the provider.
53 | *
54 | * @param the type of data to be cached.
55 | */
56 | public ProviderList.ProviderBuilderList providerList() {
57 | return new ProviderList.ProviderBuilderList<>(processorProviders);
58 | }
59 |
60 | /**
61 | * Return a {@link ProviderGroup.ProviderBuilder} to build the provider group.
62 | *
63 | * @param the type of data to be cached.
64 | */
65 | public ProviderGroup.ProviderBuilder providerGroup() {
66 | return new ProviderGroup.ProviderBuilder<>(processorProviders);
67 | }
68 |
69 | /**
70 | * Return a {@link ProviderGroupList.ProviderBuilder} to build the provider.
71 | *
72 | * @param the type of data to be cached.
73 | */
74 | public ProviderGroupList.ProviderBuilderList providerGroupList() {
75 | return new ProviderGroupList.ProviderBuilderList<>(processorProviders);
76 | }
77 |
78 | /**
79 | * Evict all the cached data.
80 | */
81 | public Completable evictAll() {
82 | return Completable.fromObservable(processorProviders.evictAll());
83 | }
84 |
85 | /**
86 | * Builder for building an specific ReactiveCache instance
87 | */
88 | public static class Builder {
89 | private boolean useExpiredDataIfLoaderNotAvailable;
90 | private Integer diskCacheSize;
91 | private String encryptKey;
92 | private List migrationsCache;
93 | private File cacheDirectory;
94 | private JolyglotGenerics jolyglot;
95 |
96 | /**
97 | * if called ReactiveCache dispatches records already expired instead of throwing an exception.
98 | */
99 | public Builder useExpiredDataWhenNoLoaderAvailable() {
100 | this.useExpiredDataIfLoaderNotAvailable = true;
101 | return this;
102 | }
103 |
104 | /**
105 | * Sets the max memory in megabytes for all the cached data on disk If not supplied, 100
106 | * megabytes will be the default value.
107 | */
108 | public Builder diskCacheSize(Integer megabytes) {
109 | this.diskCacheSize = megabytes;
110 | return this;
111 | }
112 |
113 | /**
114 | * Set the key to encrypt data for specifics providers.
115 | */
116 | public Builder encrypt(String key) {
117 | this.encryptKey = key;
118 | return this;
119 | }
120 |
121 | /**
122 | * Set the migrations to run between releases.
123 | */
124 | public Builder migrations(List migrationsCache) {
125 | this.migrationsCache = migrationsCache;
126 | return this;
127 | }
128 |
129 | /**
130 | * Sets the File cache system and the implementation of {@link JolyglotGenerics} to serialise
131 | * and deserialize objects
132 | *
133 | * @param cacheDirectory The File system used by the persistence layer
134 | * @param jolyglot A concrete implementation of {@link JolyglotGenerics}
135 | */
136 | public ReactiveCache using(File cacheDirectory, JolyglotGenerics jolyglot) {
137 | if (cacheDirectory == null) {
138 | throw new InvalidParameterException(Locale.REPOSITORY_DISK_ADAPTER_CAN_NOT_BE_NULL);
139 | }
140 | if (!cacheDirectory.exists()) {
141 | throw new InvalidParameterException(Locale.REPOSITORY_DISK_ADAPTER_DOES_NOT_EXIST);
142 | }
143 | if (!cacheDirectory.canWrite()) {
144 | throw new InvalidParameterException(Locale.REPOSITORY_DISK_ADAPTER_IS_NOT_WRITABLE);
145 | }
146 |
147 | if (jolyglot == null) {
148 | throw new InvalidParameterException(Locale.JSON_CONVERTER_CAN_NOT_BE_NULL);
149 | }
150 |
151 | this.cacheDirectory = cacheDirectory;
152 | this.jolyglot = jolyglot;
153 |
154 | return new ReactiveCache(this);
155 | }
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/reactive_cache/src/test/java/io/reactivecache2/ActionsListTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 Victor Albertos
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.reactivecache2;
18 |
19 | import io.reactivex.Single;
20 | import io.reactivex.observers.TestObserver;
21 | import java.util.ArrayList;
22 | import java.util.Arrays;
23 | import java.util.List;
24 | import org.junit.Test;
25 |
26 | import static org.hamcrest.MatcherAssert.assertThat;
27 | import static org.hamcrest.core.Is.is;
28 |
29 | public abstract class ActionsListTest {
30 |
31 | @Test public void Add_All() {
32 | checkInitialState();
33 | addAll(10);
34 | }
35 |
36 | @Test public void Add_All_First() {
37 | checkInitialState();
38 | addAll(10);
39 |
40 | actions()
41 | .addAllFirst(Arrays.asList(new Mock("11"), new Mock("12")))
42 | .test()
43 | .awaitTerminalEvent();
44 |
45 | List mocks = cache().test().values().get(0);
46 | assertThat(mocks.size(), is(12));
47 | assertThat(mocks.get(0).getMessage(), is("11"));
48 | assertThat(mocks.get(1).getMessage(), is("12"));
49 | }
50 |
51 | @Test public void Add_All_Last() {
52 | checkInitialState();
53 | addAll(10);
54 |
55 | actions()
56 | .addAllLast(Arrays.asList(new Mock("11"), new Mock("12")))
57 | .test()
58 | .awaitTerminalEvent();
59 |
60 | List mocks = cache().test().values().get(0);
61 | assertThat(mocks.size(), is(12));
62 | assertThat(mocks.get(10).getMessage(), is("11"));
63 | assertThat(mocks.get(11).getMessage(), is("12"));
64 | }
65 |
66 | @Test public void Add_First() {
67 | checkInitialState();
68 |
69 | actions()
70 | .addFirst(new Mock("1"))
71 | .test()
72 | .awaitTerminalEvent();
73 |
74 | List mocks = cache().test().values().get(0);
75 | assertThat(mocks.size(), is(1));
76 | assertThat(mocks.get(0).getMessage(), is("1"));
77 | }
78 |
79 | @Test public void Add_Last() {
80 | checkInitialState();
81 | addAll(10);
82 |
83 | actions()
84 | .addLast(new Mock("11"))
85 | .test()
86 | .awaitTerminalEvent();
87 |
88 | List mocks = cache().test().values().get(0);
89 | assertThat(mocks.size(), is(11));
90 | assertThat(mocks.get(10).getMessage(), is("11"));
91 | }
92 |
93 | @Test public void Add() {
94 | checkInitialState();
95 | addAll(10);
96 |
97 | actions()
98 | .add((position, count) -> position == 5, new Mock("6_added"))
99 | .test()
100 | .awaitTerminalEvent();
101 |
102 | List mocks = cache().test().values().get(0);
103 | assertThat(mocks.size(), is(11));
104 | assertThat(mocks.get(5).getMessage(), is("6_added"));
105 | }
106 |
107 | @Test public void EvictFirst() {
108 | checkInitialState();
109 | addAll(10);
110 |
111 | actions()
112 | .evictFirst()
113 | .test()
114 | .awaitTerminalEvent();
115 |
116 | List mocks = cache().test().values().get(0);
117 | assertThat(mocks.size(), is(9));
118 | assertThat(mocks.get(0).getMessage(), is("1"));
119 | }
120 |
121 | @Test public void EvictFirstN() {
122 | checkInitialState();
123 | addAll(10);
124 |
125 | actions()
126 | .evictFirstN(4)
127 | .test()
128 | .awaitTerminalEvent();
129 |
130 | List mocks = cache().test().values().get(0);
131 | assertThat(mocks.size(), is(6));
132 | assertThat(mocks.get(0).getMessage(), is("4"));
133 | }
134 |
135 | @Test public void EvictFirstExposingCount() {
136 | checkInitialState();
137 | addAll(10);
138 |
139 | //do not evict
140 | actions()
141 | .evictFirst(count -> count > 10)
142 | .test()
143 | .awaitTerminalEvent();
144 |
145 | List mocks = cache().test().values().get(0);
146 | assertThat(mocks.size(), is(10));
147 |
148 | //evict
149 | actions()
150 | .evictFirst(count -> count > 9)
151 | .test()
152 | .awaitTerminalEvent();
153 |
154 | mocks = cache().test().values().get(0);
155 | assertThat(mocks.size(), is(9));
156 | assertThat(mocks.get(0).getMessage(), is("1"));
157 | }
158 |
159 | @Test public void EvictFirstNExposingCount() {
160 | checkInitialState();
161 | addAll(10);
162 |
163 | //do not evict
164 | actions()
165 | .evictFirstN(count -> count > 10, 5)
166 | .test()
167 | .awaitTerminalEvent();
168 |
169 | List mocks = cache().test().values().get(0);
170 | assertThat(mocks.size(), is(10));
171 |
172 | //evict
173 | actions()
174 | .evictFirstN(count -> count > 9, 5)
175 | .test()
176 | .awaitTerminalEvent();
177 |
178 | mocks = cache().test().values().get(0);
179 | assertThat(mocks.size(), is(5));
180 | assertThat(mocks.get(0).getMessage(), is("5"));
181 | assertThat(mocks.get(1).getMessage(), is("6"));
182 | }
183 |
184 | @Test public void EvictLast() {
185 | checkInitialState();
186 | addAll(10);
187 |
188 | actions()
189 | .evictLast()
190 | .test()
191 | .awaitTerminalEvent();
192 |
193 | List mocks = cache().test().values().get(0);
194 | assertThat(mocks.size(), is(9));
195 | assertThat(mocks.get(8).getMessage(), is("8"));
196 | }
197 |
198 | @Test public void EvictLastN() {
199 | checkInitialState();
200 | addAll(10);
201 |
202 | actions()
203 | .evictLastN(4)
204 | .test()
205 | .awaitTerminalEvent();
206 |
207 | List mocks = cache().test().values().get(0);
208 | assertThat(mocks.size(), is(6));
209 | assertThat(mocks.get(0).getMessage(), is("0"));
210 | }
211 |
212 | @Test public void EvictLastExposingCount() {
213 | checkInitialState();
214 | addAll(10);
215 |
216 | //do not evict
217 | actions()
218 | .evictLast(count -> count > 10)
219 | .test()
220 | .awaitTerminalEvent();
221 |
222 | List mocks = cache().test().values().get(0);
223 | assertThat(mocks.size(), is(10));
224 |
225 | //evict
226 | actions()
227 | .evictLast(count -> count > 9)
228 | .test()
229 | .awaitTerminalEvent();
230 |
231 | mocks = cache().test().values().get(0);
232 | assertThat(mocks.size(), is(9));
233 | assertThat(mocks.get(8).getMessage(), is("8"));
234 | }
235 |
236 | @Test public void EvictLastNExposingCount() {
237 | checkInitialState();
238 | addAll(10);
239 |
240 | //do not evict
241 | actions()
242 | .evictLastN(count -> count > 10, 5)
243 | .test()
244 | .awaitTerminalEvent();
245 |
246 | List mocks = cache().test().values().get(0);
247 | assertThat(mocks.size(), is(10));
248 |
249 | //evict
250 | actions()
251 | .evictLastN(count -> count > 9, 5)
252 | .test()
253 | .awaitTerminalEvent();
254 |
255 | mocks = cache().test().values().get(0);
256 | assertThat(mocks.size(), is(5));
257 | assertThat(mocks.get(0).getMessage(), is("0"));
258 | assertThat(mocks.get(1).getMessage(), is("1"));
259 | }
260 |
261 | @Test public void EvictExposingElementCurrentIteration() {
262 | checkInitialState();
263 | addAll(10);
264 |
265 | actions()
266 | .evict(element -> element.getMessage().equals("3"))
267 | .test()
268 | .awaitTerminalEvent();
269 |
270 | List mocks = cache().test().values().get(0);
271 | assertThat(mocks.size(), is(9));
272 | assertThat(mocks.get(3).getMessage(), is("4"));
273 | }
274 |
275 | @Test public void EvictExposingCountAndPositionAndElementCurrentIteration() {
276 | checkInitialState();
277 | addAll(10);
278 |
279 | //do not evict
280 | actions()
281 | .evict((position, count, element) -> count > 10 && element.getMessage().equals("3"))
282 | .test()
283 | .awaitTerminalEvent();
284 |
285 | List mocks = cache().test().values().get(0);
286 | assertThat(mocks.size(), is(10));
287 | assertThat(mocks.get(3).getMessage(), is("3"));
288 |
289 | //evict
290 | actions()
291 | .evict((position, count, element) -> count > 9 && element.getMessage().equals("3"))
292 | .test()
293 | .awaitTerminalEvent();
294 |
295 | mocks = cache().test().values().get(0);
296 | assertThat(mocks.size(), is(9));
297 | assertThat(mocks.get(3).getMessage(), is("4"));
298 | }
299 |
300 | @Test public void EvictIterable() {
301 | checkInitialState();
302 | addAll(10);
303 |
304 | actions()
305 | .evictIterable(
306 | (position, count, element) -> element.getMessage().equals("2") || element.getMessage()
307 | .equals("3"))
308 | .test()
309 | .awaitTerminalEvent();
310 |
311 | List mocks = cache().test().values().get(0);
312 | assertThat(mocks.size(), is(8));
313 | assertThat(mocks.get(2).getMessage(), is("4"));
314 | assertThat(mocks.get(3).getMessage(), is("5"));
315 | }
316 |
317 | @Test public void EvictAllKeepingFirstN() {
318 | checkInitialState();
319 | addAll(10);
320 |
321 | actions()
322 | .evictAllKeepingFirstN(3)
323 | .test()
324 | .awaitTerminalEvent();
325 |
326 | List mocks = cache().test().values().get(0);
327 | assertThat(mocks.size(), is(3));
328 | assertThat(mocks.get(0).getMessage(), is("0"));
329 | }
330 |
331 | @Test public void EvictAllKeepingLastN() {
332 | checkInitialState();
333 | addAll(10);
334 |
335 | actions()
336 | .evictAllKeepingLastN(7)
337 | .test()
338 | .awaitTerminalEvent();
339 |
340 | List mocks = cache().test().values().get(0);
341 | assertThat(mocks.size(), is(7));
342 | assertThat(mocks.get(0).getMessage(), is("3"));
343 | }
344 |
345 | @Test public void UpdateExposingElementCurrentIteration() {
346 | checkInitialState();
347 | addAll(10);
348 |
349 | actions()
350 | .update(element -> element.getMessage().equals("5"), element -> {
351 | element.setMessage("5_updated");
352 | return element;
353 | })
354 | .test()
355 | .awaitTerminalEvent();
356 |
357 | List mocks = cache().test().values().get(0);
358 | assertThat(mocks.size(), is(10));
359 | assertThat(mocks.get(5).getMessage(), is("5_updated"));
360 | }
361 |
362 | @Test public void UpdateExposingCountAndPositionAndElementCurrentIteration() {
363 | checkInitialState();
364 | addAll(10);
365 |
366 | //do not evict
367 | actions()
368 | .update((position, count, element) -> count > 10 && element.getMessage().equals("5"),
369 | element -> {
370 | element.setMessage("5_updated");
371 | return element;
372 | })
373 | .test()
374 | .awaitTerminalEvent();
375 |
376 | List mocks = cache().test().values().get(0);
377 | assertThat(mocks.size(), is(10));
378 | assertThat(mocks.get(5).getMessage(), is("5"));
379 |
380 | //evict
381 | actions()
382 | .update((position, count, element) -> count > 9 && element.getMessage().equals("5"),
383 | element -> {
384 | element.setMessage("5_updated");
385 | return element;
386 | })
387 | .test()
388 | .awaitTerminalEvent();
389 |
390 | mocks = cache().test().values().get(0);
391 | assertThat(mocks.size(), is(10));
392 | assertThat(mocks.get(5).getMessage(), is("5_updated"));
393 | }
394 |
395 | @Test public void UpdateIterableExposingElementCurrentIteration() {
396 | checkInitialState();
397 | addAll(10);
398 |
399 | actions()
400 | .updateIterable(
401 | element -> element.getMessage().equals("5") || element.getMessage().equals("6"),
402 | element -> {
403 | element.setMessage("5_or_6_updated");
404 | return element;
405 | })
406 | .test()
407 | .awaitTerminalEvent();
408 |
409 | List mocks = cache().test().values().get(0);
410 | assertThat(mocks.size(), is(10));
411 | assertThat(mocks.get(5).getMessage(), is("5_or_6_updated"));
412 | assertThat(mocks.get(6).getMessage(), is("5_or_6_updated"));
413 | }
414 |
415 | @Test public void UpdateIterableExposingCountAndPositionAndElementCurrentIteration() {
416 | checkInitialState();
417 | addAll(10);
418 |
419 | //do not evict
420 | actions()
421 | .updateIterable(
422 | (position, count, element) -> count > 10 && (element.getMessage().equals("5") || element.getMessage()
423 | .equals("6")), element -> {
424 | element.setMessage("5_or_6_updated");
425 | return element;
426 | })
427 | .test()
428 | .awaitTerminalEvent();
429 |
430 | List mocks = cache().test().values().get(0);
431 | assertThat(mocks.size(), is(10));
432 | assertThat(mocks.get(5).getMessage(), is("5"));
433 | assertThat(mocks.get(6).getMessage(), is("6"));
434 |
435 | //evict
436 | actions()
437 | .updateIterable(
438 | (position, count, element) -> count > 9 && (element.getMessage().equals("5") || element.getMessage()
439 | .equals("6")), element -> {
440 | element.setMessage("5_or_6_updated");
441 | return element;
442 | })
443 | .test()
444 | .awaitTerminalEvent();
445 |
446 | mocks = cache().test().values().get(0);
447 | assertThat(mocks.size(), is(10));
448 | assertThat(mocks.get(5).getMessage(), is("5_or_6_updated"));
449 | assertThat(mocks.get(6).getMessage(), is("5_or_6_updated"));
450 | }
451 |
452 | private void checkInitialState() {
453 | TestObserver> testObserver = cache().test();
454 | testObserver.awaitTerminalEvent();
455 |
456 | if (testObserver.values().isEmpty()) return;
457 |
458 | List mocks = testObserver.values().get(0);
459 | assertThat(mocks.size(), is(0));
460 | }
461 |
462 | private void addAll(int count) {
463 | List mocks = new ArrayList<>();
464 |
465 | for (int i = 0; i < count; i++) {
466 | mocks.add(new Mock(String.valueOf(i)));
467 | }
468 |
469 | actions()
470 | .addAll((position, count1) -> true, mocks)
471 | .test()
472 | .assertNoErrors()
473 | .awaitTerminalEvent();
474 |
475 | assertThat(cache().test().values().get(0).size(),
476 | is(count));
477 | }
478 |
479 | protected abstract ActionsList actions();
480 |
481 | protected abstract Single> cache();
482 | }
483 |
--------------------------------------------------------------------------------
/reactive_cache/src/test/java/io/reactivecache2/ExceptionAdapterTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Victor Albertos
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.reactivecache2;
18 |
19 | import io.reactivex.exceptions.CompositeException;
20 | import io.rx_cache2.RxCacheException;
21 | import java.util.List;
22 | import org.junit.Before;
23 | import org.junit.Test;
24 |
25 | import static org.hamcrest.core.Is.is;
26 | import static org.hamcrest.core.IsEqual.equalTo;
27 | import static org.junit.Assert.assertThat;
28 |
29 | public final class ExceptionAdapterTest {
30 | private ExceptionAdapter exceptionAdapter;
31 |
32 | @Before public void setUp() {
33 | exceptionAdapter = new ExceptionAdapter();
34 | }
35 |
36 | @Test public void When_Exception_Is_Not_RxCache_Exception_Then_Return_Observable_Error() {
37 | exceptionAdapter.completeOnRxCacheLoaderError(new RuntimeException())
38 | .test()
39 | .assertNoValues()
40 | .assertNotComplete()
41 | .assertError(RuntimeException.class);
42 | }
43 |
44 | @Test
45 | public void When_CompositeException_Is_Not_RxCache_Exception_Then_Return_Observable_Error() {
46 | CompositeException compositeException = new CompositeException(new RuntimeException());
47 | exceptionAdapter.completeOnRxCacheLoaderError(compositeException)
48 | .test()
49 | .assertNoValues()
50 | .assertNotComplete()
51 | .assertError(RuntimeException.class);
52 | }
53 |
54 | @Test public void When_Exception_Is_RxCache_Exception_Then_Return_Completable() {
55 | exceptionAdapter.completeOnRxCacheLoaderError(new RxCacheException(""))
56 | .test()
57 | .assertNoValues()
58 | .assertNoErrors()
59 | .assertComplete();
60 | }
61 |
62 | @Test public void When_CompositeException_Is_RxCache_Exception_Then_Return_Completable() {
63 | CompositeException compositeException = new CompositeException(new RuntimeException(),
64 | new RxCacheException(""), new RuntimeException());
65 |
66 | exceptionAdapter.completeOnRxCacheLoaderError(compositeException)
67 | .test()
68 | .assertNoValues()
69 | .assertNoErrors()
70 | .assertComplete();
71 | }
72 |
73 | @Test public void When_StripPlaceholderLoaderException_Then_Strip_It() {
74 | exceptionAdapter.stripPlaceholderLoaderException(new RuntimeException())
75 | .test()
76 | .assertNoValues()
77 | .assertNotComplete()
78 | .assertError(RuntimeException.class);
79 | }
80 |
81 | @Test public void When_StripPlaceholderLoaderException_Composite_Then_Strip_It() {
82 | CompositeException compositeException = new CompositeException(new RuntimeException(),
83 | new ExceptionAdapter.PlaceHolderLoader(), new RuntimeException());
84 |
85 | List errors = exceptionAdapter.stripPlaceholderLoaderException(compositeException)
86 | .test()
87 | .assertNoValues()
88 | .assertNotComplete()
89 | .errors();
90 | assertThat(1, is(errors.size()));
91 |
92 | List compositeErrors = ((CompositeException) errors.get(0)).getExceptions();
93 | assertThat(2, is(compositeErrors.size()));
94 | assertThat(RuntimeException.class, is(equalTo(compositeErrors.get(0).getClass())));
95 | assertThat(RuntimeException.class, is(equalTo(compositeErrors.get(1).getClass())));
96 | }
97 |
98 | @Test public void When_emptyListIfRxCacheException_RxCacheException_Then_Empty_List() {
99 | exceptionAdapter.emptyListIfRxCacheException(new RxCacheException(""))
100 | .test()
101 | .assertNoErrors()
102 | .assertValueAt(0, List::isEmpty)
103 | .assertComplete();
104 | }
105 |
106 | @Test public void When_emptyListIfRxCacheException_Exception_Then_Throw() {
107 | exceptionAdapter.emptyListIfRxCacheException(new RuntimeException("Fuck"))
108 | .test()
109 | .assertNoValues()
110 | .assertNotComplete()
111 | .assertErrorMessage("Fuck");
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/reactive_cache/src/test/java/io/reactivecache2/Jolyglot$.java:
--------------------------------------------------------------------------------
1 | package io.reactivecache2;
2 |
3 | import io.victoralbertos.jolyglot.JolyglotGenerics;
4 | import io.victoralbertos.jolyglot.MoshiSpeaker;
5 |
6 | public final class Jolyglot$ {
7 | public static JolyglotGenerics newInstance() {
8 | return new MoshiSpeaker();
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/reactive_cache/src/test/java/io/reactivecache2/Mock.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Victor Albertos
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.reactivecache2;
18 |
19 | public final class Mock {
20 | private String message;
21 |
22 | public Mock(String message) {
23 | this.message = message;
24 | }
25 |
26 | public Mock() {
27 | this.message = null;
28 | }
29 |
30 | public String getMessage() {
31 | return message;
32 | }
33 |
34 | public void setMessage(String message) {
35 | this.message = message;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/reactive_cache/src/test/java/io/reactivecache2/ProviderGroupListTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 Victor Albertos
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.reactivecache2;
18 |
19 | import io.reactivex.Single;
20 | import io.reactivex.observers.TestObserver;
21 | import io.rx_cache2.RxCacheException;
22 | import java.util.Arrays;
23 | import java.util.List;
24 | import org.junit.Before;
25 | import org.junit.Rule;
26 | import org.junit.Test;
27 | import org.junit.rules.TemporaryFolder;
28 |
29 | public final class ProviderGroupListTest extends ActionsListTest {
30 | private static final String group = "group";
31 | @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder();
32 | private ReactiveCache reactiveCache;
33 | private ProviderGroupList cacheProvider;
34 |
35 | @Before public void setUp() {
36 | reactiveCache = new ReactiveCache.Builder()
37 | .using(temporaryFolder.getRoot(), Jolyglot$.newInstance());
38 | cacheProvider = reactiveCache.providerGroupList()
39 | .withKey("mock");
40 | }
41 |
42 | @Test public void Verify_Inherited_Api() {
43 | final String message = "1";
44 |
45 | //save
46 | Single.just(Arrays.asList(new Mock(message)))
47 | .compose(cacheProvider.replace(group))
48 | .test()
49 | .awaitTerminalEvent();
50 |
51 | TestObserver> observer = cacheProvider.read(group)
52 | .test();
53 |
54 | //read
55 | observer.awaitTerminalEvent();
56 | observer
57 | .assertValueAt(0, mocks -> mocks.get(0).getMessage().equals("1"))
58 | .assertNoErrors()
59 | .assertComplete();
60 |
61 | //evict
62 | cacheProvider.evict().test().awaitTerminalEvent();
63 |
64 | //read throws
65 | observer = cacheProvider.read(group).test();
66 | observer.awaitTerminalEvent();
67 | observer.assertNoValues()
68 | .assertError(RxCacheException.class)
69 | .assertNotComplete();
70 |
71 | //read with loader
72 | observer = Single.just(Arrays.asList(new Mock(message)))
73 | .compose(cacheProvider.readWithLoader(group)).test();
74 | observer.awaitTerminalEvent();
75 | observer
76 | .assertValueAt(0, mocks -> mocks.get(0).getMessage().equals("1"))
77 | .assertNoErrors()
78 | .assertComplete();
79 | }
80 |
81 | @Override protected ActionsList actions() {
82 | return cacheProvider.entries(group);
83 | }
84 |
85 | @Override protected Single> cache() {
86 | return cacheProvider.read(group);
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/reactive_cache/src/test/java/io/reactivecache2/ProviderGroupTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Victor Albertos
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.reactivecache2;
18 |
19 | import io.reactivex.Single;
20 | import io.reactivex.observers.TestObserver;
21 | import io.rx_cache2.Reply;
22 | import io.rx_cache2.RxCacheException;
23 | import io.rx_cache2.Source;
24 | import java.util.concurrent.TimeUnit;
25 | import org.junit.Before;
26 | import org.junit.Rule;
27 | import org.junit.Test;
28 | import org.junit.rules.TemporaryFolder;
29 |
30 | import static org.hamcrest.core.Is.is;
31 | import static org.junit.Assert.assertThat;
32 |
33 | public final class ProviderGroupTest {
34 | @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder();
35 | private ReactiveCache reactiveCache;
36 | private ProviderGroup cacheProvider;
37 | private static final String MESSAGE_GROUP = "MESSAGE_GROUP";
38 |
39 | @Before public void setUp() {
40 | reactiveCache = new ReactiveCache.Builder()
41 | .using(temporaryFolder.getRoot(), Jolyglot$.newInstance());
42 |
43 | cacheProvider = reactiveCache.providerGroup()
44 | .withKey("mock");
45 | }
46 |
47 | @Test public void When_Evict_With_No_Cached_Data_Then_Do_Not_Throw() {
48 | TestObserver observer = cacheProvider.evict().test();
49 | observer.awaitTerminalEvent();
50 |
51 | observer.assertNoErrors();
52 | observer.assertNoValues();
53 | observer.assertComplete();
54 | }
55 |
56 | @Test public void When_Evict_By_Group_With_No_Cached_Data_Then_Do_Not_Throw() {
57 | TestObserver observer = cacheProvider.evict(MESSAGE_GROUP).test();
58 | observer.awaitTerminalEvent();
59 |
60 | observer.assertNoErrors();
61 | observer.assertNoValues();
62 | observer.assertComplete();
63 | }
64 |
65 | @Test public void When_Evict_With_Cached_Data_Then_Evict_Data() {
66 | saveMock(MESSAGE_GROUP);
67 | verifyMockCached(MESSAGE_GROUP);
68 |
69 | saveMock(MESSAGE_GROUP+2);
70 | verifyMockCached(MESSAGE_GROUP+2);
71 |
72 | TestObserver observer = cacheProvider.evict().test();
73 | observer.awaitTerminalEvent();
74 |
75 | observer.assertNoErrors();
76 | observer.assertNoValues();
77 | observer.assertComplete();
78 |
79 | verifyNoMockCached(MESSAGE_GROUP);
80 | verifyNoMockCached(MESSAGE_GROUP+2);
81 | }
82 |
83 | @Test public void When_Evict_By_Group_With_Cached_Data_Then_Evict_Data() {
84 | saveMock(MESSAGE_GROUP);
85 | verifyMockCached(MESSAGE_GROUP);
86 |
87 | saveMock(MESSAGE_GROUP+2);
88 | verifyMockCached(MESSAGE_GROUP+2);
89 |
90 | TestObserver observer = cacheProvider.evict(MESSAGE_GROUP).test();
91 | observer.awaitTerminalEvent();
92 |
93 | observer.assertNoErrors();
94 | observer.assertNoValues();
95 | observer.assertComplete();
96 |
97 | verifyNoMockCached(MESSAGE_GROUP);
98 | verifyMockCached(MESSAGE_GROUP+2);
99 | }
100 |
101 | @Test public void When_Evict_With_Cached_And_Use_Expired_Data_Then_Evict_Data() {
102 | reactiveCache = new ReactiveCache.Builder()
103 | .useExpiredDataWhenNoLoaderAvailable()
104 | .using(temporaryFolder.getRoot(), Jolyglot$.newInstance());
105 |
106 | cacheProvider = reactiveCache.providerGroup()
107 | .withKey("mock");
108 |
109 | saveMock(MESSAGE_GROUP);
110 | verifyMockCached(MESSAGE_GROUP);
111 |
112 | saveMock(MESSAGE_GROUP+2);
113 | verifyMockCached(MESSAGE_GROUP+2);
114 |
115 | TestObserver observer = cacheProvider.evict().test();
116 | observer.awaitTerminalEvent();
117 |
118 | observer.assertNoErrors();
119 | observer.assertNoValues();
120 | observer.assertComplete();
121 |
122 | verifyNoMockCached(MESSAGE_GROUP);
123 | verifyNoMockCached(MESSAGE_GROUP+2);
124 | }
125 |
126 | @Test public void When_Evict_By_Group_With_Cached_And_Use_Expired_Data_Then_Evict_Data() {
127 | reactiveCache = new ReactiveCache.Builder()
128 | .useExpiredDataWhenNoLoaderAvailable()
129 | .using(temporaryFolder.getRoot(), Jolyglot$.newInstance());
130 |
131 | cacheProvider = reactiveCache.providerGroup()
132 | .withKey("mock");
133 |
134 | saveMock(MESSAGE_GROUP+2);
135 | verifyMockCached(MESSAGE_GROUP+2);
136 |
137 | saveMock(MESSAGE_GROUP);
138 | verifyMockCached(MESSAGE_GROUP);
139 |
140 | TestObserver observer = cacheProvider.evict(MESSAGE_GROUP).test();
141 | observer.awaitTerminalEvent();
142 |
143 | observer.assertNoErrors();
144 | observer.assertNoValues();
145 | observer.assertComplete();
146 |
147 | verifyNoMockCached(MESSAGE_GROUP);
148 | verifyMockCached(MESSAGE_GROUP+2);
149 | }
150 |
151 | @Test public void When_Replace_But_Loader_Throws_Then_Do_Not_Replace_Cache() {
152 | saveMock(MESSAGE_GROUP);
153 | verifyMockCached(MESSAGE_GROUP);
154 |
155 | TestObserver observer = Single.error(new RuntimeException())
156 | .compose(cacheProvider.replace(MESSAGE_GROUP))
157 | .test();
158 | observer.awaitTerminalEvent();
159 |
160 | observer.assertError(RuntimeException.class);
161 | observer.assertNoValues();
162 |
163 | verifyMockCached(MESSAGE_GROUP);
164 | }
165 |
166 | @Test public void When_Replace_Then_Replace_Cache() {
167 | saveMock(MESSAGE_GROUP);
168 | verifyMockCached(MESSAGE_GROUP);
169 |
170 | TestObserver observer = Single.just(new Mock("1"))
171 | .compose(cacheProvider.replace(MESSAGE_GROUP))
172 | .test();
173 | observer.awaitTerminalEvent();
174 |
175 | observer.assertValueCount(1);
176 | observer.assertNoErrors();
177 | observer.assertComplete();
178 |
179 | observer = cacheProvider.read(MESSAGE_GROUP).test();
180 | observer.awaitTerminalEvent();
181 |
182 | observer.assertValueCount(1);
183 | observer.assertNoErrors();
184 | observer.assertComplete();
185 | assertThat(observer.values()
186 | .get(0).getMessage(), is("1"));
187 | }
188 |
189 | @Test public void When_Read_With_Nothing_To_Read_Then_Throw() {
190 | TestObserver observer = cacheProvider.read(MESSAGE_GROUP).test();
191 | observer.awaitTerminalEvent();
192 |
193 | observer.assertError(RxCacheException.class);
194 | observer.assertNoValues();
195 | }
196 |
197 | @Test public void When_Read_Then_Return_Data() {
198 | saveMock(MESSAGE_GROUP);
199 |
200 | TestObserver observer = cacheProvider.read(MESSAGE_GROUP).test();
201 | observer.awaitTerminalEvent();
202 |
203 | observer.assertValueCount(1);
204 | observer.assertNoErrors();
205 | observer.assertComplete();
206 | assertThat(observer.values()
207 | .get(0).getMessage(), is(MESSAGE_GROUP));
208 | }
209 |
210 | @Test public void Verify_Read_With_Loader() {
211 | cacheProvider = reactiveCache.providerGroup()
212 | .lifeCache(100, TimeUnit.MILLISECONDS)
213 | .withKey("ephemeralMock");
214 |
215 | saveMock(MESSAGE_GROUP);
216 |
217 | TestObserver observer = Single.just(new Mock("1"))
218 | .compose(cacheProvider.readWithLoader(MESSAGE_GROUP))
219 | .test();
220 | observer.awaitTerminalEvent();
221 |
222 | observer.assertNoErrors();
223 | observer.assertValueCount(1);
224 | assertThat(observer.values()
225 | .get(0).getMessage(), is(MESSAGE_GROUP));
226 |
227 | waitTime(200);
228 |
229 | observer = Single.just(new Mock("1"))
230 | .compose(cacheProvider.readWithLoader(MESSAGE_GROUP))
231 | .test();
232 | observer.awaitTerminalEvent();
233 |
234 | observer.assertNoErrors();
235 | observer.assertValueCount(1);
236 | assertThat(observer.values()
237 | .get(0).getMessage(), is("1"));
238 |
239 | observer = Single.just(new Mock("3"))
240 | .compose(cacheProvider.readWithLoader(MESSAGE_GROUP))
241 | .test();
242 | observer.awaitTerminalEvent();
243 |
244 | observer.assertNoErrors();
245 | observer.assertValueCount(1);
246 | assertThat(observer.values()
247 | .get(0).getMessage(), is("1"));
248 | }
249 |
250 | @Test public void Verify_Read_As_Reply_With_Loader() {
251 | cacheProvider = reactiveCache.providerGroup()
252 | .lifeCache(100, TimeUnit.MILLISECONDS)
253 | .withKey("ephemeralMock");
254 |
255 | saveMock(MESSAGE_GROUP);
256 |
257 | TestObserver> observer = Single.just(new Mock(MESSAGE_GROUP))
258 | .compose(cacheProvider.readWithLoaderAsReply(MESSAGE_GROUP))
259 | .test();
260 | observer.awaitTerminalEvent();
261 |
262 | observer.assertNoErrors();
263 | observer.assertValueCount(1);
264 | assertThat(observer.values()
265 | .get(0).getSource(), is(Source.MEMORY));
266 |
267 | waitTime(200);
268 |
269 | observer = Single.just(new Mock(MESSAGE_GROUP))
270 | .compose(cacheProvider.readWithLoaderAsReply(MESSAGE_GROUP))
271 | .test();
272 | observer.awaitTerminalEvent();
273 |
274 | observer.assertNoErrors();
275 | observer.assertValueCount(1);
276 | assertThat(observer.values()
277 | .get(0).getSource(), is(Source.CLOUD));
278 | }
279 |
280 | @Test public void Verify_Pagination() {
281 | ProviderGroup mockProvider = reactiveCache.providerGroup()
282 | .withKey("mocks");
283 |
284 | TestObserver observer;
285 |
286 | Mock mockPage1 = new Mock("1");
287 |
288 | observer = Single.just(mockPage1)
289 | .compose(mockProvider.replace("1"))
290 | .test();
291 |
292 | observer.awaitTerminalEvent();
293 |
294 | Mock mockPage2 = new Mock("2");
295 |
296 | observer = Single.just(mockPage2)
297 | .compose(mockProvider.replace("2"))
298 | .test();
299 |
300 | observer.awaitTerminalEvent();
301 |
302 | Mock mockPage3 = new Mock("3");
303 |
304 | observer = Single.just(mockPage3)
305 | .compose(mockProvider.replace("3"))
306 | .test();
307 | observer.awaitTerminalEvent();
308 |
309 | observer = mockProvider.read("1").test();
310 | observer.awaitTerminalEvent();
311 | assertThat(observer.values().get(0).getMessage(), is("1"));
312 |
313 | observer = mockProvider.read("2").test();
314 | observer.awaitTerminalEvent();
315 | assertThat(observer.values().get(0).getMessage(), is("2"));
316 |
317 | observer = mockProvider.read("3").test();
318 | observer.awaitTerminalEvent();
319 | assertThat(observer.values().get(0).getMessage(), is("3"));
320 | }
321 |
322 | private void saveMock(String messageGroup) {
323 | Single.just(new Mock(messageGroup))
324 | .compose(cacheProvider.replace(messageGroup))
325 | .test()
326 | .awaitTerminalEvent();
327 | }
328 |
329 | private void verifyMockCached(String messageGroup) {
330 | TestObserver observer = cacheProvider.read(messageGroup).test();
331 | observer.awaitTerminalEvent();
332 |
333 | observer.assertNoErrors();
334 | observer.assertValueCount(1);
335 | assertThat(observer.values()
336 | .get(0).getMessage(), is(messageGroup));
337 | }
338 |
339 | private void verifyNoMockCached(String group) {
340 | TestObserver observer = cacheProvider.read(group).test();
341 | observer.awaitTerminalEvent();
342 |
343 | observer.assertError(RxCacheException.class);
344 | observer.assertNoValues();
345 | observer.onComplete();
346 | }
347 |
348 | private void waitTime(long millis) {
349 | try {
350 | Thread.sleep(millis);
351 | } catch (InterruptedException e) {
352 | e.printStackTrace();
353 | }
354 | }
355 |
356 |
357 | }
358 |
--------------------------------------------------------------------------------
/reactive_cache/src/test/java/io/reactivecache2/ProviderListTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 Victor Albertos
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.reactivecache2;
18 |
19 | import io.reactivex.Single;
20 | import io.reactivex.observers.TestObserver;
21 | import io.rx_cache2.RxCacheException;
22 | import java.util.Arrays;
23 | import java.util.List;
24 | import org.junit.Before;
25 | import org.junit.Rule;
26 | import org.junit.Test;
27 | import org.junit.rules.TemporaryFolder;
28 |
29 | public final class ProviderListTest extends ActionsListTest {
30 | @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder();
31 | private ReactiveCache reactiveCache;
32 | private ProviderList cacheProvider;
33 |
34 | @Before public void setUp() {
35 | reactiveCache = new ReactiveCache.Builder()
36 | .using(temporaryFolder.getRoot(), Jolyglot$.newInstance());
37 | cacheProvider = reactiveCache.providerList()
38 | .withKey("mock");
39 | }
40 |
41 | @Test public void Verify_Inherited_Api() {
42 | final String message = "1";
43 |
44 | //save
45 | Single.just(Arrays.asList(new Mock(message)))
46 | .compose(cacheProvider.replace())
47 | .test()
48 | .awaitTerminalEvent();
49 |
50 | TestObserver> observer = cacheProvider.read()
51 | .test();
52 |
53 | //read
54 | observer.awaitTerminalEvent();
55 | observer
56 | .assertValueAt(0, mocks -> mocks.get(0).getMessage().equals("1"))
57 | .assertNoErrors()
58 | .assertComplete();
59 |
60 | //evict
61 | cacheProvider.evict().test().awaitTerminalEvent();
62 |
63 | //read throws
64 | observer = cacheProvider.read().test();
65 | observer.awaitTerminalEvent();
66 | observer.assertNoValues()
67 | .assertError(RxCacheException.class)
68 | .assertNotComplete();
69 |
70 | //read with loader
71 | observer = Single.just(Arrays.asList(new Mock(message)))
72 | .compose(cacheProvider.readWithLoader()).test();
73 | observer.awaitTerminalEvent();
74 | observer
75 | .assertValueAt(0, mocks -> mocks.get(0).getMessage().equals("1"))
76 | .assertNoErrors()
77 | .assertComplete();
78 | }
79 |
80 | @Override protected ActionsList actions() {
81 | return cacheProvider.entries();
82 | }
83 |
84 | @Override protected Single> cache() {
85 | return cacheProvider.read();
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/reactive_cache/src/test/java/io/reactivecache2/ProviderTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Victor Albertos
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.reactivecache2;
18 |
19 | import io.reactivex.Single;
20 | import io.reactivex.observers.TestObserver;
21 | import io.rx_cache2.Reply;
22 | import io.rx_cache2.RxCacheException;
23 | import io.rx_cache2.Source;
24 | import java.util.concurrent.TimeUnit;
25 | import org.junit.Before;
26 | import org.junit.Rule;
27 | import org.junit.Test;
28 | import org.junit.rules.TemporaryFolder;
29 |
30 | import static org.hamcrest.core.Is.is;
31 | import static org.junit.Assert.assertThat;
32 |
33 | public final class ProviderTest {
34 | @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder();
35 | private ReactiveCache reactiveCache;
36 | private Provider cacheProvider;
37 | private static final String MESSAGE = "0";
38 |
39 | @Before public void setUp() {
40 | reactiveCache = new ReactiveCache.Builder()
41 | .using(temporaryFolder.getRoot(), Jolyglot$.newInstance());
42 |
43 | cacheProvider = reactiveCache.provider()
44 | .withKey("mock");
45 | }
46 |
47 | @Test public void When_useExpiredDataWhenNoLoaderAvailable_Then_Retrieve_Expired_Data() {
48 | reactiveCache = new ReactiveCache.Builder()
49 | .useExpiredDataWhenNoLoaderAvailable()
50 | .using(temporaryFolder.getRoot(), Jolyglot$.newInstance());
51 |
52 | cacheProvider = reactiveCache.provider()
53 | .lifeCache(100, TimeUnit.MILLISECONDS)
54 | .withKey("mock");
55 |
56 | saveMock();
57 | verifyMockCached();
58 |
59 | waitTime(300);
60 |
61 | verifyMockCached();
62 | }
63 |
64 | @Test public void When_Not_useExpiredDataWhenNoLoaderAvailable_Then_Throw() {
65 | reactiveCache = new ReactiveCache.Builder()
66 | .using(temporaryFolder.getRoot(), Jolyglot$.newInstance());
67 |
68 | cacheProvider = reactiveCache.provider()
69 | .lifeCache(100, TimeUnit.MILLISECONDS)
70 | .withKey("mock");
71 |
72 | saveMock();
73 | verifyMockCached();
74 |
75 | waitTime(300);
76 |
77 | verifyNoMockCached();
78 | }
79 |
80 | @Test public void When_Evict_With_No_Cached_Data_Then_Do_Not_Throw() {
81 | TestObserver observer = cacheProvider.evict().test();
82 | observer.awaitTerminalEvent();
83 |
84 | observer.assertNoErrors();
85 | observer.assertNoValues();
86 | observer.assertComplete();
87 | }
88 |
89 | @Test public void When_Evict_With_Cached_Data_Then_Evict_Data() {
90 | saveMock();
91 | verifyMockCached();
92 |
93 | TestObserver observer = cacheProvider.evict().test();
94 | observer.awaitTerminalEvent();
95 |
96 | observer.assertNoErrors();
97 | observer.assertNoValues();
98 | observer.assertComplete();
99 |
100 | verifyNoMockCached();
101 | }
102 |
103 | @Test public void When_Evict_With_Cached_And_Use_Expired_Data_Then_Evict_Data() {
104 | reactiveCache = new ReactiveCache.Builder()
105 | .useExpiredDataWhenNoLoaderAvailable()
106 | .using(temporaryFolder.getRoot(), Jolyglot$.newInstance());
107 |
108 | cacheProvider = reactiveCache.provider()
109 | .withKey("mock");
110 |
111 | saveMock();
112 | verifyMockCached();
113 |
114 | TestObserver observer = cacheProvider.evict().test();
115 | observer.awaitTerminalEvent();
116 |
117 | observer.assertNoErrors();
118 | observer.assertNoValues();
119 | observer.assertComplete();
120 |
121 | verifyNoMockCached();
122 | }
123 |
124 | @Test public void When_Replace_But_Loader_Throws_Then_Do_Not_Replace_Cache() {
125 | saveMock();
126 | verifyMockCached();
127 |
128 | TestObserver observer = Single.error(new RuntimeException())
129 | .compose(cacheProvider.replace())
130 | .test();
131 | observer.awaitTerminalEvent();
132 |
133 | observer.assertError(RuntimeException.class);
134 | observer.assertNoValues();
135 |
136 | verifyMockCached();
137 | }
138 |
139 | @Test public void When_Replace_Then_Replace_Cache() {
140 | saveMock();
141 | verifyMockCached();
142 |
143 | TestObserver observer = Single.just(new Mock("1"))
144 | .compose(cacheProvider.replace())
145 | .test();
146 | observer.awaitTerminalEvent();
147 |
148 | observer.assertValueCount(1);
149 | observer.assertNoErrors();
150 | observer.assertComplete();
151 |
152 | observer = cacheProvider.read().test();
153 | observer.awaitTerminalEvent();
154 |
155 | observer.assertValueCount(1);
156 | observer.assertNoErrors();
157 | observer.assertComplete();
158 | assertThat(observer.values()
159 | .get(0).getMessage(), is("1"));
160 | }
161 |
162 | @Test public void When_Read_With_Nothing_To_Read_Then_Throw() {
163 | TestObserver observer = cacheProvider.read().test();
164 | ;
165 | observer.awaitTerminalEvent();
166 |
167 | observer.assertError(RxCacheException.class);
168 | observer.assertNoValues();
169 | }
170 |
171 | @Test public void When_Read_Then_Return_Data() {
172 | saveMock();
173 |
174 | TestObserver observer = cacheProvider.read().test();
175 | observer.awaitTerminalEvent();
176 |
177 | observer.assertValueCount(1);
178 | observer.assertNoErrors();
179 | observer.assertComplete();
180 | assertThat(observer.values()
181 | .get(0).getMessage(), is(MESSAGE));
182 | }
183 |
184 | @Test public void Verify_Read_With_Loader() {
185 | cacheProvider = reactiveCache.provider()
186 | .lifeCache(100, TimeUnit.MILLISECONDS)
187 | .withKey("ephemeralMock");
188 |
189 | saveMock();
190 |
191 | TestObserver observer = Single.just(new Mock("1"))
192 | .compose(cacheProvider.readWithLoader())
193 | .test();
194 | observer.awaitTerminalEvent();
195 |
196 | observer.assertNoErrors();
197 | observer.assertValueCount(1);
198 | assertThat(observer.values()
199 | .get(0).getMessage(), is(MESSAGE));
200 |
201 | waitTime(200);
202 |
203 | observer = Single.just(new Mock("1"))
204 | .compose(cacheProvider.readWithLoader())
205 | .test();
206 | observer.awaitTerminalEvent();
207 |
208 | observer.assertNoErrors();
209 | observer.assertValueCount(1);
210 | assertThat(observer.values()
211 | .get(0).getMessage(), is("1"));
212 |
213 | observer = Single.just(new Mock("3"))
214 | .compose(cacheProvider.readWithLoader())
215 | .test();
216 | observer.awaitTerminalEvent();
217 |
218 | observer.assertNoErrors();
219 | observer.assertValueCount(1);
220 | assertThat(observer.values()
221 | .get(0).getMessage(), is("1"));
222 | }
223 |
224 | @Test public void Verify_Read_As_Reply_With_Loader() {
225 | cacheProvider = reactiveCache.provider()
226 | .lifeCache(100, TimeUnit.MILLISECONDS)
227 | .withKey("ephemeralMock");
228 |
229 | saveMock();
230 |
231 | TestObserver> observer = Single.just(new Mock(MESSAGE))
232 | .compose(cacheProvider.readWithLoaderAsReply())
233 | .test();
234 | observer.awaitTerminalEvent();
235 |
236 | observer.assertNoErrors();
237 | observer.assertValueCount(1);
238 | assertThat(observer.values()
239 | .get(0).getSource(), is(Source.MEMORY));
240 |
241 | waitTime(200);
242 |
243 | observer = Single.just(new Mock(MESSAGE))
244 | .compose(cacheProvider.readWithLoaderAsReply())
245 | .test();
246 | observer.awaitTerminalEvent();
247 |
248 | observer.assertNoErrors();
249 | observer.assertValueCount(1);
250 | assertThat(observer.values()
251 | .get(0).getSource(), is(Source.CLOUD));
252 | }
253 |
254 | private void saveMock() {
255 | TestObserver observer = Single.just(new Mock(MESSAGE))
256 | .compose(cacheProvider.replace())
257 | .test();
258 | observer.awaitTerminalEvent();
259 | }
260 |
261 | private void verifyMockCached() {
262 | TestObserver observer = cacheProvider.read().test();
263 | observer.awaitTerminalEvent();
264 |
265 | observer.assertNoErrors();
266 | observer.assertValueCount(1);
267 | assertThat(observer.values()
268 | .get(0).getMessage(), is(MESSAGE));
269 | }
270 |
271 | private void verifyNoMockCached() {
272 | TestObserver observer = cacheProvider.read().test();
273 | observer.awaitTerminalEvent();
274 |
275 | observer.assertError(RxCacheException.class);
276 | observer.assertNoValues();
277 | observer.onComplete();
278 | }
279 |
280 | private void waitTime(long millis) {
281 | try {
282 | Thread.sleep(millis);
283 | } catch (InterruptedException e) {
284 | e.printStackTrace();
285 | }
286 | }
287 | }
288 |
--------------------------------------------------------------------------------
/reactive_cache/src/test/java/io/reactivecache2/ReactiveCacheBuilderValidationTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Victor Albertos
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.reactivecache2;
18 |
19 | import java.io.File;
20 | import java.security.InvalidParameterException;
21 | import org.junit.Rule;
22 | import org.junit.Test;
23 | import org.junit.rules.TemporaryFolder;
24 |
25 | /**
26 | * Created by daemontus on 02/09/16.
27 | */
28 | public class ReactiveCacheBuilderValidationTest {
29 |
30 | @Rule
31 | public TemporaryFolder temporaryFolder = new TemporaryFolder();
32 |
33 | @Test(expected = InvalidParameterException.class)
34 | public void Cache_Directory_Null() {
35 | new ReactiveCache.Builder()
36 | .using(null, Jolyglot$.newInstance());
37 |
38 | }
39 |
40 | @Test(expected = InvalidParameterException.class)
41 | public void Jolyglot_Null() {
42 | new ReactiveCache.Builder()
43 | .using(temporaryFolder.getRoot(), null);
44 | }
45 |
46 | @Test(expected = InvalidParameterException.class)
47 | public void Cache_Directory_Not_Exist() {
48 | File cacheDir = new File(temporaryFolder.getRoot(), "non_existent_folder");
49 | new ReactiveCache.Builder()
50 | .using(cacheDir, Jolyglot$.newInstance());
51 | }
52 |
53 | @Test(expected = InvalidParameterException.class)
54 | public void Cache_Directory_Not_Writable() {
55 | File cacheDir = new File(temporaryFolder.getRoot(), "non_existent_folder");
56 | if (!cacheDir.mkdirs()) {
57 | throw new IllegalStateException("Cannot create temporary directory");
58 | }
59 | if (!cacheDir.setWritable(false, false)) {
60 | throw new IllegalStateException("Cannot modify permissions");
61 | }
62 | new ReactiveCache.Builder()
63 | .using(cacheDir, Jolyglot$.newInstance());
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/reactive_cache/src/test/java/io/reactivecache2/ReactiveCacheTest.java:
--------------------------------------------------------------------------------
1 | package io.reactivecache2;
2 |
3 | import io.reactivex.Single;
4 | import io.reactivex.observers.TestObserver;
5 | import org.junit.Before;
6 | import org.junit.Rule;
7 | import org.junit.Test;
8 | import org.junit.rules.TemporaryFolder;
9 |
10 | import static org.hamcrest.core.Is.is;
11 | import static org.junit.Assert.assertThat;
12 |
13 | public final class ReactiveCacheTest {
14 | @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder();
15 | private ReactiveCache reactiveCache;
16 |
17 | @Before public void setUp() {
18 | reactiveCache = new ReactiveCache.Builder()
19 | .using(temporaryFolder.getRoot(), Jolyglot$.newInstance());
20 | }
21 |
22 | @Test public void Verify_Evict_All() {
23 | assertThat(temporaryFolder.getRoot().listFiles().length, is(0));
24 |
25 | Provider provider1 = reactiveCache.provider()
26 | .withKey("1");
27 |
28 | ProviderGroup provider2 = reactiveCache.providerGroup()
29 | .withKey("2");
30 |
31 | Single.just(new Mock())
32 | .compose(provider1.replace())
33 | .test()
34 | .awaitTerminalEvent();
35 |
36 | for (int i = 0; i < 50; i++) {
37 | Single.just(new Mock())
38 | .compose(provider2.replace(i))
39 | .test()
40 | .awaitTerminalEvent();
41 | }
42 |
43 | assertThat(temporaryFolder.getRoot().listFiles().length, is(51));
44 |
45 | TestObserver observer = reactiveCache.evictAll().test();
46 | observer.awaitTerminalEvent();
47 |
48 | observer.assertComplete();
49 | observer.assertNoErrors();
50 | observer.assertNoValues();
51 |
52 | assertThat(temporaryFolder.getRoot().listFiles().length, is(0));
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/reactive_cache/src/test/java/io/reactivecache2/UsageTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Victor Albertos
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.reactivecache2;
18 |
19 | import io.reactivex.Completable;
20 | import io.reactivex.Single;
21 | import io.reactivex.observers.TestObserver;
22 | import io.rx_cache2.Reply;
23 | import io.rx_cache2.Source;
24 | import java.util.ArrayList;
25 | import java.util.Arrays;
26 | import java.util.HashMap;
27 | import java.util.List;
28 | import org.junit.Before;
29 | import org.junit.Rule;
30 | import org.junit.Test;
31 | import org.junit.rules.TemporaryFolder;
32 |
33 | import static org.hamcrest.core.Is.is;
34 | import static org.junit.Assert.assertNotNull;
35 | import static org.junit.Assert.assertThat;
36 |
37 | public final class UsageTest {
38 | @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder();
39 | private ReactiveCache reactiveCache;
40 |
41 | @Before public void setUp() {
42 | reactiveCache = new ReactiveCache.Builder()
43 | .using(temporaryFolder.getRoot(), Jolyglot$.newInstance());
44 | }
45 |
46 | @Test public void Verify_User() {
47 | UserRepository userRepository = new UserRepository(new ApiUser(), reactiveCache);
48 |
49 | TestObserver observerIsLogged = userRepository.isLogged().test();
50 | observerIsLogged.awaitTerminalEvent();
51 |
52 | assertThat(observerIsLogged.values().get(0), is(false));
53 | observerIsLogged.assertValueCount(1);
54 | observerIsLogged.assertNoErrors();
55 | observerIsLogged.assertComplete();
56 |
57 | userRepository.login("")
58 | .test()
59 | .awaitTerminalEvent();
60 |
61 | observerIsLogged = userRepository.isLogged().test();
62 | observerIsLogged.awaitTerminalEvent();
63 |
64 | assertThat(observerIsLogged.values().get(0),
65 | is(true));
66 |
67 | TestObserver observerProfile = userRepository.profile().test();
68 | observerProfile.awaitTerminalEvent();
69 |
70 | observerProfile.assertNoErrors();
71 | observerProfile.assertValueCount(1);
72 | observerProfile.assertComplete();
73 | assertNotNull(observerProfile.values().get(0));
74 |
75 | TestObserver observerUpdate = userRepository.updateUserName("aNewName").test();
76 | observerUpdate.awaitTerminalEvent();
77 |
78 | observerUpdate.assertComplete();
79 | observerUpdate.assertNoErrors();
80 | observerUpdate.assertNoValues();
81 |
82 | observerProfile = userRepository.profile().test();
83 | observerProfile.awaitTerminalEvent();
84 |
85 | observerProfile.assertNoErrors();
86 | observerProfile.assertValueCount(1);
87 | observerProfile.assertComplete();
88 | assertNotNull(observerProfile.values().get(0).getName(),
89 | is("aNewName"));
90 |
91 | TestObserver observerLogout = userRepository.logout().test();
92 | observerLogout.awaitTerminalEvent();
93 |
94 | observerLogout.assertNoErrors();
95 | observerLogout.assertNoValues();
96 | observerLogout.assertComplete();
97 |
98 | observerIsLogged = userRepository.isLogged().test();
99 | observerIsLogged.awaitTerminalEvent();
100 |
101 | assertThat(observerIsLogged.values().get(0), is(false));
102 | }
103 |
104 | @Test public void Verify_Tasks() {
105 | TasksRepository tasksRepository = new TasksRepository(new ApiTasks(), reactiveCache);
106 |
107 | TestObserver>> observerTasks = tasksRepository.tasks(false).test();
108 | observerTasks.awaitTerminalEvent();
109 |
110 | observerTasks.assertComplete();
111 | observerTasks.assertNoErrors();
112 | observerTasks.assertValueCount(1);
113 | assertThat(observerTasks.values().get(0).getSource(),
114 | is(Source.CLOUD));
115 |
116 | observerTasks = tasksRepository.tasks(false).test();
117 | observerTasks.awaitTerminalEvent();
118 |
119 | observerTasks.assertComplete();
120 | observerTasks.assertNoErrors();
121 | observerTasks.assertValueCount(1);
122 | assertThat(observerTasks.values().get(0).getSource(),
123 | is(Source.MEMORY));
124 |
125 | observerTasks = tasksRepository.tasks(true).test();
126 | observerTasks.awaitTerminalEvent();
127 |
128 | observerTasks.assertComplete();
129 | observerTasks.assertNoErrors();
130 | observerTasks.assertValueCount(1);
131 | assertThat(observerTasks.values().get(0).getSource(),
132 | is(Source.CLOUD));
133 | assertThat(observerTasks.values().get(0).getData().size(),
134 | is(0));
135 |
136 | TestObserver observerAddTask = tasksRepository.addTask("", "").test();
137 | observerAddTask.awaitTerminalEvent();
138 |
139 | observerAddTask.assertComplete();
140 | observerAddTask.assertNoErrors();
141 | observerAddTask.assertNoValues();
142 |
143 | observerTasks = tasksRepository.tasks(false).test();
144 | observerTasks.awaitTerminalEvent();
145 |
146 | observerTasks.assertComplete();
147 | observerTasks.assertNoErrors();
148 | observerTasks.assertValueCount(1);
149 | assertThat(observerTasks.values().get(0).getSource(), is(Source.MEMORY));
150 | assertThat(observerTasks.values().get(0).getData().size(), is(1));
151 |
152 | TestObserver observerRemoveTask = tasksRepository.removeTask(1).test();
153 |
154 | observerRemoveTask.assertComplete();
155 | observerRemoveTask.assertNoErrors();
156 | observerRemoveTask.assertNoValues();
157 |
158 | observerTasks = tasksRepository.tasks(false).test();
159 | observerTasks.awaitTerminalEvent();
160 |
161 | observerTasks.assertComplete();
162 | observerTasks.assertNoErrors();
163 | observerTasks.assertValueCount(1);
164 | assertThat(observerTasks.values().get(0).getSource(), is(Source.MEMORY));
165 | assertThat(observerTasks.values().get(0).getData().size(), is(0));
166 |
167 | observerTasks = tasksRepository.tasks(true).test();
168 | observerTasks.awaitTerminalEvent();
169 |
170 | observerTasks.assertComplete();
171 | observerTasks.assertNoErrors();
172 | observerTasks.assertValueCount(1);
173 | assertThat(observerTasks.values().get(0).getSource(), is(Source.CLOUD));
174 | assertThat(observerTasks.values().get(0).getData().size(), is(0));
175 | }
176 |
177 | @Test public void Verify_Events() {
178 | EventsRepository eventsRepository = new EventsRepository(new ApiEvents(), reactiveCache);
179 | TestObserver>> observerEvents = eventsRepository.events(false, 1).test();
180 | observerEvents.awaitTerminalEvent();
181 |
182 | observerEvents.assertComplete();
183 | observerEvents.assertNoErrors();
184 | observerEvents.assertValueCount(1);
185 |
186 | assertThat(observerEvents.values().get(0).getData().size(), is(1));
187 | assertThat(observerEvents.values().get(0).getSource(), is(Source.CLOUD));
188 |
189 | observerEvents = eventsRepository.events(false, 2).test();
190 | observerEvents.awaitTerminalEvent();
191 |
192 | observerEvents.assertComplete();
193 | observerEvents.assertNoErrors();
194 | observerEvents.assertValueCount(1);
195 |
196 | assertThat(observerEvents.values().get(0).getData().size(), is(2));
197 | assertThat(observerEvents.values().get(0).getSource(), is(Source.CLOUD));
198 |
199 | observerEvents = eventsRepository.events(false, 1).test();
200 | observerEvents.awaitTerminalEvent();
201 |
202 | observerEvents.assertComplete();
203 | observerEvents.assertNoErrors();
204 | observerEvents.assertValueCount(1);
205 |
206 | assertThat(observerEvents.values().get(0).getData().size(), is(1));
207 | assertThat(observerEvents.values().get(0).getSource(), is(Source.MEMORY));
208 |
209 | observerEvents = eventsRepository.events(false, 2).test();
210 | observerEvents.awaitTerminalEvent();
211 |
212 | observerEvents.assertComplete();
213 | observerEvents.assertNoErrors();
214 | observerEvents.assertValueCount(1);
215 |
216 | assertThat(observerEvents.values().get(0).getData().size(), is(2));
217 | assertThat(observerEvents.values().get(0).getSource(), is(Source.MEMORY));
218 | }
219 |
220 | /**
221 | * Managing user session.
222 | */
223 | private static class UserRepository {
224 | private final Provider cacheProvider;
225 | private final ApiUser api;
226 |
227 | private UserRepository(ApiUser api, ReactiveCache reactiveCache) {
228 | this.api = api;
229 | this.cacheProvider = reactiveCache.provider()
230 | .withKey("user");
231 | }
232 |
233 | Single login(String email) {
234 | return api.loginUser(email)
235 | .compose(cacheProvider.replace());
236 | }
237 |
238 | Single isLogged() {
239 | return cacheProvider.read()
240 | .map(user -> true)
241 | .onErrorReturn(observer -> false);
242 | }
243 |
244 | Single profile() {
245 | return cacheProvider.read();
246 | }
247 |
248 | Completable updateUserName(String name) {
249 | return cacheProvider.read()
250 | .map(user -> {
251 | user.setName(name);
252 | return user;
253 | })
254 | .compose(cacheProvider.replace())
255 | .toCompletable();
256 | }
257 |
258 | Completable logout() {
259 | return api.logout().andThen(cacheProvider.evict());
260 | }
261 | }
262 |
263 | /**
264 | * Managing tasks.
265 | */
266 | private static class TasksRepository {
267 | private final ProviderList cacheProvider;
268 | private final ApiTasks api;
269 |
270 | private TasksRepository(ApiTasks api, ReactiveCache reactiveCache) {
271 | this.api = api;
272 | this.cacheProvider = reactiveCache.providerList()
273 | .withKey("tasks");
274 | }
275 |
276 | Single>> tasks(boolean refresh) {
277 | return refresh ? api.tasks().compose(cacheProvider.replaceAsReply())
278 | : api.tasks().compose(cacheProvider.readWithLoaderAsReply());
279 | }
280 |
281 | Completable addTask(String name, String desc) {
282 | return api.addTask(1, name, desc)
283 | .andThen(cacheProvider.entries()
284 | .addFirst(new Task(1)));
285 | }
286 |
287 | Completable removeTask(int id) {
288 | return api.removeTask(id)
289 | .andThen(cacheProvider.entries()
290 | .evict((position, count, element) -> element.getId() == id));
291 | }
292 | }
293 |
294 | /**
295 | * Managing events feed with pagination.
296 | */
297 | private static class EventsRepository {
298 | private final ProviderGroup> cacheProvider;
299 | private final ApiEvents apiEvents;
300 |
301 | private EventsRepository(ApiEvents apiEvents, ReactiveCache reactiveCache) {
302 | this.apiEvents = apiEvents;
303 | this.cacheProvider = reactiveCache.>providerGroup()
304 | .withKey("events");
305 | }
306 |
307 | Single>> events(boolean refresh, int page) {
308 | if (refresh) {
309 | return apiEvents.events(page)
310 | .compose(cacheProvider.replaceAsReply(page));
311 | }
312 |
313 | return apiEvents.events(page)
314 | .compose(cacheProvider.readWithLoaderAsReply(page));
315 | }
316 | }
317 |
318 | private static class User {
319 | String name;
320 |
321 | void setName(String name) {
322 | this.name = name;
323 | }
324 |
325 | String getName() {
326 | return name;
327 | }
328 | }
329 |
330 | private static class Task {
331 | private final int id;
332 |
333 | public Task(int id) {
334 | this.id = id;
335 | }
336 |
337 | public int getId() {
338 | return id;
339 | }
340 | }
341 |
342 | private static class Event {
343 |
344 | }
345 |
346 | private static class ApiUser {
347 |
348 | public Single loginUser(String email) {
349 | return Single.just(new User());
350 | }
351 |
352 | public Completable logout() {
353 | return Completable.complete();
354 | }
355 | }
356 |
357 | private static class ApiTasks {
358 | private final List tasks;
359 |
360 | public ApiTasks() {
361 | this.tasks = new ArrayList<>();
362 | }
363 |
364 | public Single> tasks() {
365 | return Single.just(new ArrayList<>(tasks));
366 | }
367 |
368 | public Completable addTask(int id, String name, String desc) {
369 | Task task = new Task(id);
370 | tasks.add(task);
371 | return Completable.complete();
372 | }
373 |
374 | public Completable removeTask(int id) {
375 | Task candidate = null;
376 | for (Task task : tasks) {
377 | if (task.getId() == id) candidate = task;
378 | }
379 | tasks.remove(candidate);
380 | return Completable.complete();
381 | }
382 | }
383 |
384 | private static class ApiEvents {
385 | private final HashMap> events = new HashMap>() {{
386 | put(1, Arrays.asList(new Event()));
387 | put(2, Arrays.asList(new Event(), new Event()));
388 | }};
389 |
390 | public Single> events(int page) {
391 | return Single.just(events.get(page));
392 | }
393 | }
394 | }
395 |
--------------------------------------------------------------------------------
/reactive_cache/src/test/java/io/reactivecache2/common/BaseTestEvictingTask.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Victor Albertos
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.reactivecache2.common;
18 |
19 | import io.reactivecache2.Mock;
20 | import io.reactivex.Single;
21 | import java.io.File;
22 | import java.util.ArrayList;
23 | import java.util.List;
24 |
25 | public class BaseTestEvictingTask {
26 |
27 | protected void waitTime(long millis) {
28 | try {
29 | Thread.sleep(millis);
30 | } catch (InterruptedException e) {
31 | e.printStackTrace();
32 | }
33 | }
34 |
35 | protected int getSizeMB(File dir) {
36 | long bytes = 0;
37 |
38 | File[] files = dir.listFiles();
39 | for (File file: files) {
40 | bytes += file.length();
41 | }
42 |
43 | return (int) Math.ceil(bytes/1024.0/1024.0);
44 | }
45 |
46 | protected Single> createObservableMocks() {
47 | List mocks = new ArrayList();
48 |
49 | for (int i = 0; i < 100; i++) {
50 | Mock mock = new Mock("Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC," +
51 | "making it over 2000 years old.Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, " +
52 | "making it over 2000 years old. Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, " +
53 | "making it over 2000 years old. Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC."+
54 | "Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC," +
55 | "making it over 2000 years old.Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, " +
56 | "making it over 2000 years old. Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, " +
57 | "making it over 2000 years old. Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC." +
58 | "Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC," +
59 | "making it over 2000 years old.Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, " +
60 | "making it over 2000 years old. Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, " +
61 | "making it over 2000 years old. Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC."+
62 | "Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC," +
63 | "making it over 2000 years old.Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, " +
64 | "making it over 2000 years old. Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, " +
65 | "making it over 2000 years old. Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC."+
66 | "Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC," +
67 | "making it over 2000 years old.Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, " +
68 | "making it over 2000 years old. Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, " +
69 | "making it over 2000 years old. Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC."+
70 | "Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC," +
71 | "making it over 2000 years old.Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, " +
72 | "making it over 2000 years old. Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, " +
73 | "making it over 2000 years old. Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC.");
74 | mocks.add(mock);
75 | }
76 |
77 | return Single.just(mocks);
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/reactive_cache/src/test/java/io/reactivecache2/encript/EncryptGroupTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Victor Albertos
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.reactivecache2.encript;
18 |
19 | import io.reactivecache2.Jolyglot$;
20 | import io.reactivecache2.Mock;
21 | import io.reactivecache2.ProviderGroup;
22 | import io.reactivecache2.ReactiveCache;
23 | import io.reactivex.Single;
24 | import io.reactivex.observers.TestObserver;
25 | import io.rx_cache2.Reply;
26 | import io.rx_cache2.Source;
27 | import java.util.ArrayList;
28 | import java.util.List;
29 | import org.junit.Before;
30 | import org.junit.ClassRule;
31 | import org.junit.FixMethodOrder;
32 | import org.junit.Test;
33 | import org.junit.rules.TemporaryFolder;
34 | import org.junit.runners.MethodSorters;
35 |
36 | import static org.hamcrest.CoreMatchers.is;
37 | import static org.junit.Assert.assertThat;
38 |
39 | @FixMethodOrder(MethodSorters.NAME_ASCENDING)
40 | public final class EncryptGroupTest {
41 | private final static int SIZE = 1000;
42 | @ClassRule public static TemporaryFolder temporaryFolder = new TemporaryFolder();
43 | private ProviderGroup> mocksEncryptedProvider;
44 | private ProviderGroup> mocksNoEncryptedProvider;
45 | private final static String GROUP = "GROUP";
46 |
47 | @Before public void init() {
48 | ReactiveCache reactiveCache = new ReactiveCache.Builder()
49 | .encrypt("myStrongKey-1234")
50 | .using(temporaryFolder.getRoot(), Jolyglot$.newInstance());
51 |
52 | mocksEncryptedProvider = reactiveCache.>providerGroup()
53 | .encrypt(true)
54 | .withKey("mocksEncryptedProvider");
55 | mocksNoEncryptedProvider = reactiveCache.>providerGroup()
56 | .withKey("mocksNoEncryptedProvider");
57 | }
58 |
59 | @Test public void _00_Save_Record_On_Disk_In_Order_To_Test_Following_Tests() {
60 | TestObserver>> observer = createSingleMocks(SIZE)
61 | .compose(mocksEncryptedProvider.readWithLoaderAsReply(GROUP))
62 | .test();
63 | observer.awaitTerminalEvent();
64 |
65 | observer = createSingleMocks(SIZE)
66 | .compose(mocksNoEncryptedProvider.readWithLoaderAsReply(GROUP))
67 | .test();
68 |
69 | observer.awaitTerminalEvent();
70 | }
71 |
72 | @Test public void _01_When_Encrypted_Record_Has_Been_Persisted_And_Memory_Has_Been_Destroyed_Then_Retrieve_From_Disk() {
73 | TestObserver>> observer = Single.>just(new ArrayList<>())
74 | .compose(mocksEncryptedProvider.readWithLoaderAsReply(GROUP))
75 | .test();
76 | observer.awaitTerminalEvent();
77 |
78 | Reply> reply = observer.values().get(0);
79 | assertThat(reply.getSource(), is(Source.PERSISTENCE));
80 | assertThat(reply.isEncrypted(), is(true));
81 | }
82 |
83 | @Test public void _02_Verify_Encrypted_Does_Not_Propagate_To_Other_Providers() {
84 | TestObserver>> observer = createSingleMocks(SIZE)
85 | .compose(mocksEncryptedProvider.readWithLoaderAsReply(GROUP))
86 | .test();
87 |
88 | observer.awaitTerminalEvent();
89 |
90 | Reply> reply = observer.values().get(0);
91 | assertThat(reply.getSource(), is(Source.PERSISTENCE));
92 | assertThat(reply.isEncrypted(), is(true));
93 |
94 | observer = createSingleMocks(SIZE)
95 | .compose(mocksNoEncryptedProvider.readWithLoaderAsReply(GROUP))
96 | .test();
97 |
98 | observer.awaitTerminalEvent();
99 |
100 | reply = observer.values().get(0);
101 | assertThat(reply.getSource(), is(Source.PERSISTENCE));
102 | assertThat(reply.isEncrypted(), is(false));
103 | }
104 |
105 |
106 | private Single> createSingleMocks(int size) {
107 | long currentTime = System.currentTimeMillis();
108 |
109 | List mocks = new ArrayList(size);
110 | for (int i = 0; i < size; i++) {
111 | mocks.add(new Mock("mock"+currentTime));
112 | }
113 |
114 | return Single.just(mocks);
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/reactive_cache/src/test/java/io/reactivecache2/encript/EncryptTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 Victor Albertos
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package io.reactivecache2.encript;
18 |
19 | import io.reactivecache2.Jolyglot$;
20 | import io.reactivecache2.Mock;
21 | import io.reactivecache2.Provider;
22 | import io.reactivecache2.ReactiveCache;
23 | import io.reactivex.Single;
24 | import io.reactivex.observers.TestObserver;
25 | import io.rx_cache2.Reply;
26 | import io.rx_cache2.Source;
27 | import java.util.ArrayList;
28 | import java.util.List;
29 | import org.junit.Before;
30 | import org.junit.ClassRule;
31 | import org.junit.FixMethodOrder;
32 | import org.junit.Test;
33 | import org.junit.rules.TemporaryFolder;
34 | import org.junit.runners.MethodSorters;
35 |
36 | import static org.hamcrest.CoreMatchers.is;
37 | import static org.junit.Assert.assertThat;
38 |
39 | @FixMethodOrder(MethodSorters.NAME_ASCENDING)
40 | public final class EncryptTest {
41 | private final static int SIZE = 1000;
42 | @ClassRule public static TemporaryFolder temporaryFolder = new TemporaryFolder();
43 | private Provider> mocksEncryptedProvider;
44 | private Provider