Package org.vertx.java.busmods.amqp

Source Code of org.vertx.java.busmods.amqp.AmqpBridge

package org.vertx.java.busmods.amqp;

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.Envelope;

import org.vertx.java.busmods.BusModBase;
import org.vertx.java.core.Handler;
import org.vertx.java.core.eventbus.EventBus;
import org.vertx.java.core.eventbus.Message;
import org.vertx.java.core.logging.Logger;

import org.vertx.java.core.json.JsonObject;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
import java.security.KeyManagementException;

import java.net.URISyntaxException;

import java.util.Map;
import java.util.HashMap;
import java.util.Queue;
import java.util.LinkedList;

import java.util.UUID;

/**
* Prototype for AMQP bridge
* Currently only does pub/sub and does not declare exchanges so only works with default exchanges
* Three operations:
* 1) Create a consumer on a topic given exchange name (use amqp.topic) and routing key (topic name)
* 2) Close a consumer
* 3) Send message
*
* @author <a href="http://tfox.org">Tim Fox</a>
* @author <a href="http://github.com/blalor">Brian Lalor</a>
*/
public class AmqpBridge extends BusModBase {
    private Connection conn;
    private Map<Long, Channel> consumerChannels = new HashMap<>();
    private long consumerSeq;
    private Queue<Channel> availableChannels = new LinkedList<>();

    private String callbackQueue;
    private RPCCallbackHandler rpcCallbackHandler;
    private ContentType defaultContentType;

    // {{{ start
    /** {@inheritDoc} */
    @Override
    public void start() {
        super.start();

        final String address = getMandatoryStringConfig("address");
        String uri = getMandatoryStringConfig("uri");

        logger.trace("address: " + address);

        defaultContentType = ContentType.fromString(getMandatoryStringConfig("defaultContentType"));

        ConnectionFactory factory = new ConnectionFactory();

        try {
            factory.setUri(uri);
        } catch (URISyntaxException e) {
            throw new IllegalArgumentException("illegal uri: " + uri, e);
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalArgumentException("illegal uri: " + uri, e);
        } catch (KeyManagementException e) {
            throw new IllegalArgumentException("illegal uri: " + uri, e);
        }

        try {
            conn = factory.newConnection(); // IOException
        } catch (IOException e) {
            throw new IllegalStateException("Failed to create connection", e);
        }

        try {
            rpcCallbackHandler = new RPCCallbackHandler(getChannel(), defaultContentType, eb);
        } catch (IOException e) {
            throw new IllegalStateException("Unable to create queue for callbacks", e);
        }

        // register handlers
        eb.registerHandler(address + ".create-consumer", new Handler<Message<JsonObject>>() {
            public void handle(final Message<JsonObject> message) {
                handleCreateConsumer(message);
            }
        });

        eb.registerHandler(address + ".close-consumer", new Handler<Message<JsonObject>>() {
            public void handle(final Message<JsonObject> message) {
                handleCloseConsumer(message);
            }
        });

        eb.registerHandler(address + ".send", new Handler<Message<JsonObject>>() {
            public void handle(final Message<JsonObject> message) {
                handleSend(message);
            }
        });

        eb.registerHandler(address + ".invoke_rpc", new Handler<Message<JsonObject>>() {
            public void handle(final Message<JsonObject> message) {
                handleInvokeRPC(message);
            }
        });
    }
    // }}}

    // {{{ stop
    /** {@inheritDoc} */
    @Override
    public void stop() {
        consumerChannels.clear();

        if (conn != null) {
            try {
                conn.close();
            } catch (Exception e) {
                logger.warn("Failed to close", e);
            }
        }
    }
    // }}}

    // {{{ getChannel
    private Channel getChannel() throws IOException {
        if (! availableChannels.isEmpty()) {
            return availableChannels.remove();
        } else {
            return conn.createChannel(); // IOException
        }
    }
    // }}}

    // {{{ send
    private void send(final AMQP.BasicProperties _props, final JsonObject message)
        throws IOException
    {
        AMQP.BasicProperties.Builder amqpPropsBuilder = new AMQP.BasicProperties.Builder();

        if (_props != null) {
            amqpPropsBuilder = _props.builder();
        }

        // correlationId and replyTo will already be set, if necessary

        JsonObject ebProps = message.getObject("properties");
        if (ebProps != null) {
            amqpPropsBuilder.clusterId(ebProps.getString("clusterId"));
            amqpPropsBuilder.contentType(ebProps.getString("contentType"));
            amqpPropsBuilder.contentEncoding(ebProps.getString("contentEncoding"));

            if (ebProps.getNumber("deliveryMode") != null) {
                amqpPropsBuilder.deliveryMode(ebProps.getNumber("deliveryMode").intValue());
            }

            amqpPropsBuilder.expiration(ebProps.getString("expiration"));

            if (ebProps.getObject("headers") != null) {
                amqpPropsBuilder.headers(ebProps.getObject("headers").toMap());
            }

            amqpPropsBuilder.messageId(ebProps.getString("messageId"));

            if (ebProps.getNumber("priority") != null) {
                amqpPropsBuilder.priority(ebProps.getNumber("priority").intValue());
            }

            // amqpPropsBuilder.timestamp(ebProps.getString("timestamp")); // @todo
            amqpPropsBuilder.type(ebProps.getString("type"));
            amqpPropsBuilder.userId(ebProps.getString("userId"));
        }
       
        Channel channel = getChannel();
        availableChannels.add(channel);

        ContentType contentType = defaultContentType;

        try {
            contentType = ContentType.fromString(amqpPropsBuilder.build().getContentType());
        } catch (IllegalArgumentException e) {
            logger.warn(
                "Illegal content type; using default " + defaultContentType.getContentType()
            );

            amqpPropsBuilder.contentType(contentType.getContentType());
        }

        byte[] messageBodyBytes;

        if (
            ContentType.JSON_CONTENT_TYPES.contains(contentType) ||
            (contentType == ContentType.TEXT_PLAIN)
        ) {
            String contentEncoding = amqpPropsBuilder.build().getContentEncoding();
            if (contentEncoding == null) {
                contentEncoding = "UTF-8";

                amqpPropsBuilder.contentEncoding(contentEncoding);
            }

            try {
                if (contentType == ContentType.TEXT_PLAIN) {
                    messageBodyBytes = message.getString("body").getBytes(contentEncoding);
                } else {
                    messageBodyBytes = message.getObject("body").encode().getBytes(contentEncoding);
                }
            } catch (UnsupportedEncodingException e) {
                throw new IllegalStateException("unsupported encoding " + contentEncoding, e);
            }
        }
        else if (contentType == ContentType.APPLICATION_BSON) {
            // this must be encoded to bytes by the sender, because Vert.x has
            // no support for BSON over the wire

            messageBodyBytes = message.getBinary("body");
        }
        else {
            throw new IllegalStateException("don't know how to transform " + contentType.getContentType());
        }

        channel.basicPublish(
            // exchange must default to non-null string
            message.getString("exchange", ""),
            message.getString("routingKey"),
            amqpPropsBuilder.build(),
            messageBodyBytes
        );
    }
    // }}}

