io.ventu.rpc.amqp.AmqpResponderImpl.java Source code

Java tutorial

Introduction

Here is the source code for io.ventu.rpc.amqp.AmqpResponderImpl.java

Source

/*
 * Copyright (c) 2015-2016. Ventu.io, Oleg Sklyar and contributors, distributed under the MIT license.
 */

package io.ventu.rpc.amqp;

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeoutException;

import com.google.common.collect.Maps;
import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.AMQP.BasicProperties.Builder;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.Envelope;
import io.ventu.rpc.amqp.defaults.DefaultRequestRouter;
import io.ventu.rpc.amqp.defaults.DefaultSerializer;
import io.ventu.rpc.exception.EncodingException;
import io.ventu.rpc.util.RequestEvaluator;
import io.ventu.rpc.util.RequestRouter;
import io.ventu.rpc.util.Serializer;

import static io.ventu.rpc.RemoteInvoker.CONTENT_TYPE;
import static io.ventu.rpc.RemoteInvoker.ENCODING;

class AmqpResponderImpl extends NoopConsumer implements Consumer, AmqpResponder {

    interface ChannelProvider {
        Channel provide(String routingKeyPattern, Consumer consumer) throws IOException, TimeoutException;

        String rpcExchange();
    }

    final ChannelProvider channelProvider;
    final String routingKeyPattern;
    final Map<Class<?>, RequestEvaluator<?, ?>> requestEvaluatorMap = Maps.newConcurrentMap();

    final Serializer serializer;
    final RequestRouter requestRouter;

    Channel channel;

    AmqpResponderImpl(ChannelProvider channelProvider, String routingKeyPattern)
            throws TimeoutException, IOException {
        this(channelProvider, routingKeyPattern, new DefaultSerializer() {
        }, new DefaultRequestRouter());
    }

    AmqpResponderImpl(ChannelProvider channelProvider, String routingKeyPattern, Serializer serializer,
            RequestRouter requestRouter) throws TimeoutException, IOException {
        this.channelProvider = channelProvider;
        this.routingKeyPattern = routingKeyPattern;
        this.serializer = serializer;
        this.requestRouter = requestRouter;
        channel = channelProvider.provide(routingKeyPattern, this);
    }

    @Override
    public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties props, byte[] body)
            throws IOException {
        // fire and forget, exceptions are suppressed
        handleDeliveryInternal(consumerTag, envelope, props, body);
    }

    CompletableFuture<Void> handleDeliveryInternal(String consumerTag, Envelope envelope, BasicProperties props,
            byte[] body) {
        CompletableFuture<byte[]> answer = handleDelivery(envelope.getRoutingKey(), props.getHeaders(), body);
        return answer.handleAsync((payload, throwable) -> {
            try {
                if (throwable != null) {
                    payload = String.format("{\"error\": \"Unhandled internal error: %s\"}", throwable.getMessage())
                            .getBytes();
                }
                BasicProperties resprops = new Builder().correlationId(props.getCorrelationId())
                        .contentType(CONTENT_TYPE).contentEncoding(ENCODING).build();
                channel.basicPublish(channelProvider.rpcExchange(), props.getReplyTo(), resprops, payload);
            } catch (IOException e) {
                // this is suppressed in the public call
                throw new IllegalStateException(e);
            }
            return null;
        });
    }

    <RQ, RS> CompletableFuture<byte[]> handleDelivery(String routingKey, Map<String, Object> headers,
            byte[] payload) {
        RequestEvaluator<RQ, RS> evaluator;
        RQ request;
        try {
            Class<RQ> clazz = requestRouter.deroute(routingKey);
            evaluator = get(clazz);
            if (evaluator == null) {
                throw new IllegalArgumentException(
                        String.format("No request evaluator found for request class %s", clazz.getName()));
            }
            request = serializer.decode(payload, clazz);

        } catch (IllegalArgumentException | EncodingException ex) {
            CompletableFuture<byte[]> res = new CompletableFuture<>();
            res.complete(String.format("{\"error\": \"%s\"}", ex.getMessage()).getBytes());
            return res;
        }

        return evaluator.eval(headers, request).handleAsync((response, throwable) -> {
            if (throwable == null) {
                try {
                    return serializer.encode(response);
                } catch (EncodingException ex) {
                    throwable = ex;
                }
            }
            return String.format("{\"error\": \"%s\"}", throwable.getMessage()).getBytes();
        });
    }

    @Override
    public <RQ, RS> void put(Class<RQ> clazz, RequestEvaluator<RQ, RS> evaluator) {
        requestEvaluatorMap.put(clazz, evaluator);
    }

    @Override
    public <RQ, RS> RequestEvaluator<RQ, RS> get(Class<RQ> clazz) {
        return (RequestEvaluator<RQ, RS>) requestEvaluatorMap.get(clazz);
    }

    @Override
    public CompletableFuture<Void> close() {
        CompletableFuture<Void> future = new CompletableFuture<>();
        try {
            channel.close();
            future.complete(null);
        } catch (IOException | TimeoutException ex) {
            future.completeExceptionally(ex);
        }
        return future;
    }

    /**
     * Holds information required to construct a channel and connect. Can and should be reused
     * (and amended where required) when adding re-connection functionality.
     */
    static class ChannelProviderImpl implements ChannelProvider {
        final String rpcExchange;
        String rpcQueue;

        private final ConnectionFactory factory;

        ChannelProviderImpl(ConnectionFactory factory, String rpcExchange, String rpcQueue) {
            this.factory = factory;
            this.rpcExchange = rpcExchange;
            this.rpcQueue = rpcQueue;
        }

        @Override
        public Channel provide(String routingKeyPattern, Consumer consumer) throws IOException, TimeoutException {
            Channel res = factory.newConnection().createChannel();

            // both, for sending and receiving
            res.exchangeDeclare(rpcExchange(), "topic");

            // this updates rpcQueue if originally unset: good for recreating the channel on the same queue,
            // no difference for an originally named queue
            rpcQueue = res.queueDeclare(rpcQueue, false, false, false, Maps.newHashMap()).getQueue();

            // receiving requests on the routingKeyPattern
            res.queueBind(rpcQueue, rpcExchange(), routingKeyPattern);
            res.basicConsume(rpcQueue, true, consumer);
            return res;
        }

        @Override
        public String rpcExchange() {
            return rpcExchange;
        }
    }
}