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

io.reactivex.mantis.network.push.ObservableTrigger Maven / Gradle / Ivy

/*
 * Copyright 2019 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.mantis.network.push;

import io.mantisrx.common.MantisGroup;
import io.mantisrx.common.metrics.Gauge;
import io.mantisrx.common.metrics.Metrics;
import io.reactivx.mantis.operators.DisableBackPressureOperator;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rx.Observable;
import rx.Scheduler;
import rx.Subscription;
import rx.functions.Action0;
import rx.functions.Action1;
import rx.functions.Func1;
import rx.observables.GroupedObservable;
import rx.schedulers.Schedulers;


public final class ObservableTrigger {

    private static final Logger logger = LoggerFactory.getLogger(ObservableTrigger.class);

    private static Scheduler timeoutScheduler = Schedulers.from(Executors.newFixedThreadPool(5));

    private ObservableTrigger() {}


    private static  PushTrigger trigger(final String name, final Observable o, final Action0 doOnComplete,
                                              final Action1 doOnError) {
        final AtomicReference subRef = new AtomicReference<>();
        final Gauge subscriptionActive;

        Metrics metrics = new Metrics.Builder()
                .name("ObservableTrigger_" + name)
                .addGauge("subscriptionActive")
                .build();

        subscriptionActive = metrics.getGauge("subscriptionActive");

        // Share the Observable so we don't create a new Observable on every subscription.
        final Observable sharedO = o.share();
        Action1> doOnStart = queue -> {
            Subscription oldSub = subRef.getAndSet(
                sharedO
                    .filter((T t1) -> t1 != null)
                    .doOnSubscribe(() -> {
                        logger.info("Subscription is ACTIVE for observable trigger with name: " + name);
                        subscriptionActive.increment();
                    })
                    .doOnUnsubscribe(() -> {
                        logger.info("Subscription is INACTIVE for observable trigger with name: " + name);
                        subscriptionActive.decrement();
                    })
                    .subscribe(
                        (T data) -> queue.write(data),
                        (Throwable e) -> {
                            logger.warn("Observable used to push data errored, on server with name: " + name, e);
                            if (doOnError != null) {
                                doOnError.call(e);
                            }
                        },
                        () -> {
                            logger.info("Observable used to push data completed, on server with name: " + name);
                            if (doOnComplete != null) {
                                doOnComplete.call();
                            }
                        }
                    )
            );
            if (oldSub != null) {
                logger.info("A new subscription is ACTIVE. " +
                    "Unsubscribe from previous subscription observable trigger with name: " + name);
                oldSub.unsubscribe();
            }
        };

        Action1> doOnStop = t1 -> {
            if (subRef.get() != null) {
                logger.warn("Connections from next stage has dropped to 0. Do not propagate unsubscribe");
                //	subRef.get().unsubscribe();
            }
        };

        return new PushTrigger<>(doOnStart, doOnStop, metrics);
    }

    private static  PushTrigger ssetrigger(final String name, final Observable o, final Action0 doOnComplete,
                                                 final Action1 doOnError) {
        final AtomicReference subRef = new AtomicReference<>();
        final Gauge subscriptionActive;

        Metrics metrics = new Metrics.Builder()
                .name("ObservableTrigger_" + name)
                .addGauge("subscriptionActive")
                .build();

        subscriptionActive = metrics.getGauge("subscriptionActive");

        Action1> doOnStart = queue -> subRef.set(
                o
                        .filter((T t1) -> t1 != null)
                        .doOnSubscribe(() -> {
                            logger.info("Subscription is ACTIVE for observable trigger with name: " + name);
                            subscriptionActive.increment();
                        })
                        .doOnUnsubscribe(() -> {
                            logger.info("Subscription is INACTIVE for observable trigger with name: " + name);
                            subscriptionActive.decrement();
                        })
                        .subscribe(
                            (T data) -> queue.write(data),
                            (Throwable e) -> {
                                logger.warn("Observable used to push data errored, on server with name: " + name, e);
                                if (doOnError != null) {
                                    doOnError.call(e);
                                }
                            },
                            () -> {
                                logger.info("Observable used to push data completed, on server with name: " + name);
                                if (doOnComplete != null) {
                                    doOnComplete.call();
                                }
                            }
                        )
        );

        Action1> doOnStop = t1 -> {
            if (subRef.get() != null) {
                logger.warn("Connections from next stage has dropped to 0 for SSE stage. propagate unsubscribe");
                subRef.get().unsubscribe();
            }
        };

        return new PushTrigger<>(doOnStart, doOnStop, metrics);
    }


    private static  PushTrigger> groupTrigger(final String name, final Observable> o, final Action0 doOnComplete,
                                                                       final Action1 doOnError, final long groupExpirySeconds, final Func1 keyEncoder, final HashFunction hashFunction) {
        final AtomicReference subRef = new AtomicReference<>();
        final Gauge subscriptionActive;

        Metrics metrics = new Metrics.Builder()
                .name("ObservableTrigger_" + name)
                .addGauge("subscriptionActive")
                .build();

        subscriptionActive = metrics.getGauge("subscriptionActive");

        // Share the Observable so we don't create a new Observable on every subscription.
        final Observable> sharedO = o.share();
        Action1>> doOnStart = queue -> {
            Subscription oldSub = subRef.getAndSet(
                sharedO
                    .doOnSubscribe(() -> {
                        logger.info("Subscription is ACTIVE for observable trigger with name: " + name);
                        subscriptionActive.increment();
                    })
                    .doOnUnsubscribe(() -> {
                        logger.info("Subscription is INACTIVE for observable trigger with name: " + name);
                        subscriptionActive.decrement();
                    })
                    .flatMap((final GroupedObservable group) -> {
                            final byte[] keyBytes = keyEncoder.call(group.getKey());
                            final long keyBytesHashed = hashFunction.computeHash(keyBytes);
                            return
                                group
                                    .timeout(groupExpirySeconds, TimeUnit.SECONDS, Observable.empty(), timeoutScheduler)
                                    .lift(new DisableBackPressureOperator())
                                    .buffer(250, TimeUnit.MILLISECONDS)
                                    .filter((List t1) -> t1 != null && !t1.isEmpty())
                                    .map((List list) -> {
                                            List> keyPairList = new ArrayList<>(list.size());
                                            for (V data : list) {
                                                keyPairList.add(new KeyValuePair<>(keyBytesHashed, keyBytes, data));
                                            }
                                            return keyPairList;
                                        }
                                    );
                        }
                    )
                    .subscribe(
                        (List> list) -> {
                            for (KeyValuePair data : list) {
                                queue.write(data);
                            }
                        },
                        (Throwable e) -> {
                            logger.warn("Observable used to push data errored, on server with name: " + name, e);
                            if (doOnError != null) {
                                doOnError.call(e);
                            }
                        },
                        () -> {
                            logger.info("Observable used to push data completed, on server with name: " + name);
                            if (doOnComplete != null) {
                                doOnComplete.call();
                            }
                        }
                    )
            );
            if (oldSub != null) {
                logger.info("A new subscription is ACTIVE. " +
                    "Unsubscribe from previous subscription observable trigger with name: " + name);
                oldSub.unsubscribe();
            }
        };

        Action1>> doOnStop = t1 -> {
            if (subRef.get() != null) {
                logger.warn("Connections from next stage has dropped to 0. " +
                    "Do not propagate unsubscribe until a new connection is made.");
                //subRef.get().unsubscribe();
            }
        };

        return new PushTrigger<>(doOnStart, doOnStop, metrics);
    }


    private static  PushTrigger> mantisGroupTrigger(final String name, final Observable> o, final Action0 doOnComplete,
                                                                             final Action1 doOnError, final long groupExpirySeconds, final Func1 keyEncoder, final HashFunction hashFunction) {
        final AtomicReference subRef = new AtomicReference<>();
        final Gauge subscriptionActive;

        Metrics metrics = new Metrics.Builder()
                .name("ObservableTrigger_" + name)
                .addGauge("subscriptionActive")
                .build();

        subscriptionActive = metrics.getGauge("subscriptionActive");

        // Share the Observable so we don't create a new Observable on every subscription.
        final Observable> sharedO = o.share();
        Action1>> doOnStart = queue -> {
            Subscription oldSub = subRef.getAndSet(
                sharedO
                    .doOnSubscribe(() -> {
                        logger.info("Subscription is ACTIVE for observable trigger with name: " + name);
                        subscriptionActive.increment();
                    })
                    .doOnUnsubscribe(() -> {
                        logger.info("Subscription is INACTIVE for observable trigger with name: " + name);
                        subscriptionActive.decrement();
                    })
                    .map((MantisGroup data) -> {
                        final byte[] keyBytes = keyEncoder.call(data.getKeyValue());
                        final long keyBytesHashed = hashFunction.computeHash(keyBytes);
                        return (new KeyValuePair(keyBytesHashed, keyBytes, data.getValue()));
                    })
                    .subscribe(
                        (KeyValuePair data) -> queue.write(data),
                        (Throwable e) -> {
                            logger.warn("Observable used to push data errored, on server with name: " + name, e);
                            if (doOnError != null) {
                                doOnError.call(e);
                            }
                        },
                        () -> {
                            logger.info("Observable used to push data completed, on server with name: " + name);
                            if (doOnComplete != null) {
                                doOnComplete.call();
                            }
                        }
                    )
            );
            if (oldSub != null) {
                logger.info("A new subscription is ACTIVE. " +
                    "Unsubscribe from previous subscription observable trigger with name: " + name);
                oldSub.unsubscribe();
            }
        };

        Action1>> doOnStop = t1 -> {
            if (subRef.get() != null) {
                logger.warn("Connections from next stage has dropped to 0. " +
                    "Do not propagate unsubscribe until a new connection is made.");
                //	subRef.get().unsubscribe();
            }
        };

        return new PushTrigger<>(doOnStart, doOnStop, metrics);
    }


    public static  PushTrigger o(String name, final Observable o,
                                       Action0 doOnComplete,
                                       Action1 doOnError) {
        return ssetrigger(name, o, doOnComplete, doOnError);
    }

    public static  PushTrigger oo(String name, final Observable> oo,
                                        Action0 doOnComplete,
                                        Action1 doOnError) {
        return trigger(name, Observable.merge(oo), doOnComplete, doOnError);
    }

    public static  PushTrigger> oogo(String name, final Observable>> oo,
                                                              Action0 doOnComplete,
                                                              Action1 doOnError,
                                                              long groupExpirySeconds,
                                                              final Func1 keyEncoder,
                                                              HashFunction hashFunction) {
        return groupTrigger(name, Observable.merge(oo), doOnComplete, doOnError, groupExpirySeconds, keyEncoder, hashFunction);
    }

    // NJ
    public static  PushTrigger> oomgo(String name, final Observable>> oo,
                                                               Action0 doOnComplete,
                                                               Action1 doOnError,
                                                               long groupExpirySeconds,
                                                               final Func1 keyEncoder,
                                                               HashFunction hashFunction) {
        return mantisGroupTrigger(name, Observable.merge(oo), doOnComplete, doOnError, groupExpirySeconds, keyEncoder, hashFunction);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy