/*
* Copyright 2015 Victor Albertos
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.rx_cache2.internal;
import io.reactivex.Completable;
import io.reactivex.Observable;
import io.reactivex.ObservableSource;
import io.reactivex.functions.Consumer;
import io.reactivex.functions.Function;
import io.reactivex.schedulers.Schedulers;
import io.rx_cache2.EvictDynamicKey;
import io.rx_cache2.EvictDynamicKeyGroup;
import io.rx_cache2.Reply;
import io.rx_cache2.Source;
import io.rx_cache2.internal.cache.GetDeepCopy;
import java.util.concurrent.Callable;
import javax.inject.Inject;
public final class ProcessorProvidersBehaviour implements ProcessorProviders {
private final io.rx_cache2.internal.cache.TwoLayersCache twoLayersCache;
private final Boolean useExpiredDataIfLoaderNotAvailable;
private final GetDeepCopy getDeepCopy;
private final Observable<Integer> oProcesses;
private volatile Boolean hasProcessesEnded;
@Inject public ProcessorProvidersBehaviour(
io.rx_cache2.internal.cache.TwoLayersCache twoLayersCache,
Boolean useExpiredDataIfLoaderNotAvailable,
io.rx_cache2.internal.cache.EvictExpiredRecordsPersistence evictExpiredRecordsPersistence,
GetDeepCopy getDeepCopy, io.rx_cache2.internal.migration.DoMigrations doMigrations) {
this.hasProcessesEnded = false;
this.twoLayersCache = twoLayersCache;
this.useExpiredDataIfLoaderNotAvailable = useExpiredDataIfLoaderNotAvailable;
this.getDeepCopy = getDeepCopy;
this.oProcesses = startProcesses(doMigrations, evictExpiredRecordsPersistence);
}
private Observable<Integer> startProcesses(
io.rx_cache2.internal.migration.DoMigrations doMigrations,
final io.rx_cache2.internal.cache.EvictExpiredRecordsPersistence evictExpiredRecordsPersistence) {
Observable<Integer> oProcesses = doMigrations.react().flatMap(new Function<Integer, ObservableSource<Integer>>() {
@Override public ObservableSource<Integer> apply(Integer ignore) throws Exception {
return evictExpiredRecordsPersistence.startEvictingExpiredRecords();
}
}).subscribeOn((Schedulers.io())).observeOn(Schedulers.io()).share();
oProcesses.subscribe(new Consumer<Integer>() {
@Override public void accept(Integer ignore) throws Exception {
hasProcessesEnded = true;
}
});
return oProcesses;
}
@Override
public <T> Observable<T> process(final io.rx_cache2.ConfigProvider configProvider) {
return Observable.defer(new Callable<ObservableSource<? extends T>>() {
@Override public ObservableSource<? extends T> call() throws Exception {
if (hasProcessesEnded) {
return getData(configProvider);
}
return oProcesses.flatMap(new Function<Integer, ObservableSource<? extends T>>() {
@Override public ObservableSource<? extends T> apply(Integer ignore) throws Exception {
return getData(configProvider);
}
});
}
});
}
//VisibleForTesting
<T> Observable<T> getData(final io.rx_cache2.ConfigProvider configProvider) {
Record<Object> record = twoLayersCache.retrieve(configProvider.getProviderKey(), configProvider.getDynamicKey(),
configProvider.getDynamicKeyGroup(), useExpiredDataIfLoaderNotAvailable,
configProvider.getLifeTimeMillis(), configProvider.isEncrypted());
Observable<Reply> replyObservable;
if (record != null && !configProvider.evictProvider().evict()) {
replyObservable = Observable.just(new Reply(record.getData(), record.getSource(), configProvider.isEncrypted()));
} else {
replyObservable = getDataFromLoader(configProvider, record);
}
return (Observable<T>) replyObservable.map(new Function<Reply, Object>() {
@Override public Object apply(Reply reply) throws Exception {
return ProcessorProvidersBehaviour.this.getReturnType(configProvider, reply);
}
});
}
private Observable<Reply> getDataFromLoader(final io.rx_cache2.ConfigProvider configProvider,
final Record record) {
return configProvider.getLoaderObservable().map(new Function<Object, Reply>() {
@Override public Reply apply(Object data) throws Exception {
boolean useExpiredData = configProvider.useExpiredDataIfNotLoaderAvailable() != null ?
configProvider.useExpiredDataIfNotLoaderAvailable()
: useExpiredDataIfLoaderNotAvailable;
if (data == null && useExpiredData && record != null) {
return new Reply(record.getData(), record.getSource(), configProvider.isEncrypted());
}
clearKeyIfNeeded(configProvider);
if (data == null) {
throw new io.rx_cache2.RxCacheException(io.rx_cache2.internal.Locale.NOT_DATA_RETURN_WHEN_CALLING_OBSERVABLE_LOADER
+ " "
+ configProvider.getProviderKey());
}
twoLayersCache.save(configProvider.getProviderKey(), configProvider.getDynamicKey(),
configProvider.getDynamicKeyGroup(), data, configProvider.getLifeTimeMillis(),
configProvider.isExpirable(), configProvider.isEncrypted());
return new Reply(data, Source.CLOUD, configProvider.isEncrypted());
}
}).onErrorReturn(new Function<Object, Object>() {
@Override public Object apply(Object o) throws Exception {
clearKeyIfNeeded(configProvider);
boolean useExpiredData = configProvider.useExpiredDataIfNotLoaderAvailable() != null ?
configProvider.useExpiredDataIfNotLoaderAvailable()
: useExpiredDataIfLoaderNotAvailable;
if (useExpiredData && record != null) {
return new Reply(record.getData(), record.getSource(), configProvider.isEncrypted());
}
throw new io.rx_cache2.RxCacheException(io.rx_cache2.internal.Locale.NOT_DATA_RETURN_WHEN_CALLING_OBSERVABLE_LOADER
+ " "
+ configProvider.getProviderKey(), (Throwable) o);
}
});
}
private void clearKeyIfNeeded(io.rx_cache2.ConfigProvider configProvider) {
if (!configProvider.evictProvider().evict()) return;
if (configProvider.evictProvider() instanceof EvictDynamicKeyGroup) {
twoLayersCache.evictDynamicKeyGroup(configProvider.getProviderKey(),
configProvider.getDynamicKey().toString(),
configProvider.getDynamicKeyGroup().toString());
} else if (configProvider.evictProvider() instanceof EvictDynamicKey) {
twoLayersCache.evictDynamicKey(configProvider.getProviderKey(),
configProvider.getDynamicKey().toString());
} else {
twoLayersCache.evictProviderKey(configProvider.getProviderKey());
}
}
private Object getReturnType(io.rx_cache2.ConfigProvider configProvider, Reply reply) {
Object data = getDeepCopy.deepCopy(reply.getData());
if (configProvider.requiredDetailedResponse()) {
return new Reply<>(data, reply.getSource(), configProvider.isEncrypted());
} else {
return data;
}
}
@Override public Observable<Void> evictAll() {
return Observable.defer(new Callable<ObservableSource<Void>>() {
@Override public ObservableSource<Void> call() throws Exception {
ProcessorProvidersBehaviour.this.twoLayersCache.evictAll();
return Completable.complete().toObservable();
}
});
}
}