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

io.reactivex.mantis.remote.observable.RemoteObservableConnectionHandler Maven / Gradle / Ivy

There is a newer version: 3.1.4
Show newest version
/*
 * 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.remote.observable;

import io.mantisrx.common.codec.Encoder;
import io.mantisrx.common.network.WritableEndpoint;
import io.reactivex.mantis.remote.observable.ingress.IngressPolicy;
import io.reactivex.mantis.remote.observable.slotting.SlottingStrategy;
import io.reactivx.mantis.operators.DisableBackPressureOperator;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import mantis.io.reactivex.netty.channel.ConnectionHandler;
import mantis.io.reactivex.netty.channel.ObservableConnection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rx.Notification;
import rx.Notification.Kind;
import rx.Observable;
import rx.Subscription;
import rx.functions.Action0;
import rx.functions.Action1;
import rx.functions.Func1;
import rx.observables.GroupedObservable;


public class RemoteObservableConnectionHandler implements
        ConnectionHandler> {

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

    @SuppressWarnings("rawtypes")
    private Map observables;
    private RxMetrics serverMetrics;
    private IngressPolicy ingressPolicy;
    private int writeBufferTimeMSec;

    @SuppressWarnings("rawtypes")
    public RemoteObservableConnectionHandler(
            Map observables,
            IngressPolicy ingressPolicy,
            RxMetrics metrics,
            int writeBufferTimeMSec) {
        this.observables = observables;
        this.ingressPolicy = ingressPolicy;
        this.serverMetrics = metrics;
        this.writeBufferTimeMSec = writeBufferTimeMSec;
    }

    @Override
    public Observable handle(final
                                   ObservableConnection> connection) {
        logger.info("Connection received: " + connection.getChannel().remoteAddress());
        if (ingressPolicy.allowed(connection)) {
            return setupConnection(connection);
        } else {
            Exception e = new RemoteObservableException("Connection rejected due to ingress policy");
            return Observable.error(e);
        }
    }

    @SuppressWarnings( {"rawtypes", "unchecked"})
    private  Subscription serveObservable(
            final Observable observable,
            final ObservableConnection> connection,
            final RemoteRxEvent event,
            final Func1, Func1> filterFunction,
            final Encoder encoder,
            final ServeObservable serveConfig,
            final WritableEndpoint> endpoint) {

        final MutableReference subReference = new MutableReference<>();
        subReference.setValue(
                observable
                        .filter(filterFunction.call(event.getSubscribeParameters()))
                        .doOnCompleted(new Action0() {
                            @Override
                            public void call() {
                                logger.info("OnCompleted recieved in serveObservable, sending to client.");
                            }
                        })
                        .doOnError(new Action1() {
                            @Override
                            public void call(Throwable t1) {
                                logger.info("OnError received in serveObservable, sending to client: ", t1);
                            }
                        })
                        .materialize()
                        .lift(new DisableBackPressureOperator>())
                        .buffer(writeBufferTimeMSec, TimeUnit.MILLISECONDS)
                        .filter(new Func1>, Boolean>() {
                            @Override
                            public Boolean call(List> t1) {
                                return t1 != null && !t1.isEmpty();
                            }
                        })
                        .map(new Func1>, List>() {

                            @Override
                            public List call(List> notifications) {

                                List rxEvents = new ArrayList(notifications.size());
                                for (Notification notification : notifications) {


                                    if (notification.getKind() == Notification.Kind.OnNext) {
                                        rxEvents.add(RemoteRxEvent.next(event.getName(), encoder.encode(notification.getValue())));
                                    } else if (notification.getKind() == Notification.Kind.OnError) {
                                        rxEvents.add(RemoteRxEvent.error(event.getName(), RemoteObservable.fromThrowableToBytes(notification.getThrowable())));
                                    } else if (notification.getKind() == Notification.Kind.OnCompleted) {
                                        rxEvents.add(RemoteRxEvent.completed(event.getName()));
                                    } else {
                                        throw new RuntimeException("Unsupported notification kind: " + notification.getKind());
                                    }
                                }
                                return rxEvents;
                            }
                        })
                        .filter(new Func1, Boolean>() {
                            @Override
                            public Boolean call(List t1) {
                                return t1 != null && !t1.isEmpty();
                            }
                        })
                        .subscribe(new WriteBytesObserver(connection, subReference, serverMetrics,
                                serveConfig.getSlottingStrategy(), endpoint)));
        return subReference.getValue();
    }

    @SuppressWarnings( {"unchecked", "rawtypes"})
    private  Subscription serveNestedObservable(
            final Observable observable,
            final ObservableConnection> connection,
            final RemoteRxEvent event,
            final Func1, Func1> filterFunction,
            final Encoder encoder,
            final ServeNestedObservable> serveConfig,
            final WritableEndpoint> endpoint) {

        final MutableReference subReference = new MutableReference<>();
        subReference.setValue(
                observable
                        .filter(filterFunction.call(event.getSubscribeParameters()))
                        .doOnCompleted(new Action0() {
                            @Override
                            public void call() {
                                logger.info("OnCompleted recieved in serveNestedObservable, sending to client.");
                            }
                        })
                        .doOnError(new Action1() {
                            @Override
                            public void call(Throwable t1) {
                                logger.info("OnError received in serveNestedObservable, sending to client: ", t1);
                            }
                        })
                        .map(new Func1() {
                            @Override
                            public byte[] call(T t1) {
                                return encoder.encode(t1);
                            }
                        })
                        .materialize()
                        .map(new Func1, RemoteRxEvent>() {
                            @Override
                            public RemoteRxEvent call(Notification notification) {
                                if (notification.getKind() == Notification.Kind.OnNext) {
                                    return RemoteRxEvent.next(event.getName(), notification.getValue());
                                } else if (notification.getKind() == Notification.Kind.OnError) {
                                    return RemoteRxEvent.error(event.getName(), RemoteObservable.fromThrowableToBytes(notification.getThrowable()));
                                } else if (notification.getKind() == Notification.Kind.OnCompleted) {
                                    return RemoteRxEvent.completed(event.getName());
                                } else {
                                    throw new RuntimeException("Unsupported notification kind: " + notification.getKind());
                                }
                            }
                        })
                        .lift(new DisableBackPressureOperator())
                        .buffer(writeBufferTimeMSec, TimeUnit.MILLISECONDS)
                        .filter(new Func1, Boolean>() {
                            @Override
                            public Boolean call(List t1) {
                                return t1 != null && !t1.isEmpty();
                            }
                        })
                        .filter(new Func1, Boolean>() {
                            @Override
                            public Boolean call(List t1) {
                                return t1 != null && !t1.isEmpty();
                            }
                        })
                        .subscribe(new WriteBytesObserver(connection, subReference, serverMetrics,
                                serveConfig.getSlottingStrategy(), endpoint)));
        return subReference.getValue();
    }

    private  Subscription serveGroupedObservable(
            final Observable> groups,
            final ObservableConnection> connection,
            final RemoteRxEvent event,
            final Func1, Func1> filterFunction,
            final Encoder keyEncoder,
            final Encoder valueEncoder,
            final ServeGroupedObservable serveConfig,
            final WritableEndpoint> endpoint) {

        final MutableReference subReference = new MutableReference<>();
        subReference.setValue(groups
                // filter out groups based on subscription parameters
                .filter(new Func1, Boolean>() {
                    @Override
                    public Boolean call(Group group) {
                        return filterFunction.call(event.getSubscribeParameters())
                                .call(group.getKeyValue());
                    }
                })
                .doOnCompleted(new Action0() {
                    @Override
                    public void call() {
                        logger.info("OnCompleted recieved in serveGroupedObservable, sending to client.");
                    }
                })
                .doOnError(new Action1() {
                    @Override
                    public void call(Throwable t1) {
                        logger.info("OnError received in serveGroupedObservable, sending to client: ", t1);
                    }
                })
                .materialize()
                .lift(new DisableBackPressureOperator>>())
                .buffer(writeBufferTimeMSec, TimeUnit.MILLISECONDS)
                .filter(new Func1>>, Boolean>() {
                    @Override
                    public Boolean call(List>> t1) {
                        return t1 != null && !t1.isEmpty();
                    }
                })
                .map(new Func1>>, List>() {
                    @Override
                    public List call(final List>> groupNotifications) {
                        List rxEvents = new ArrayList(groupNotifications.size());
                        for (Notification> groupNotification : groupNotifications) {
                            if (Kind.OnNext == groupNotification.getKind()) {
                                // encode inner group notification
                                Group group = groupNotification.getValue();
                                final int keyLength = group.getKeyBytes().length;
                                Notification notification = groupNotification.getValue().getNotification();
                                byte[] data = null;
                                if (Kind.OnNext == notification.getKind()) {
                                    V value = notification.getValue();
                                    byte[] valueBytes = valueEncoder.encode(value);
                                    // 1 byte for notification type,
                                    // 4 bytes is to encode key length as int
                                    data = ByteBuffer.allocate(1 + 4 + keyLength + valueBytes.length)
                                            .put((byte) 1)
                                            .putInt(keyLength)
                                            .put(group.getKeyBytes())
                                            .put(valueBytes)
                                            .array();
                                } else if (Kind.OnCompleted == notification.getKind()) {
                                    data = ByteBuffer.allocate(1 + 4 + keyLength)
                                            .put((byte) 2)
                                            .putInt(keyLength)
                                            .put(group.getKeyBytes())
                                            .array();
                                } else if (Kind.OnError == notification.getKind()) {
                                    Throwable error = notification.getThrowable();
                                    byte[] errorBytes = RemoteObservable.fromThrowableToBytes(error);
                                    data = ByteBuffer.allocate(1 + 4 + keyLength + errorBytes.length)
                                            .put((byte) 3)
                                            .putInt(keyLength)
                                            .put(group.getKeyBytes())
                                            .put(errorBytes)
                                            .array();
                                }
                                rxEvents.add(RemoteRxEvent.next(event.getName(), data));
                            } else if (Kind.OnCompleted == groupNotification.getKind()) {
                                rxEvents.add(RemoteRxEvent.completed(event.getName()));
                            } else if (Kind.OnError == groupNotification.getKind()) {
                                rxEvents.add(RemoteRxEvent.error(event.getName(),
                                        RemoteObservable.fromThrowableToBytes(groupNotification.getThrowable())));
                            } else {
                                throw new RuntimeException("Unsupported notification type: " + groupNotification.getKind());
                            }
                        }
                        return rxEvents;
                    }
                })
                .filter(new Func1, Boolean>() {
                    @Override
                    public Boolean call(List t1) {
                        return t1 != null && !t1.isEmpty();
                    }
                })
                .subscribe(new WriteBytesObserver(connection, subReference, serverMetrics,
                        serveConfig.getSlottingStrategy(), endpoint)));
        return subReference.getValue();
    }

    @SuppressWarnings( {"unchecked", "rawtypes"})
    private void subscribe(final MutableReference unsubscribeCallbackReference,
                           RemoteRxEvent event, final ObservableConnection> connection,
                           ServeConfig configuration, WritableEndpoint endpoint) {
        Func1 filterFunction = configuration.getFilterFunction();

        Subscription subscription = null;
        if (configuration instanceof ServeObservable) {
            ServeObservable serveObservable = (ServeObservable) configuration;
            if (serveObservable.isSubscriptionPerConnection()) {
                Observable toServe = serveObservable.getObservable();
                if (serveObservable.isHotStream()) {
                    toServe = toServe.share();
                }
                subscription = serveObservable(toServe, connection,
                        event, filterFunction, serveObservable.getEncoder(), serveObservable, endpoint);
            } else {
                subscription = serveObservable(endpoint.read(), connection,
                        event, filterFunction, serveObservable.getEncoder(), serveObservable, endpoint);
            }
        } else if (configuration instanceof ServeGroupedObservable) {
            ServeGroupedObservable sgo = (ServeGroupedObservable) configuration;
            subscription = serveGroupedObservable(endpoint.read(), connection,
                    event, filterFunction, sgo.getKeyEncoder(), sgo.getValueEncoder(),
                    sgo, endpoint);
        } else if (configuration instanceof ServeNestedObservable) {
            ServeNestedObservable serveNestedObservable = (ServeNestedObservable) configuration;
            subscription = serveNestedObservable(endpoint.read(), connection,
                    event, filterFunction, serveNestedObservable.getEncoder(), serveNestedObservable, endpoint);
        }

        unsubscribeCallbackReference.setValue(subscription);
    }

    @SuppressWarnings( {"rawtypes", "unchecked"})
    private Observable handleSubscribeRequest(RemoteRxEvent event, final ObservableConnection> connection, MutableReference slottingStrategyReference,
                                                    MutableReference unsubscribeCallbackReference,
                                                    MutableReference slottingIdReference) {

        // check if observable exists in configs
        String observableName = event.getName();
        ServeConfig config = observables.get(observableName);
        if (config == null) {
            return Observable.error(new RemoteObservableException("No remote observable configuration found for name: " + observableName));
        }

        if (event.getType() == RemoteRxEvent.Type.subscribed) {
            String slotId = null;
            Map subscriptionParameters = event.getSubscribeParameters();
            if (subscriptionParameters != null) {
                slotId = subscriptionParameters.get("slotId");
            }

            InetSocketAddress address = (InetSocketAddress) connection.getChannel().remoteAddress();
            WritableEndpoint endpoint = new WritableEndpoint<>(address.getHostName(), address.getPort(), slotId,
                    connection);
            SlottingStrategy slottingStrategy = config.getSlottingStrategy();

            slottingIdReference.setValue(endpoint);
            slottingStrategyReference.setValue(slottingStrategy);

            logger.info("Connection received on server from client endpoint: " + endpoint + ", subscribed to observable: " + observableName);
            serverMetrics.incrementSubscribedCount();
            subscribe(unsubscribeCallbackReference, event, connection, config, endpoint);
            if (!slottingStrategy.addConnection(endpoint)) {
                // unable to slot connection
                logger.warn("Failed to slot connection for endpoint: " + endpoint);
                connection.close(true);
            }
        }
        return Observable.empty();
    }

    @SuppressWarnings("rawtypes")
    private Observable setupConnection(final ObservableConnection> connection) {

        // state associated with connection
        // used to initiate 'unsubscribe' callback to subscriber
        final MutableReference unsubscribeCallbackReference = new MutableReference();
        // used to release slot when connection completes
        final MutableReference slottingStrategyReference = new MutableReference();
        // used to get slotId for connection
        final MutableReference slottingIdReference = new MutableReference();

        return connection.getInput()
                // filter out unsupported operations
                .filter(new Func1() {
                    @Override
                    public Boolean call(RemoteRxEvent event) {
                        boolean supportedOperation = false;
                        if (event.getType() == RemoteRxEvent.Type.subscribed ||
                                event.getType() == RemoteRxEvent.Type.unsubscribed) {
                            supportedOperation = true;
                        }
                        return supportedOperation;
                    }
                })
                .flatMap(new Func1>() {
                    @SuppressWarnings("unchecked")
                    @Override
                    public Observable call(RemoteRxEvent event) {
                        if (event.getType() == RemoteRxEvent.Type.subscribed) {
                            return handleSubscribeRequest(event, connection, slottingStrategyReference, unsubscribeCallbackReference,
                                    slottingIdReference);
                        } else if (event.getType() == RemoteRxEvent.Type.unsubscribed) {
                            Subscription subscription = unsubscribeCallbackReference.getValue();
                            if (subscription != null) {
                                subscription.unsubscribe();
                            }
                            serverMetrics.incrementUnsubscribedCount();
                            // release slot
                            if (slottingStrategyReference.getValue() != null) {
                                if (!slottingStrategyReference.getValue().removeConnection(slottingIdReference.getValue())) {
                                    logger.error("Failed to remove endpoint from slot,  endpoint: " + slottingIdReference.getValue());
                                }
                            }
                            logger.info("Connection: " + connection.getChannel()
                                    .remoteAddress() + " unsubscribed, closing connection");
                            connection.close(true);
                        }
                        return Observable.empty();
                    }
                });
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy