2015-12-05 21:41:50 +08:00
|
|
|
/**
|
|
|
|
|
* Copyright 2013 Netflix, Inc.
|
2016-01-01 11:03:09 +08:00
|
|
|
*
|
2015-12-05 21:41:50 +08:00
|
|
|
* 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
|
2016-01-01 11:03:09 +08:00
|
|
|
*
|
2015-12-05 21:41:50 +08:00
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
2016-01-01 11:03:09 +08:00
|
|
|
*
|
2015-12-05 21:41:50 +08:00
|
|
|
* 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 rx.operators;
|
|
|
|
|
|
|
|
|
|
import java.util.Arrays;
|
|
|
|
|
import java.util.concurrent.atomic.AtomicReference;
|
|
|
|
|
|
|
|
|
|
import rx.Observable;
|
|
|
|
|
import rx.Observer;
|
|
|
|
|
import rx.Subscription;
|
|
|
|
|
import rx.util.AtomicObservableSubscription;
|
|
|
|
|
import rx.util.CompositeException;
|
|
|
|
|
import rx.util.functions.Func1;
|
|
|
|
|
|
|
|
|
|
public final class OperationOnErrorResumeNextViaFunction<T> {
|
|
|
|
|
|
|
|
|
|
public static <T> Func1<Observer<T>, Subscription> onErrorResumeNextViaFunction(Observable<T> originalSequence, Func1<Exception, Observable<T>> resumeFunction) {
|
|
|
|
|
return new OnErrorResumeNextViaFunction<>(originalSequence, resumeFunction);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static class OnErrorResumeNextViaFunction<T> implements Func1<Observer<T>, Subscription> {
|
|
|
|
|
|
|
|
|
|
private final Func1<Exception, Observable<T>> resumeFunction;
|
|
|
|
|
private final Observable<T> originalSequence;
|
|
|
|
|
|
|
|
|
|
public OnErrorResumeNextViaFunction(Observable<T> originalSequence, Func1<Exception, Observable<T>> resumeFunction) {
|
|
|
|
|
this.resumeFunction = resumeFunction;
|
|
|
|
|
this.originalSequence = originalSequence;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public Subscription call(final Observer<T> observer) {
|
|
|
|
|
// AtomicReference since we'll be accessing/modifying this across threads so we can switch it if needed
|
|
|
|
|
final AtomicReference<AtomicObservableSubscription> subscriptionRef = new AtomicReference<>(new AtomicObservableSubscription());
|
|
|
|
|
|
|
|
|
|
// subscribe to the original Observable and remember the subscription
|
|
|
|
|
subscriptionRef.get().wrap(new AtomicObservableSubscription(originalSequence.subscribe(new Observer<T>() {
|
|
|
|
|
@Override
|
|
|
|
|
public void onNext(T value) {
|
|
|
|
|
// forward the successful calls
|
|
|
|
|
observer.onNext(value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2016-01-01 11:03:09 +08:00
|
|
|
* Instead of passing the onError forward, we intercept and
|
|
|
|
|
* "resume" with the resumeSequence.
|
2015-12-05 21:41:50 +08:00
|
|
|
*/
|
|
|
|
|
@Override
|
|
|
|
|
public void onError(Exception ex) {
|
2016-01-01 11:03:09 +08:00
|
|
|
/*
|
|
|
|
|
* remember what the current subscription is so we can
|
|
|
|
|
* determine if someone unsubscribes concurrently
|
|
|
|
|
*/
|
2015-12-05 21:41:50 +08:00
|
|
|
AtomicObservableSubscription currentSubscription = subscriptionRef.get();
|
|
|
|
|
// check that we have not been unsubscribed before we can process the error
|
2016-01-01 11:03:09 +08:00
|
|
|
if (currentSubscription != null)
|
2015-12-05 21:41:50 +08:00
|
|
|
try {
|
|
|
|
|
Observable<T> resumeSequence = resumeFunction.call(ex);
|
2016-01-01 11:03:09 +08:00
|
|
|
/*
|
|
|
|
|
* error occurred, so switch subscription to the
|
|
|
|
|
* 'resumeSequence'
|
|
|
|
|
*/
|
2015-12-05 21:41:50 +08:00
|
|
|
AtomicObservableSubscription innerSubscription = new AtomicObservableSubscription(resumeSequence.subscribe(observer));
|
2016-01-01 11:03:09 +08:00
|
|
|
/*
|
|
|
|
|
* we changed the sequence, so also change the
|
|
|
|
|
* subscription to the one of the 'resumeSequence'
|
|
|
|
|
* instead
|
|
|
|
|
*/
|
|
|
|
|
if (!subscriptionRef.compareAndSet(currentSubscription, innerSubscription))
|
2015-12-05 21:41:50 +08:00
|
|
|
// we failed to set which means 'subscriptionRef' was set to NULL via the unsubscribe below
|
|
|
|
|
// so we want to immediately unsubscribe from the resumeSequence we just subscribed to
|
|
|
|
|
innerSubscription.unsubscribe();
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
// the resume function failed so we need to call onError
|
|
|
|
|
// I am using CompositeException so that both exceptions can be seen
|
|
|
|
|
observer.onError(new CompositeException("OnErrorResume function failed", Arrays.asList(ex, e)));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void onCompleted() {
|
|
|
|
|
// forward the successful calls
|
|
|
|
|
observer.onCompleted();
|
|
|
|
|
}
|
|
|
|
|
})));
|
|
|
|
|
|
|
|
|
|
return () -> {
|
|
|
|
|
// this will get either the original, or the resumeSequence one and unsubscribe on it
|
|
|
|
|
Subscription s = subscriptionRef.getAndSet(null);
|
|
|
|
|
if (s != null)
|
|
|
|
|
s.unsubscribe();
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-01-01 11:03:09 +08:00
|
|
|
}
|