All Downloads are FREE. Search and download functionalities are using the official Maven repository.

netflix.ocelli.util.RxUtil Maven / Gradle / Ivy

There is a newer version: 0.1.0-rc.2
Show newest version
package netflix.ocelli.util;

import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import rx.Observable;
import rx.Observable.OnSubscribe;
import rx.Observable.Operator;
import rx.Observer;
import rx.Subscriber;
import rx.functions.Action0;
import rx.functions.Action1;
import rx.functions.Func0;
import rx.functions.Func1;
import rx.functions.FuncN;
import rx.subscriptions.Subscriptions;

public class RxUtil {
    private static final Logger LOG = LoggerFactory.getLogger(RxUtil.class);
    
    private interface Action01 extends Action1, Action0 {}
    
    /**
     * Increment a stateful counter outside the stream.
     * 
     * {code
     * 
     * observable
     *    .doOnNext(RxUtil.increment(mycounter))
     * 
* } * * @param metric * @return */ public static Action01 increment(final AtomicLong metric) { return new Action01() { @Override public void call(T t1) { metric.incrementAndGet(); } @Override public void call() { metric.incrementAndGet(); } }; } public static Action01 increment(final AtomicInteger metric) { return new Action01() { @Override public void call(T t1) { metric.incrementAndGet(); } @Override public void call() { metric.incrementAndGet(); } }; } /** * Decrement a stateful counter outside the stream. * * {code *
     * observable
     *    .doOnNext(RxUtil.decrement(mycounter))
     * 
* } * * @param metric * @return */ public static Action01 decrement(final AtomicLong metric) { return new Action01() { @Override public void call(T t1) { metric.decrementAndGet(); } @Override public void call() { metric.decrementAndGet(); } }; } /** * Trace each item emitted on the stream with a given label. * Will log the file and line where the trace occurs. * * {code *
     * observable
     *    .doOnNext(RxUtil.trace("next: "))
     * 
* } * * @param label */ public static Action01 trace(String label) { final String caption = getSourceLabel(label); final AtomicLong counter = new AtomicLong(); return new Action01() { @Override public void call(T t1) { LOG.trace("{} ({}) {}", caption, counter.incrementAndGet(), t1); } @Override public void call() { LOG.trace("{} ({}) {}", caption, counter.incrementAndGet()); } }; } /** * Log info line for each item emitted on the stream with a given label. * Will log the file and line where the trace occurs. * * {code *
     * observable
     *    .doOnNext(RxUtil.info("next: "))
     * 
* } * * @param label */ public static Action01 info(String label) { final String caption = getSourceLabel(label); final AtomicLong counter = new AtomicLong(); return new Action01() { @Override public void call(T t1) { LOG.info("{} ({}) {}", caption, counter.incrementAndGet(), t1); } @Override public void call() { LOG.info("{} ({})", caption, counter.incrementAndGet()); } }; } /** * Action to sleep in the middle of a pipeline. This is normally used in tests to * introduce an artifical delay. * @param timeout * @param units * @return */ public static Action01 sleep(final long timeout, final TimeUnit units) { return new Action01() { @Override public void call(T t1) { call(); } @Override public void call() { try { units.sleep(timeout); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(e); } } }; } /** * Decrement a countdown latch for each item * * {code *
     * observable
     *    .doOnNext(RxUtil.decrement(latch))
     * 
* } */ public static Action01 countdown(final CountDownLatch latch) { return new Action01() { @Override public void call(T t1) { latch.countDown(); } @Override public void call() { latch.countDown(); } }; } /** * Log the request rate at the given interval. * * {code *
     * observable
     *    .lift(RxUtil.rate("items per 10 seconds", 10, TimeUnit.SECONDS))
     * 
* } * * @param label * @param interval * @param units */ public static String[] TIME_UNIT = {"ns", "us", "ms", "s", "m", "h", "d"}; public static Operator rate(final String label, final long interval, final TimeUnit units) { final String caption = getSourceLabel(label); return new Operator() { @Override public Subscriber call(final Subscriber child) { final AtomicLong counter = new AtomicLong(); final String sUnits = (interval == 1) ? TIME_UNIT[units.ordinal()] : String.format("({} {})", interval, TIME_UNIT[units.ordinal()]); child.add( Observable.interval(interval, units) .subscribe(new Action1() { @Override public void call(Long t1) { LOG.info("{} {} / {}", caption, counter.getAndSet(0), sUnits); } })); return new Subscriber(child) { @Override public void onCompleted() { if (!isUnsubscribed()) child.onCompleted(); } @Override public void onError(Throwable e) { if (!isUnsubscribed()) child.onError(e); } @Override public void onNext(T t) { counter.incrementAndGet(); if (!isUnsubscribed()) child.onNext(t); } }; } }; } /** * Log error line when an error occurs. * Will log the file and line where the trace occurs. * * {code *
     * observable
     *    .doOnError(RxUtil.error("Stream broke"))
     * 
* } * * @param label */ public static Action1 error(String label) { final String caption = getSourceLabel(label); final AtomicLong counter = new AtomicLong(); return new Action1() { @Override public void call(Throwable t1) { LOG.error("{} ({}) {}", caption, counter.incrementAndGet(), t1); } }; } /** * Log a warning line when an error occurs. * Will log the file and line where the trace occurs. * * {code *
     * observable
     *    .doOnError(RxUtil.warn("Stream broke"))
     * 
* } * * @param label */ public static Action1 warn(String label) { final String caption = getSourceLabel(label); final AtomicLong counter = new AtomicLong(); return new Action1() { @Override public void call(Throwable t1) { LOG.warn("{} ({}) {}", caption, counter.incrementAndGet(), t1); } }; } public static Func1, Boolean> listNotEmpty() { return new Func1, Boolean>() { @Override public Boolean call(List t1) { return !t1.isEmpty(); } }; } /** * Filter out any collection that is empty. * * {code *
     * observable
     *    .filter(RxUtil.collectionNotEmpty())
     * 
* } */ public static Func1, Boolean> collectionNotEmpty() { return new Func1, Boolean>() { @Override public Boolean call(Collection t1) { return !t1.isEmpty(); } }; } /** * Operator that acts as a pass through. Use this when you want the operator * to be interchangable with the default implementation being a single passthrough. * * {code *
     * Operator customOperator = RxUtil.passthrough();
     * observable
     *    .lift(customOperator)
     * 
* } */ public static Operator passthrough() { return new Operator() { @Override public Subscriber call(final Subscriber o) { return o; } }; } /** * Cache all items and emit a single LinkedHashSet with all data when onComplete is called * @return */ public static Operator, T> toLinkedHashSet() { return new Operator, T>() { @Override public Subscriber call(final Subscriber> o) { final Set set = new LinkedHashSet(); return new Subscriber() { @Override public void onCompleted() { o.onNext(set); o.onCompleted(); } @Override public void onError(Throwable e) { o.onError(e); } @Override public void onNext(T t) { set.add(t); } }; } }; } private static String getSourceLabel(String label) { StackTraceElement[] stack = Thread.currentThread().getStackTrace(); StackTraceElement element = stack[3]; return "(" + element.getFileName() + ":" + element.getLineNumber() + ") " + label; } /** * Filter that returns true whenever an external state is true * {code *
     * final AtomicBoolean condition = new AtomicBoolean();
     * 
     * observable
     *    .filter(RxUtil.isTrue(condition))
     * 
* } * @param condition */ public static Func1 isTrue(final AtomicBoolean condition) { return new Func1() { @Override public Boolean call(T t1) { return condition.get(); } }; } /** * Filter that returns true whenever an external state is false * {code *
     * final AtomicBoolean condition = new AtomicBoolean();
     * 
     * observable
     *    .filter(RxUtil.isTrue(condition))
     * 
* } * @param condition */ public static Func1 isFalse(final AtomicBoolean condition) { return new Func1() { @Override public Boolean call(T t1) { return !condition.get(); } }; } /** * Filter that returns true whenever a CAS operation on an external static * AtomicBoolean succeeds * {code *
     * final AtomicBoolean condition = new AtomicBoolean();
     * 
     * observable
     *    .filter(RxUtil.isTrue(condition))
     * 
* } * @param condition */ public static Func1 compareAndSet(final AtomicBoolean condition, final boolean expect, final boolean value) { return new Func1() { @Override public Boolean call(Long t1) { return condition.compareAndSet(expect, value); } }; } /** * Simple operation that sets an external condition for each emitted item * {code *
     * final AtomicBoolean condition = new AtomicBoolean();
     * 
     * observable
     *    .doOnNext(RxUtil.set(condition, true))
     * 
* } * @param condition * @param value * @return */ public static Action01 set(final AtomicBoolean condition, final boolean value) { return new Action01() { @Override public void call(T t1) { condition.set(value); } @Override public void call() { condition.set(value); } }; } /** * Filter that always returns a constant value. Use this to create a default filter * when the filter implementation is plugable. * * @param constant * @return */ public static Func1 constantFilter(final boolean constant) { return new Func1() { @Override public Boolean call(T t1) { return constant; } }; } /** * Observable factory to be used with {@link Observable.defer()} which will round robin * through a list of {@link Observable}'s so that each subscribe() returns the next * {@link Observable} in the list. * * @param sources * @return */ public static Func0> roundRobinObservableFactory(@SuppressWarnings("unchecked") final Observable ... sources) { return new Func0>() { final AtomicInteger count = new AtomicInteger(); @Override public Observable call() { int index = count.getAndIncrement() % sources.length; return sources[index]; } }; } public static Observable> onSubscribeChooseNext(final Observable ... sources) { return Observable.create(new OnSubscribe>() { private AtomicInteger count = new AtomicInteger(); @Override public void call(Subscriber> t1) { int index = count.getAndIncrement(); if (index < sources.length) { t1.onNext(sources[index]); } t1.onCompleted(); } }); } /** * Given a list of observables that emit a boolean condition AND all conditions whenever * any condition changes and emit the resulting condition when the final condition changes. * @param sources * @return */ public static Observable conditionAnder(List> sources) { return Observable.combineLatest(sources, new FuncN>() { @Override public Observable call(Object... args) { return Observable.from(args).cast(Boolean.class).firstOrDefault(true, new Func1() { @Override public Boolean call(Boolean status) { return !status; } }); } }) .flatMap(new Func1, Observable>() { @Override public Observable call(Observable t1) { return t1; } }) .distinctUntilChanged(); } /** * Trace all parts of an observable's state and especially when * notifications are discarded due to being unsubscribed. This should * only used for debugging purposes. * @param label * @return */ public static Operator uberTracer(String label) { final String caption = getSourceLabel(label); return new Operator() { @Override public Subscriber call(final Subscriber s) { s.add(Subscriptions.create(new Action0() { @Override public void call() { LOG.info("{} unsubscribing", caption); } })); return new Subscriber(s) { private AtomicLong completedCounter = new AtomicLong(); private AtomicLong nextCounter = new AtomicLong(); private AtomicLong errorCounter = new AtomicLong(); @Override public void onCompleted() { if (!s.isUnsubscribed()) { s.onCompleted(); } else { LOG.info("{} ({}) Discarding onCompleted", caption, completedCounter.incrementAndGet()); } } @Override public void onError(Throwable e) { if (!s.isUnsubscribed()) { s.onCompleted(); } else { LOG.info("{} ({}) Discarding onError", caption, errorCounter.incrementAndGet()); } } @Override public void onNext(T t) { if (!s.isUnsubscribed()) { s.onNext(t); } else { LOG.info("{} ({}) Discarding onNext", caption, nextCounter.incrementAndGet()); } } }; } }; } /** * Utility to call an action when any event occurs regardless that event * @param action * @return */ public static Observer onAny(final Action0 action) { return new Observer() { @Override public void onCompleted() { action.call(); } @Override public void onError(Throwable e) { action.call(); } @Override public void onNext(T t) { action.call(); } } ; } public static Action01 acquire(final Semaphore sem) { return new Action01() { @Override public void call(T t1) { call(); } @Override public void call() { try { sem.acquire(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }; } public static Action01 release(final Semaphore sem) { return new Action01() { @Override public void call(T t1) { call(); } @Override public void call() { sem.release(); } }; } public static Action1 set(final AtomicReference ref) { return new Action1() { @Override public void call(T t1) { ref.set(t1); } }; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy