com.netflix.hystrix.collapser.CollapsedRequestSubject Maven / Gradle / Ivy
Show all versions of hystrix-core Show documentation
/**
* Copyright 2015 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.hystrix.collapser;
import com.netflix.hystrix.HystrixCollapser.CollapsedRequest;
import rx.Observable;
import rx.functions.Action0;
import rx.subjects.ReplaySubject;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* The Observable that represents a collapsed request sent back to a user. It gets used by Collapser implementations
* when receiving a batch response and emitting values/errors to collapsers.
*
* There are 4 methods that Collapser implementations may use:
*
* 1) {@link #setResponse(T)}: return a single-valued response. equivalent to OnNext(T), OnCompleted()
* 2) {@link #emitResponse(T)}: emit a single value. equivalent to OnNext(T)
* 3) {@link #setException(Exception)}: return an exception. equivalent to OnError(Exception)
* 4) {@link #setComplete()}: mark that no more values will be emitted. Should be used in conjunction with {@link #emitResponse(T)}. equivalent to OnCompleted()
*
*
* This is an internal implementation of CollapsedRequest functionality. Instead of directly extending {@link rx.Observable},
* it provides a {@link #toObservable()} method
*
*
* @param
*
* @param
*/
/* package */class CollapsedRequestSubject implements CollapsedRequest {
private final R argument;
private AtomicBoolean valueSet = new AtomicBoolean(false);
private final ReplaySubject subject = ReplaySubject.create();
private final Observable subjectWithAccounting;
private volatile int outstandingSubscriptions = 0;
public CollapsedRequestSubject(final R arg, final RequestBatch, T, R> containingBatch) {
if (arg == RequestCollapser.NULL_SENTINEL) {
this.argument = null;
} else {
this.argument = arg;
}
this.subjectWithAccounting = subject
.doOnSubscribe(new Action0() {
@Override
public void call() {
outstandingSubscriptions++;
}
})
.doOnUnsubscribe(new Action0() {
@Override
public void call() {
outstandingSubscriptions--;
if (outstandingSubscriptions == 0) {
containingBatch.remove(arg);
}
}
});
}
public CollapsedRequestSubject(final R arg) {
this.subjectWithAccounting = subject;
this.argument = arg;
}
/**
* The request argument.
*
* @return request argument
*/
@Override
public R getArgument() {
return argument;
}
/**
* When set any client thread blocking on get() will immediately be unblocked and receive the single-valued response.
*
* @throws IllegalStateException
* if called more than once or after setException.
* @param response response to give to initial command
*/
@Override
public void setResponse(T response) {
if (!isTerminated()) {
subject.onNext(response);
valueSet.set(true);
subject.onCompleted();
} else {
throw new IllegalStateException("Response has already terminated so response can not be set : " + response);
}
}
/**
* Emit a response that should be OnNexted to an Observer
* @param response response to emit to initial command
*/
@Override
public void emitResponse(T response) {
if (!isTerminated()) {
subject.onNext(response);
valueSet.set(true);
} else {
throw new IllegalStateException("Response has already terminated so response can not be set : " + response);
}
}
@Override
public void setComplete() {
if (!isTerminated()) {
subject.onCompleted();
}
}
/**
* Set an exception if a response is not yet received otherwise skip it
*
* @param e synthetic error to set on initial command when no actual response is available
*/
public void setExceptionIfResponseNotReceived(Exception e) {
if (!valueSet.get() && !isTerminated()) {
subject.onError(e);
}
}
/**
* Set an ISE if a response is not yet received otherwise skip it
*
* @param e A pre-generated exception. If this is null an ISE will be created and returned
* @param exceptionMessage The message for the ISE
*/
public Exception setExceptionIfResponseNotReceived(Exception e, String exceptionMessage) {
Exception exception = e;
if (!valueSet.get() && !isTerminated()) {
if (e == null) {
exception = new IllegalStateException(exceptionMessage);
}
setExceptionIfResponseNotReceived(exception);
}
// return any exception that was generated
return exception;
}
/**
* When set any client thread blocking on get() will immediately be unblocked and receive the exception.
*
* @throws IllegalStateException
* if called more than once or after setResponse.
* @param e received exception that gets set on the initial command
*/
@Override
public void setException(Exception e) {
if (!isTerminated()) {
subject.onError(e);
} else {
throw new IllegalStateException("Response has already terminated so exception can not be set", e);
}
}
private boolean isTerminated() {
return (subject.hasCompleted() || subject.hasThrowable());
}
public Observable toObservable() {
return subjectWithAccounting;
}
}