com.spotify.heroic.rpc.nativerpc.NativeRpcProtocolServer.java Source code

Java tutorial

Introduction

Here is the source code for com.spotify.heroic.rpc.nativerpc.NativeRpcProtocolServer.java

Source

/*
 * Copyright (c) 2015 Spotify AB.
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 com.spotify.heroic.rpc.nativerpc;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableList;
import com.spotify.heroic.cluster.NodeMetadata;
import com.spotify.heroic.lifecycle.LifeCycleRegistry;
import com.spotify.heroic.lifecycle.LifeCycles;
import com.spotify.heroic.metadata.CountSeries;
import com.spotify.heroic.metadata.DeleteSeries;
import com.spotify.heroic.metadata.FindKeys;
import com.spotify.heroic.metadata.FindSeries;
import com.spotify.heroic.metadata.FindTags;
import com.spotify.heroic.metadata.MetadataBackend;
import com.spotify.heroic.metadata.MetadataManager;
import com.spotify.heroic.metadata.WriteMetadata;
import com.spotify.heroic.metric.FullQuery;
import com.spotify.heroic.metric.MetricBackend;
import com.spotify.heroic.metric.MetricBackendGroup;
import com.spotify.heroic.metric.MetricManager;
import com.spotify.heroic.metric.WriteMetric;
import com.spotify.heroic.rpc.nativerpc.NativeRpcProtocol.GroupedQuery;
import com.spotify.heroic.rpc.nativerpc.message.NativeRpcEmptyBody;
import com.spotify.heroic.suggest.KeySuggest;
import com.spotify.heroic.suggest.SuggestBackend;
import com.spotify.heroic.suggest.SuggestManager;
import com.spotify.heroic.suggest.TagKeyCount;
import com.spotify.heroic.suggest.TagSuggest;
import com.spotify.heroic.suggest.TagValueSuggest;
import com.spotify.heroic.suggest.TagValuesSuggest;
import eu.toolchain.async.AsyncFramework;
import eu.toolchain.async.AsyncFuture;
import eu.toolchain.async.ResolvableFuture;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.util.Timer;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;

import javax.inject.Inject;
import javax.inject.Named;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicReference;

public class NativeRpcProtocolServer implements LifeCycles {
    private final AsyncFramework async;
    private final MetricManager metrics;
    private final MetadataManager metadata;
    private final SuggestManager suggest;
    private final NodeMetadata localMetadata;
    private final ObjectMapper mapper;
    private final Timer timer;
    private final EventLoopGroup bossGroup;
    private final EventLoopGroup workerGroup;
    private final NativeEncoding encoding;
    private final ResolvableFuture<InetSocketAddress> bindFuture;

    private final SocketAddress address;
    private final int maxFrameSize;

    private final AtomicReference<Channel> serverChannel = new AtomicReference<>();
    private final NativeRpcContainer container = new NativeRpcContainer();

    @Inject
    public NativeRpcProtocolServer(AsyncFramework async, MetricManager metrics, MetadataManager metadata,
            SuggestManager suggest, NodeMetadata localMetadata,
            @Named("application/json+internal") ObjectMapper mapper, Timer timer,
            @Named("boss") EventLoopGroup bossGroup, @Named("worker") EventLoopGroup workerGroup,
            NativeEncoding encoding, @Named("bindFuture") ResolvableFuture<InetSocketAddress> bindFuture,
            @Named("bindAddress") SocketAddress address, @Named("maxFrameSize") int maxFrameSize) {
        this.async = async;
        this.metrics = metrics;
        this.metadata = metadata;
        this.suggest = suggest;
        this.localMetadata = localMetadata;
        this.mapper = mapper;
        this.timer = timer;
        this.bossGroup = bossGroup;
        this.workerGroup = workerGroup;
        this.encoding = encoding;
        this.bindFuture = bindFuture;
        this.address = address;
        this.maxFrameSize = maxFrameSize;
    }

    @Override
    public void register(LifeCycleRegistry registry) {
        registry.start(this::start);
        registry.stop(this::stop);
    }

    {
        container.register("metadata", new NativeRpcEndpoint<NativeRpcEmptyBody, NodeMetadata>() {
            @Override
            public AsyncFuture<NodeMetadata> handle(final NativeRpcEmptyBody request) throws Exception {
                return async.resolved(localMetadata);
            }
        });

        container.register(NativeRpcProtocol.METRICS_QUERY,
                new NativeRpcEndpoint<GroupedQuery<FullQuery.Request>, FullQuery>() {
                    @Override
                    public AsyncFuture<FullQuery> handle(final GroupedQuery<FullQuery.Request> g) throws Exception {
                        return g.apply(metrics, MetricBackendGroup::query);
                    }
                });

        container.register(NativeRpcProtocol.METRICS_WRITE,
                new NativeRpcEndpoint<GroupedQuery<WriteMetric.Request>, WriteMetric>() {
                    @Override
                    public AsyncFuture<WriteMetric> handle(final GroupedQuery<WriteMetric.Request> g)
                            throws Exception {
                        return g.apply(metrics, MetricBackend::write);
                    }
                });

        container.register(NativeRpcProtocol.METADATA_FIND_TAGS,
                new NativeRpcEndpoint<GroupedQuery<FindTags.Request>, FindTags>() {
                    @Override
                    public AsyncFuture<FindTags> handle(final GroupedQuery<FindTags.Request> g) throws Exception {
                        return g.apply(metadata, MetadataBackend::findTags);
                    }
                });

        container.register(NativeRpcProtocol.METADATA_FIND_KEYS,
                new NativeRpcEndpoint<GroupedQuery<FindKeys.Request>, FindKeys>() {
                    @Override
                    public AsyncFuture<FindKeys> handle(final GroupedQuery<FindKeys.Request> g) throws Exception {
                        return g.apply(metadata, MetadataBackend::findKeys);
                    }
                });

        container.register(NativeRpcProtocol.METADATA_FIND_SERIES,
                new NativeRpcEndpoint<GroupedQuery<FindSeries.Request>, FindSeries>() {
                    @Override
                    public AsyncFuture<FindSeries> handle(final GroupedQuery<FindSeries.Request> g)
                            throws Exception {
                        return g.apply(metadata, MetadataBackend::findSeries);
                    }
                });

        container.register(NativeRpcProtocol.METADATA_COUNT_SERIES,
                new NativeRpcEndpoint<GroupedQuery<CountSeries.Request>, CountSeries>() {
                    @Override
                    public AsyncFuture<CountSeries> handle(final GroupedQuery<CountSeries.Request> g)
                            throws Exception {
                        return g.apply(metadata, MetadataBackend::countSeries);
                    }
                });

        container.register(NativeRpcProtocol.METADATA_WRITE,
                new NativeRpcEndpoint<GroupedQuery<WriteMetadata.Request>, WriteMetadata>() {
                    @Override
                    public AsyncFuture<WriteMetadata> handle(final GroupedQuery<WriteMetadata.Request> g)
                            throws Exception {
                        return g.apply(metadata, MetadataBackend::write);
                    }
                });

        container.register(NativeRpcProtocol.METADATA_DELETE_SERIES,
                new NativeRpcEndpoint<GroupedQuery<DeleteSeries.Request>, DeleteSeries>() {
                    @Override
                    public AsyncFuture<DeleteSeries> handle(final GroupedQuery<DeleteSeries.Request> g)
                            throws Exception {
                        return g.apply(metadata, MetadataBackend::deleteSeries);
                    }
                });

        container.register(NativeRpcProtocol.SUGGEST_TAG_KEY_COUNT,
                new NativeRpcEndpoint<GroupedQuery<TagKeyCount.Request>, TagKeyCount>() {
                    @Override
                    public AsyncFuture<TagKeyCount> handle(final GroupedQuery<TagKeyCount.Request> g)
                            throws Exception {
                        return g.apply(suggest, SuggestBackend::tagKeyCount);
                    }
                });

        container.register(NativeRpcProtocol.SUGGEST_TAG,
                new NativeRpcEndpoint<GroupedQuery<TagSuggest.Request>, TagSuggest>() {
                    @Override
                    public AsyncFuture<TagSuggest> handle(final GroupedQuery<TagSuggest.Request> g)
                            throws Exception {
                        return g.apply(suggest, SuggestBackend::tagSuggest);
                    }
                });

        container.register(NativeRpcProtocol.SUGGEST_KEY,
                new NativeRpcEndpoint<GroupedQuery<KeySuggest.Request>, KeySuggest>() {
                    @Override
                    public AsyncFuture<KeySuggest> handle(final GroupedQuery<KeySuggest.Request> g)
                            throws Exception {
                        return g.apply(suggest, SuggestBackend::keySuggest);
                    }
                });

        container.register(NativeRpcProtocol.SUGGEST_TAG_VALUES,
                new NativeRpcEndpoint<GroupedQuery<TagValuesSuggest.Request>, TagValuesSuggest>() {
                    @Override
                    public AsyncFuture<TagValuesSuggest> handle(final GroupedQuery<TagValuesSuggest.Request> g)
                            throws Exception {
                        return g.apply(suggest, SuggestBackend::tagValuesSuggest);
                    }
                });

        container.register(NativeRpcProtocol.SUGGEST_TAG_VALUE,
                new NativeRpcEndpoint<GroupedQuery<TagValueSuggest.Request>, TagValueSuggest>() {
                    @Override
                    public AsyncFuture<TagValueSuggest> handle(final GroupedQuery<TagValueSuggest.Request> g)
                            throws Exception {
                        return g.apply(suggest, SuggestBackend::tagValueSuggest);
                    }
                });
    }

    private AsyncFuture<Void> start() {
        final ServerBootstrap s = new ServerBootstrap();
        s.channel(NioServerSocketChannel.class);
        s.group(bossGroup, workerGroup);
        s.childHandler(new NativeRpcServerSession(timer, mapper, container, maxFrameSize, encoding));

        final ChannelFuture bind = s.bind(address);

        bind.addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(final ChannelFuture f) throws Exception {
                if (!f.isSuccess()) {
                    bindFuture.fail(f.cause());
                    return;
                }

                serverChannel.set(f.channel());
                final InetSocketAddress address = (InetSocketAddress) f.channel().localAddress();
                bindFuture.resolve(address);
            }
        });

        return bindFuture.directTransform(a -> null);
    }

    private Callable<Void> teardownServer() {
        return new Callable<Void>() {
            @Override
            public Void call() throws Exception {
                final Channel ch = serverChannel.getAndSet(null);

                if (ch != null) {
                    ch.close();
                }

                return null;
            }
        };
    }

    private List<AsyncFuture<Void>> teardownThreadpools() {
        final ResolvableFuture<Void> worker = async.future();

        workerGroup.shutdownGracefully().addListener(new FutureListener<Object>() {
            @Override
            public void operationComplete(Future<Object> f) throws Exception {
                if (!f.isSuccess()) {
                    worker.fail(f.cause());
                    return;
                }

                worker.resolve(null);
            }
        });

        final ResolvableFuture<Void> boss = async.future();

        bossGroup.shutdownGracefully().addListener(new FutureListener<Object>() {
            @Override
            public void operationComplete(Future<Object> f) throws Exception {
                if (!f.isSuccess()) {
                    boss.fail(f.cause());
                    return;
                }

                boss.resolve(null);
            }
        });

        return ImmutableList.<AsyncFuture<Void>>of(worker, boss);
    }

    private Callable<Void> teardownTimer() {
        return new Callable<Void>() {
            @Override
            public Void call() throws Exception {
                timer.stop();
                return null;
            }
        };
    }

    private AsyncFuture<Void> stop() {
        final List<AsyncFuture<Void>> callbacks = new ArrayList<>();
        callbacks.add(async.call(teardownServer()));
        callbacks.addAll(teardownThreadpools());
        callbacks.add(async.call(teardownTimer()));
        return async.collectAndDiscard(callbacks);
    }
}