/*
* Copyright 2014 Netflix, Inc.
*
* 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 com.netflix.ribbon.http;
import com.netflix.hystrix.HystrixInvokableInfo;
import com.netflix.hystrix.HystrixObservableCommand;
import com.netflix.ribbon.RequestWithMetaData;
import com.netflix.ribbon.RibbonResponse;
import com.netflix.ribbon.hystrix.HystrixObservableCommandChain;
import com.netflix.ribbon.hystrix.ResultCommandPair;
import io.netty.buffer.ByteBuf;
import rx.Notification;
import rx.Observable;
import rx.Observable.OnSubscribe;
import rx.Subscriber;
import rx.functions.Action1;
import rx.functions.Func1;
import rx.subjects.ReplaySubject;
import rx.subjects.Subject;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
class HttpMetaRequest<T> implements RequestWithMetaData<T> {
private static class ResponseWithSubject<T> extends RibbonResponse<Observable<T>> {
Subject<T, T> subject;
HystrixInvokableInfo<?> info;
public ResponseWithSubject(Subject<T, T> subject,
HystrixInvokableInfo<?> info) {
this.subject = subject;
this.info = info;
}
@Override
public Observable<T> content() {
return subject;
}
@Override
public HystrixInvokableInfo<?> getHystrixInfo() {
return info;
}
}
private final HttpRequest<T> request;
HttpMetaRequest(HttpRequest<T> request) {
this.request = request;
}
@Override
public Observable<RibbonResponse<Observable<T>>> toObservable() {
HystrixObservableCommandChain<T> commandChain = request.createHystrixCommandChain();
return convertToRibbonResponse(commandChain, commandChain.toResultCommandPairObservable());
}
@Override
public Observable<RibbonResponse<Observable<T>>> observe() {
HystrixObservableCommandChain<T> commandChain = request.createHystrixCommandChain();
Observable<ResultCommandPair<T>> notificationObservable = commandChain.toResultCommandPairObservable();
notificationObservable = retainBufferIfNeeded(notificationObservable);
ReplaySubject<ResultCommandPair<T>> subject = ReplaySubject.create();
notificationObservable.subscribe(subject);
return convertToRibbonResponse(commandChain, subject);
}
@Override
public Future<RibbonResponse<T>> queue() {
Observable<ResultCommandPair<T>> resultObservable = request.createHystrixCommandChain().toResultCommandPairObservable();
resultObservable = retainBufferIfNeeded(resultObservable);
final Future<ResultCommandPair<T>> f = resultObservable.toBlocking().toFuture();
return new Future<RibbonResponse<T>>() {
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
return f.cancel(mayInterruptIfRunning);
}
@Override
public RibbonResponse<T> get() throws InterruptedException,
ExecutionException {
final ResultCommandPair<T> pair = f.get();
return new HttpMetaResponse<T>(pair.getResult(), pair.getCommand());
}
@Override
public RibbonResponse<T> get(long timeout, TimeUnit timeUnit)
throws InterruptedException, ExecutionException,
TimeoutException {
final ResultCommandPair<T> pair = f.get(timeout, timeUnit);
return new HttpMetaResponse<T>(pair.getResult(), pair.getCommand());
}
@Override
public boolean isCancelled() {
return f.isCancelled();
}
@Override
public boolean isDone() {
return f.isDone();
}
};
}
@Override
public RibbonResponse<T> execute() {
RibbonResponse<Observable<T>> response = observe().toBlocking().last();
return new HttpMetaResponse<T>(response.content().toBlocking().last(), response.getHystrixInfo());
}
private Observable<ResultCommandPair<T>> retainBufferIfNeeded(Observable<ResultCommandPair<T>> resultObservable) {
if (request.isByteBufResponse()) {
resultObservable = resultObservable.map(new Func1<ResultCommandPair<T>, ResultCommandPair<T>>() {
@Override
public ResultCommandPair<T> call(ResultCommandPair<T> pair) {
((ByteBuf) pair.getResult()).retain();
return pair;
}
});
}
return resultObservable;
}
private Observable<RibbonResponse<Observable<T>>> convertToRibbonResponse(
final HystrixObservableCommandChain<T> commandChain, final Observable<ResultCommandPair<T>> hystrixNotificationObservable) {
return Observable.create(new OnSubscribe<RibbonResponse<Observable<T>>>() {
@Override
public void call(
final Subscriber<? super RibbonResponse<Observable<T>>> t1) {
final Subject<T, T> subject = ReplaySubject.create();
hystrixNotificationObservable.materialize().subscribe(new Action1<Notification<ResultCommandPair<T>>>() {
AtomicBoolean first = new AtomicBoolean(true);
@Override
public void call(Notification<ResultCommandPair<T>> notification) {
if (first.compareAndSet(true, false)) {
HystrixObservableCommand<T> command = notification.isOnError() ? commandChain.getLastCommand() : notification.getValue().getCommand();
t1.onNext(new ResponseWithSubject<T>(subject, command));
t1.onCompleted();
}
if (notification.isOnNext()) {
subject.onNext(notification.getValue().getResult());
} else if (notification.isOnCompleted()) {
subject.onCompleted();
} else { // onError
subject.onError(notification.getThrowable());
}
}
});
}
});
}
}