Java tutorial
/* * 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; } } }