net.roboconf.messaging.internal.client.rabbitmq.DmClient.java Source code

Java tutorial

Introduction

Here is the source code for net.roboconf.messaging.internal.client.rabbitmq.DmClient.java

Source

/**
 * Copyright 2014 Linagora, Universit Joseph Fourier
 *
 * 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 net.roboconf.messaging.internal.client.rabbitmq;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;

import net.roboconf.core.model.runtime.Application;
import net.roboconf.core.model.runtime.Instance;
import net.roboconf.core.utils.Utils;
import net.roboconf.messaging.client.AbstractMessageProcessor;
import net.roboconf.messaging.client.IDmClient;
import net.roboconf.messaging.internal.utils.RabbitMqUtils;
import net.roboconf.messaging.internal.utils.SerializationUtils;
import net.roboconf.messaging.messages.Message;

import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.QueueingConsumer;
import com.rabbitmq.client.ReturnListener;

/**
 * The RabbitMQ client for the DM.
 * @author Vincent Zurczak - Linagora
 */
public class DmClient implements IDmClient {

    private final Logger logger = Logger.getLogger(getClass().getName());
    private String messageServerIp, username, password;

    final Map<String, String> applicationNameToConsumerTag = new HashMap<String, String>();
    Channel channel;
    AbstractMessageProcessor messageProcessor;

    /*
     * (non-Javadoc)
     * @see net.roboconf.messaging.client.IClient
     * #setParameters(java.lang.String, java.lang.String, java.lang.String)
     */
    @Override
    public void setParameters(String messageServerIp, String username, String password) {
        this.messageServerIp = messageServerIp;
        this.username = username;
        this.password = password;
    }

    /*
     * (non-Javadoc)
     * @see net.roboconf.messaging.client.IClient
     * #isConnected()
     */
    @Override
    public boolean isConnected() {
        return this.channel != null;
    }

    /* (non-Javadoc)
     * @see net.roboconf.messaging.client.IClient
     * #openConnection(net.roboconf.messaging.client.AbstractMessageProcessor)
     */
    @Override
    public void openConnection(AbstractMessageProcessor messageProcessor) throws IOException {

        // Already connected? Do nothing
        this.logger.fine("The DM is opening a connection to RabbitMQ.");
        if (isConnected()) {
            this.logger.info("The DM has already a connection to RabbitMQ.");
            return;
        }

        // Initialize the connection
        ConnectionFactory factory = new ConnectionFactory();
        RabbitMqUtils.configureFactory(factory, this.messageServerIp, this.username, this.password);
        this.channel = factory.newConnection().createChannel();

        // Be notified when a message does not arrive in a queue (i.e. nobody is listening)
        this.channel.addReturnListener(new ReturnListener() {
            @Override
            public void handleReturn(int replyCode, String replyText, String exchange, String routingKey,
                    BasicProperties properties, byte[] body) throws IOException {

                String messageType = "undetermined";
                try {
                    Message msg = SerializationUtils.deserializeObject(body);
                    messageType = msg.getClass().getName();

                } catch (ClassNotFoundException e) {
                    DmClient.this.logger.severe("Failed to deserialize a message object.");
                    DmClient.this.logger.finest(Utils.writeException(e));
                }

                StringBuilder sb = new StringBuilder();
                sb.append("A message sent by the DM was not received by any agent queue.");
                sb.append("\nMessage type: " + messageType);
                sb.append("\nRouting key: " + routingKey);
                sb.append("\nReason: " + replyText);

                DmClient.this.logger.warning(sb.toString());
            }
        });

        // Store the message processor for later
        this.messageProcessor = messageProcessor;
        this.messageProcessor.start();
    }

    /* (non-Javadoc)
     * @see net.roboconf.messaging.client.IClient
     * #closeConnection()
     */
    @Override
    public void closeConnection() throws IOException {
        this.logger.fine("The DM is closing its connection to RabbitMQ.");

        if (this.messageProcessor != null && this.messageProcessor.isRunning())
            this.messageProcessor.interrupt();

        RabbitMqUtils.closeConnection(this.channel);
        this.channel = null;
    }