    // {{{ createConsumer
    private long createConsumer(final String exchangeName,
                                final String routingKey,
                                final String forwardAddress)
        throws IOException
    {
        Channel channel = getChannel();

        Consumer cons = new MessageTransformingConsumer(channel, defaultContentType) {
            public void doHandle(final String consumerTag,
                                 final Envelope envelope,
                                 final AMQP.BasicProperties properties,
                                 final JsonObject body)
                throws IOException
            {
                long deliveryTag = envelope.getDeliveryTag();

                eb.send(forwardAddress, body);

                getChannel().basicAck(deliveryTag, false);
            }
        };

        String queueName = channel.queueDeclare().getQueue();

        channel.queueBind(queueName, exchangeName, routingKey);
        channel.basicConsume(queueName, cons);

        long id = consumerSeq++;
        consumerChannels.put(id, channel);

        return id;
    }
    // }}}

    // {{{ closeConsumer
    private void closeConsumer(final long id) {
        Channel channel = consumerChannels.remove(id);

        if (channel != null) {
            availableChannels.add(channel);
        }
    }
    // }}}

    // {{{ handleCreateConsumer
    private void handleCreateConsumer(final Message<JsonObject> message) {
        String exchange = message.body.getString("exchange");
        String routingKey = message.body.getString("routingKey");
        String forwardAddress = message.body.getString("forward");

        JsonObject reply = new JsonObject();

        try {
            reply.putNumber("id", createConsumer(exchange, routingKey, forwardAddress));

            sendOK(message, reply);
        } catch (IOException e) {
            sendError(message, "unable to create consumer: " + e.getMessage(), e);
        }
    }
    // }}}

    // {{{ handleCloseConsumer
    private void handleCloseConsumer(final Message<JsonObject> message) {
        long id = (Long) message.body.getNumber("id");

        closeConsumer(id);
    }
    // }}}

    // {{{ handleSend
    private void handleSend(final Message<JsonObject> message) {
        try {
            send(null, message.body);

            sendOK(message);
        } catch (IOException e) {
            sendError(message, "unable to send: " + e.getMessage(), e);
        }
    }
    // }}}

    // {{{ handleInvokeRPC
    private void handleInvokeRPC(final Message<JsonObject> message) {
        // if replyTo is non-null, then this is a multiple-response RPC invocation
        String replyTo = message.body.getString("replyTo");

        boolean isMultiResponse = (replyTo != null);

        // the correlationId is what ties this all together.
        String correlationId = UUID.randomUUID().toString();

        AMQP.BasicProperties.Builder amqpPropsBuilder = new AMQP.BasicProperties.Builder()
            .correlationId(correlationId)
            .replyTo(rpcCallbackHandler.getQueueName());

        if (isMultiResponse) {
            // multiple-response invocation

            JsonObject msgProps = message.body.getObject("properties");

            String ebCorrelationId = null;
            Integer ttl = null;

            if (msgProps != null) {
                ebCorrelationId = msgProps.getString("correlationId");
                ttl = ((Integer) msgProps.getNumber("timeToLive", 0)).intValue();

                if (ttl == 0) {
                    ttl = null;
                }

                // do not pass these on to send(); that could confuse things
                msgProps.removeField("correlationId");
                msgProps.removeField("timeToLive");
            }

            rpcCallbackHandler.addMultiResponseCorrelation(
                correlationId,
                ebCorrelationId,
                replyTo,
                ttl
            );
        } else {
            // standard call/response invocation; message.reply() will be called
            rpcCallbackHandler.addCorrelation(correlationId, message);
        }

        try {
            send(amqpPropsBuilder.build(), message.body);

            // always invoke message.reply to avoid ambiguity
            if (isMultiResponse) {
                sendOK(message);
            }
        } catch (IOException e) {
            sendError(message, "unable to publish: " + e.getMessage(), e);
        }
    }
    // }}}
}
TOP

Related Classes of org.vertx.java.busmods.amqp.AmqpBridge

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.