package netflix.ocelli;
import rx.Observable;
import rx.Observable.Operator;
import rx.Observable.Transformer;
import rx.Subscriber;
import rx.Subscription;
import rx.functions.Action0;
import rx.functions.Action1;
import rx.functions.Func0;
import rx.functions.Func1;
import rx.subscriptions.CompositeSubscription;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* From a list of Instance<T> maintain a List of active Instance<T>. Add when T is up and remove
* when T is either down or Instance failed or completed.
*
* @author elandau
*
* @param <T>
*/
public class InstanceCollector<T> implements Transformer<Instance<T>, List<T>> {
private final Func0<Map<T, Subscription>> instanceStoreFactory;
private InstanceCollector(Func0<Map<T, Subscription>> instanceStoreFactory) {
this.instanceStoreFactory = instanceStoreFactory;
}
public static <T> InstanceCollector<T> create() {
return create(new Func0<Map<T, Subscription>>() {
@Override
public Map<T, Subscription> call() {
return new ConcurrentHashMap<T, Subscription>();
}
});
}
public static <T> InstanceCollector<T> create(Func0<Map<T, Subscription>> instanceStoreFactory) {
return new InstanceCollector<T>(instanceStoreFactory);
}
// TODO: Move this into a utils package
public static <K, T> Action1<Instance<T>> toMap(final Map<K, T> map, final Func1<T, K> keyFunc) {
return new Action1<Instance<T>>() {
@Override
public void call(final Instance<T> t1) {
map.put(keyFunc.call(t1.getValue()), t1.getValue());
t1.getLifecycle().doOnCompleted(new Action0() {
@Override
public void call() {
map.remove(t1.getValue());
}
});
}
};
}
@Override
public Observable<List<T>> call(Observable<Instance<T>> o) {
return o.lift(new Operator<Set<T>, Instance<T>>() {
@Override
public Subscriber<? super Instance<T>> call(final Subscriber<? super Set<T>> s) {
final CompositeSubscription cs = new CompositeSubscription();
final Map<T, Subscription> instances = instanceStoreFactory.call();
s.add(cs);
return new Subscriber<Instance<T>>() {
@Override
public void onCompleted() {
s.onCompleted();
}
@Override
public void onError(Throwable e) {
s.onError(e);
}
@Override
public void onNext(final Instance<T> t) {
Subscription sub = t.getLifecycle().doOnCompleted(new Action0() {
@Override
public void call() {
Subscription sub = instances.remove(t.getValue());
cs.remove(sub);
s.onNext(instances.keySet());
}
}).subscribe();
instances.put(t.getValue(), sub);
s.onNext(instances.keySet());
}
};
}
})
.map(new Func1<Set<T>, List<T>>() {
@Override
public List<T> call(Set<T> instances) {
ArrayList<T> snapshot = new ArrayList<T>(instances.size());
snapshot.addAll(instances);
// Make an immutable copy of the list
return Collections.unmodifiableList(snapshot);
}
});
}
}