    /*
     * (non-Javadoc)
     * @see net.roboconf.messaging.client.IDmClient
     * #publishMessageToAgent(net.roboconf.core.model.runtime.Application, net.roboconf.core.model.runtime.Instance, net.roboconf.messaging.messages.Message)
     */
    @Override
    public void sendMessageToAgent(Application application, Instance instance, Message message) throws IOException {

        String exchangeName = RabbitMqUtils.buildExchangeName(application, false);
        String routingKey = RabbitMqUtils.buildRoutingKeyForAgent(instance);
        this.logger.fine("The DM sends a message to " + routingKey + ". Message type: "
                + message.getClass().getSimpleName());

        // We are requesting mandatory publication.
        // It means we expect this message to reach at least one queue.
        // If not, we want to be notified about it.
        this.channel.basicPublish(exchangeName, routingKey, true, false, null,
                SerializationUtils.serializeObject(message));

        this.logger.fine(
                "The DM sent a message to " + routingKey + ". Message type: " + message.getClass().getSimpleName());
    }

    /* (non-Javadoc)
     * @see net.roboconf.messaging.client.IDmClient
     * #listenToAgentMessages(net.roboconf.core.model.runtime.Application, net.roboconf.messaging.client.IClient.ListenerCommand)
     */
    @Override
    public void listenToAgentMessages(Application application, ListenerCommand command) throws IOException {

        if (command == ListenerCommand.STOP) {
            this.logger.fine(
                    "The DM stops listening agents messages for the '" + application.getName() + "' application.");
            String consumerTag = this.applicationNameToConsumerTag.remove(application.getName());
            if (consumerTag != null && this.channel != null && this.channel.isOpen())
                this.channel.basicCancel(consumerTag);

        } else {
            // Already listening? Ignore...
            if (this.applicationNameToConsumerTag.containsKey(application.getName()))
                return;

            this.logger.fine(
                    "The DM starts listening agents messages for the '" + application.getName() + "' application.");

            // Exchange declaration is idem-potent
            RabbitMqUtils.declareApplicationExchanges(application.getName(), this.channel);

            // Queue declaration is idem-potent
            String queueName = application.getName() + ".dm";
            this.channel.queueDeclare(queueName, true, false, true, null);

            // queueBind is idem-potent
            // Every message sent to the "DM" exchange will land into the DM's queue.
            String exchangeName = RabbitMqUtils.buildExchangeName(application, true);
            this.channel.queueBind(queueName, exchangeName, "");

            // Start to listen to the queue
            final QueueingConsumer consumer = new QueueingConsumer(this.channel);
            String consumerTag = this.channel.basicConsume(queueName, true, consumer);
            this.applicationNameToConsumerTag.put(application.getName(), consumerTag);

            // In the DM, it is performed in a separate thread.
            // So, basically, the DM has a listening thread for every application.
            // Each thread listens for new messages and stores them in the message processor.

            // There is only ONE processor for all the threads. It stores messages
            // and processes them sequentially. DM operations are expected to be short-rabbitMqIsRunning.
            // The DM is just an intermediary between REST clients and agents.
            new Thread("Roboconf - Queue listener for the DM") {
                @Override
                public void run() {
                    RabbitMqUtils.listenToRabbitMq("The DM", DmClient.this.logger, consumer,
                            DmClient.this.messageProcessor);
                };

            }.start();
        }
    }

    /* (non-Javadoc)
     * @see net.roboconf.messaging.client.IDmClient
     * #deleteMessagingServerArtifacts(net.roboconf.core.model.runtime.Application)
     */
    @Override
    public void deleteMessagingServerArtifacts(Application application) throws IOException {

        // We delete the exchanges
        this.channel.exchangeDelete(RabbitMqUtils.buildExchangeName(application, true));
        this.channel.exchangeDelete(RabbitMqUtils.buildExchangeName(application, false));
        // Queues are deleted automatically by RabbitMQ
    }
}