/*
* Copyright 2016 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 io.reactivex.netty.client.loadbalancer;
import io.reactivex.netty.channel.Connection;
import io.reactivex.netty.client.ConnectionProvider;
import io.reactivex.netty.client.ConnectionProviderFactory;
import io.reactivex.netty.client.HostConnector;
import io.reactivex.netty.client.loadbalancer.HostCollector.HostUpdate;
import io.reactivex.netty.client.loadbalancer.HostCollector.HostUpdate.Action;
import io.reactivex.netty.internal.VoidToAnythingCast;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rx.Observable;
import rx.Single;
import rx.functions.Action1;
import rx.functions.Func1;
import java.util.List;
public class LoadBalancerFactory<W, R> implements ConnectionProviderFactory<W, R> {
private static final Logger logger = LoggerFactory.getLogger(LoadBalancerFactory.class);
private final LoadBalancingStrategy<W, R> strategy;
private final HostCollector collector;
private LoadBalancerFactory(LoadBalancingStrategy<W, R> strategy, HostCollector collector) {
this.strategy = strategy;
this.collector = collector;
}
@Override
public ConnectionProvider<W, R> newProvider(Observable<HostConnector<W, R>> hosts) {
return new ConnectionProviderImpl(hosts.map(new Func1<HostConnector<W, R>, HostHolder<W, R>>() {
@Override
public HostHolder<W, R> call(HostConnector<W, R> connector) {
HostHolder<W, R> newHolder = strategy.toHolder(connector);
connector.subscribe(newHolder.getEventListener());
return newHolder;
}
}).flatMap(new Func1<HostHolder<W, R>, Observable<HostUpdate<W, R>>>() {
@Override
public Observable<HostUpdate<W, R>> call(HostHolder<W, R> holder) {
return holder.getConnector()
.getHost()
.getCloseNotifier()
.map(new VoidToAnythingCast<HostUpdate<W, R>>())
.ignoreElements()
.onErrorResumeNext(Observable.<HostUpdate<W, R>>empty())
.concatWith(Observable.just(new HostUpdate<>(Action.Remove, holder)))
.mergeWith(Observable.just(new HostUpdate<>(Action.Add, holder)));
}
}).flatMap(newCollector(collector.<W, R>newCollector()), 1).distinctUntilChanged());
}
public static <WW, RR> LoadBalancerFactory<WW, RR> create(LoadBalancingStrategy<WW, RR> strategy) {
return create(strategy, new NoBufferHostCollector());
}
public static <WW, RR> LoadBalancerFactory<WW, RR> create(LoadBalancingStrategy<WW, RR> strategy,
HostCollector collector) {
return new LoadBalancerFactory<>(strategy, collector);
}
private class ConnectionProviderImpl implements ConnectionProvider<W, R> {
private volatile ConnectionProvider<W, R> currentProvider = new ConnectionProvider<W, R>() {
@Override
public Observable<Connection<R, W>> newConnectionRequest() {
return Observable.error(NoHostsAvailableException.EMPTY_INSTANCE);
}
};
public ConnectionProviderImpl(Observable<List<HostHolder<W, R>>> hosts) {
hosts.subscribe(new Action1<List<HostHolder<W, R>>>() {
@Override
public void call(List<HostHolder<W, R>> hostHolders) {
currentProvider = strategy.newStrategy(hostHolders);
}
}, new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
logger.error("Error while listening on the host stream. Hosts will not be refreshed.", throwable);
}
});
}
@Override
public Observable<Connection<R, W>> newConnectionRequest() {
return currentProvider.newConnectionRequest();
}
}
private Func1<? super HostUpdate<W, R>, ? extends Observable<List<HostHolder<W, R>>>>
newCollector(final Func1<HostUpdate<W, R>, Single<List<HostHolder<W, R>>>> f) {
return new Func1<HostUpdate<W, R>, Observable<List<HostHolder<W, R>>>>() {
@Override
public Observable<List<HostHolder<W, R>>> call(HostUpdate<W, R> holder) {
return f.call(holder).toObservable();
}
};
}
}