type of the data to store
35 | * @param type of the policy to store
36 | * @author pakoito
37 | */
38 | public class RxObservableDiskCache {
39 | private static final String POLICY_APPEND = "_policy";
40 |
41 | private final RxPaperBook book;
42 |
43 | private final Func1 policyCreator;
44 |
45 | private final Func1 policyValidator;
46 |
47 | RxObservableDiskCache(RxPaperBook book, Func1 policyCreator,
48 | Func1 policyValidator) {
49 | this.book = book;
50 | this.policyValidator = policyValidator;
51 | this.policyCreator = policyCreator;
52 | }
53 |
54 | /**
55 | * Creates a reusable {@link RxObservableDiskCache} for the same {@link RxPaperBook}, Policy and
56 | * Value types.
57 | *
58 | * @param book {@link RxPaperBook} storage book
59 | * @param policyCreator lazy method to construct a Policy object
60 | * @param policyValidator lazy method to validate a Policy object
61 | */
62 | public static RxObservableDiskCache create(
63 | RxPaperBook book, Func1 policyCreator, Func1 policyValidator) {
64 | return new RxObservableDiskCache<>(book, policyCreator, policyValidator);
65 | }
66 |
67 | /**
68 | * Transforms a {@link Single} into an {@link Observable} returning a disk cached version of the
69 | * latest Value seen for the same key followed by the {@link Single} result.
70 | *
71 | * The execution assures that the cached Value, if available, will be returned first. If no
72 | * Value is cached, or its Policy is not validated, then the current Value and Policy are
73 | * deleted silently and just the value of the {@link Single} is returned.
74 | *
75 | * This version uses a default {@link RxPaperBook}.
76 | *
77 | * @param single {@link Single} operation whose result is to be cached
78 | * @param key string value under where the values will be stored
79 | * @param policyCreator lazy method to construct a Policy object
80 | * @param policyValidator lazy method to validate a Policy object
81 | * @param type of the data to store
82 | * @param type of the policy to store
83 | * @return an {@link Observable} that will return a cached Value followed by the result of
84 | * executing single
85 | */
86 | public static Observable> transform(
87 | Single single, String key, Func1 policyCreator, Func1 policyValidator) {
88 | return transform(single, key, RxPaperBook.with(BuildConfig.APPLICATION_ID), policyCreator,
89 | policyValidator);
90 | }
91 |
92 | /**
93 | * Transforms a {@link Single} into an {@link Observable} returning a disk cached version of the
94 | * latest Value seen for the same key followed by the {@link Single} result.
95 | *
96 | * The execution assures that the cached Value, if available, will be returned first. If no
97 | * Value is cached, or its Policy is not validated, then the current Value and Policy are
98 | * deleted silently and just the value of the {@link Single} is returned.
99 | *
100 | * @param single {@link Single} operation whose result is to be cached
101 | * @param key string value under where the values will be stored
102 | * @param paperBook storage book
103 | * @param policyCreator lazy method to construct a Policy object
104 | * @param policyValidator lazy method to validate a Policy object
105 | * @param type of the data to store
106 | * @param type of the policy to store
107 | * @return an {@link Observable} that will return a cached Value followed by the result of
108 | * executing single
109 | */
110 | public static Observable> transform(
111 | final Single single, final String key, final RxPaperBook paperBook,
112 | final Func1 policyCreator, final Func1 policyValidator) {
113 | return Observable
114 | /* Errors require being delayed so the cached subscription is completed even if the remote one fails */
115 | .concatDelayError(
116 | RxObservableDiskCache.requestCachedValue(key, paperBook, policyValidator),
117 | RxObservableDiskCache.requestFreshValue(single, key, paperBook, policyCreator));
118 | }
119 |
120 | private static Observable> requestCachedValue(
121 | final String key, final RxPaperBook cache, Func1 policyValidator) {
122 | return cache
123 | .
read(composePolicyKey(key))
124 | .toObservable()
125 | .filter(policyValidator)
126 | .switchIfEmpty(
127 | RxObservableDiskCache.
deleteValueAndPolicy(key, cache)
128 | .doOnCompleted(Logging.logCacheInvalid(key)))
129 | .flatMap(RxObservableDiskCache. readValue(key, cache))
130 | .doOnNext(Logging. logCacheHit(key))
131 | .doOnError(Logging. logCacheMiss(key))
132 | .onErrorResumeNext(RxObservableDiskCache. handleErrors(key, cache));
133 | }
134 |
135 | private static Observable
deleteValueAndPolicy(String key, RxPaperBook cache) {
136 | return Completable.mergeDelayError(cache.delete(key), cache.delete(composePolicyKey(key)))
137 | .toObservable();
138 | }
139 |
140 | private static Func1>> readValue(
141 | final String key, final RxPaperBook cache) {
142 | return new Func1
>>() {
143 | @Override
144 | public Observable> call(final P policy) {
145 | return cache. read(key)
146 | .map(RxObservableDiskCache. createDiskCached(policy))
147 | .toObservable();
148 | }
149 | };
150 | }
151 |
152 | private static Func1>> handleErrors(
153 | final String key, final RxPaperBook cache) {
154 | return new Func1>>() {
155 | @Override
156 | public Observable> call(final Throwable throwable) {
157 | return RxObservableDiskCache.> deleteValueAndPolicy(key, cache)
158 | .flatMap(
159 | new Func1, Observable>>() {
160 | @Override
161 | public Observable> call(
162 | Cached valuePolicyCached) {
163 | return Observable.error(throwable);
164 | }
165 | });
166 | }
167 | };
168 | }
169 |
170 | private static Observable> requestFreshValue(
171 | Single single, String key, RxPaperBook cache, Func1 policyCreator) {
172 | return single.toObservable()
173 | .map(createObservableCached(policyCreator))
174 | .flatMap(RxObservableDiskCache. toStoreKeyAndValue(key, cache));
175 | }
176 |
177 | private static Func1, Observable>> toStoreKeyAndValue(
178 | final String key, final RxPaperBook cache) {
179 | return new Func1, Observable>>() {
180 | @Override
181 | public Observable> call(final Cached ktCached) {
182 | return Completable
183 | .mergeDelayError(
184 | cache.write(key, ktCached.value),
185 | cache.write(composePolicyKey(key), ktCached.policy))
186 | .andThen(Observable.just(ktCached));
187 | }
188 | };
189 | }
190 |
191 | private static String composePolicyKey(String key) {
192 | return key + POLICY_APPEND;
193 | }
194 |
195 | private static Func1> createDiskCached(
196 | final P policy) {
197 | return new Func1>() {
198 | @Override
199 | public Cached call(V value) {
200 | return new Cached<>(value, policy, true);
201 | }
202 | };
203 | }
204 |
205 | private static Func1> createObservableCached(
206 | final Func1 policyCreator) {
207 | return new Func1>() {
208 | @Override
209 | public Cached call(V value) {
210 | return new Cached<>(value, policyCreator.call(value), false);
211 | }
212 | };
213 | }
214 |
215 | /**
216 | * Transforms a {@link Single} into an {@link Observable} returning a disk cached version of the
217 | * latest Value seen for the same key followed by the {@link Single} result.
218 | *
219 | * The execution assures that the cached Value, if available, will be returned first. If no
220 | * Value is cached, or its Policy is not validated, then the current Value and Policy are
221 | * deleted silently and just the value of the {@link Single} is returned.
222 | *
223 | * @param single {@link Single} operation whose result is to be cached
224 | * @param key string value under where the values will be stored
225 | * @return an {@link Observable} that will return a cached Value followed by the result of
226 | * executing single
227 | */
228 | public Observable> transform(Single single, String key) {
229 | return transform(single, key, book, policyCreator, policyValidator);
230 | }
231 | }
232 |
--------------------------------------------------------------------------------