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

Java tutorial

Introduction

Here is the source code for net.roboconf.messaging.internal.client.rabbitmq.AgentClient.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.helpers.InstanceHelpers;
import net.roboconf.core.model.helpers.VariableHelpers;
import net.roboconf.core.model.runtime.Instance;
import net.roboconf.messaging.client.AbstractMessageProcessor;
import net.roboconf.messaging.client.IAgentClient;
import net.roboconf.messaging.internal.utils.RabbitMqUtils;
import net.roboconf.messaging.internal.utils.SerializationUtils;
import net.roboconf.messaging.messages.Message;
import net.roboconf.messaging.messages.from_agent_to_agent.MsgCmdImportAdd;
import net.roboconf.messaging.messages.from_agent_to_agent.MsgCmdImportRemove;
import net.roboconf.messaging.messages.from_agent_to_agent.MsgCmdImportRequest;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.QueueingConsumer;

/**
 * The RabbitMQ client for an agent.
 * @author Vincent Zurczak - Linagora
 */
public class AgentClient implements IAgentClient {

    private static final String THOSE_THAT_EXPORT = "those.that.export.";
    private static final String THOSE_THAT_IMPORT = "those.that.import.";

    private final Logger logger = Logger.getLogger(getClass().getName());
    private String applicationName, rootInstanceName, messageServerIp, messageServerUsername, messageServerPassword;

    String consumerTag;
    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 messageServerUsername, String messageServerPassword) {
        this.messageServerIp = messageServerIp;
        this.messageServerUsername = messageServerUsername;
        this.messageServerPassword = messageServerPassword;
    }

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

    /*
     * (non-Javadoc)
     * @see net.roboconf.messaging.client.IAgentClient
     * #setRootInstanceName(java.lang.String)
     */
    @Override
    public void setRootInstanceName(String rootInstanceName) {
        this.rootInstanceName = rootInstanceName;
    }

    /* (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("Agent " + this.rootInstanceName + " is opening a connection to RabbitMQ.");
        if (this.channel != null) {
            this.logger.info("Agent " + this.rootInstanceName + " has already a connection to RabbitMQ.");
            return;
        }

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

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

        // We start listening the queue here
        // We declare both exchanges.
        // This is for cases where the agent would try to contact the DM
        // before the DM was started. In such cases, the RabbitMQ client
        // will get an error. This error will in turn close the channel and it
        // won't be usable anymore.
        RabbitMqUtils.declareApplicationExchanges(this.applicationName, this.channel);
        // This is really important.

        // Queue declaration is idem-potent
        String queueName = getQueueName();
        this.channel.queueDeclare(queueName, true, false, true, null);

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

        new Thread("Roboconf - Queue listener for Agent " + this.rootInstanceName) {
            @Override
            public void run() {
                RabbitMqUtils.listenToRabbitMq(AgentClient.this.rootInstanceName, AgentClient.this.logger, consumer,
                        AgentClient.this.messageProcessor);
            };

        }.start();
    }

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

        // Stop listening messages
        if (this.channel != null && this.channel.isOpen() && this.consumerTag != null)
            this.channel.basicCancel(this.consumerTag);

        // Stop processing messages
        if (this.messageProcessor != null && this.messageProcessor.isRunning())
            this.messageProcessor.interrupt();

        // Close the connection
        this.consumerTag = null;
        RabbitMqUtils.closeConnection(this.channel);
        this.channel = null;
    }

    /* (non-Javadoc)
     * @see net.roboconf.messaging.client.IAgentClient#setApplicationName(java.lang.String)
     */
    @Override
    public void setApplicationName(String applicationName) {
        this.applicationName = applicationName;
    }

    /* (non-Javadoc)
     * @see net.roboconf.messaging.client.IAgentClient
     * #publishExports(net.roboconf.core.model.runtime.Instance)
     */
    @Override
    public void publishExports(Instance instance) throws IOException {
        this.logger.fine("Agent " + this.rootInstanceName + " is publishing its exports.");

        // For all the exported variables...
        // ... find the component or facet name...
        for (String facetOrComponentName : VariableHelpers.findPrefixesForExportedVariables(instance))
            publishExports(instance, facetOrComponentName);
    }

    /* (non-Javadoc)
     * @see net.roboconf.messaging.client.IAgentClient
     * #publishExports(net.roboconf.core.model.runtime.Instance)
     */
    @Override
    public void publishExports(Instance instance, String facetOrComponentName) throws IOException {
        this.logger.fine("Agent " + this.rootInstanceName + " is publishing its exports prefixed by "
                + facetOrComponentName + ".");

        // Find the variables to export.
        Map<String, String> toPublish = new HashMap<String, String>();
        for (Map.Entry<String, String> entry : instance.getExports().entrySet()) {
            if (entry.getKey().startsWith(facetOrComponentName + "."))
                toPublish.put(entry.getKey(), entry.getValue());
        }

        // Publish them
        if (!toPublish.isEmpty()) {
            MsgCmdImportAdd message = new MsgCmdImportAdd(facetOrComponentName,
                    InstanceHelpers.computeInstancePath(instance), toPublish);

            this.channel.basicPublish(RabbitMqUtils.buildExchangeName(this.applicationName, false),
                    THOSE_THAT_IMPORT + facetOrComponentName, null, SerializationUtils.serializeObject(message));
        }
    }

    /* (non-Javadoc)
     * @see net.roboconf.messaging.client.IAgentClient
     * #unpublishExports(net.roboconf.core.model.runtime.Instance)
     */
    @Override
    public void unpublishExports(Instance instance) throws IOException {
        this.logger.fine("Agent " + this.rootInstanceName + " is un-publishing its exports.");

        // For all the exported variables...
        // ... find the component or facet name...
        for (String facetOrComponentName : VariableHelpers.findPrefixesForExportedVariables(instance)) {

            // Publish them
            MsgCmdImportRemove message = new MsgCmdImportRemove(facetOrComponentName,
                    InstanceHelpers.computeInstancePath(instance));

            this.channel.basicPublish(RabbitMqUtils.buildExchangeName(this.applicationName, false),
                    THOSE_THAT_IMPORT + facetOrComponentName, null, SerializationUtils.serializeObject(message));
        }
    }

    /* (non-Javadoc)
     * @see net.roboconf.messaging.client.IAgentClient
     * #listenToRequestsFromOtherAgents(net.roboconf.messaging.client.IClient.ListenerCommand, net.roboconf.core.model.runtime.Instance)
     */
    @Override
    public void listenToRequestsFromOtherAgents(ListenerCommand command, Instance instance) throws IOException {

        // With RabbitMQ, and for agents, listening to others means
        // create a binding between the "agents" exchange and the agent's queue.
        for (String facetOrComponentName : VariableHelpers.findPrefixesForExportedVariables(instance)) {

            // On which routing key do request go? Those.that.export...
            String routingKey = THOSE_THAT_EXPORT + facetOrComponentName;
            String queueName = getQueueName();
            String exchangeName = RabbitMqUtils.buildExchangeName(this.applicationName, false);

            if (command == ListenerCommand.START) {
                this.logger
                        .fine("Agent " + this.rootInstanceName + " starts listening requests from other agents.");
                this.channel.queueBind(queueName, exchangeName, routingKey);

            } else {
                this.logger.fine("Agent " + this.rootInstanceName + " stops listening requests from other agents.");
                this.channel.queueUnbind(queueName, exchangeName, routingKey);
            }
        }
    }

    /* (non-Javadoc)
     * @see net.roboconf.messaging.client.IAgentClient
     * #requestExportsFromOtherAgents(net.roboconf.core.model.runtime.Instance)
     */
    @Override
    public void requestExportsFromOtherAgents(Instance instance) throws IOException {
        this.logger.fine("Agent " + this.rootInstanceName + " is requesting exports from other agents.");

        // For all the imported variables...
        // ... find the component or facet name...
        for (String facetOrComponentName : VariableHelpers.findPrefixesForImportedVariables(instance)) {

            // ... and ask to publish them.
            // Grouping variable requests by prefix reduces the number of messages.
            MsgCmdImportRequest message = new MsgCmdImportRequest(facetOrComponentName);
            this.channel.basicPublish(RabbitMqUtils.buildExchangeName(this.applicationName, false),
                    THOSE_THAT_EXPORT + facetOrComponentName, null, SerializationUtils.serializeObject(message));
        }
    }

    /* (non-Javadoc)
     * @see net.roboconf.messaging.client.IAgentClient
     * #listenToExportsFromOtherAgents(net.roboconf.messaging.client.IClient.ListenerCommand, net.roboconf.core.model.runtime.Instance)
     */
    @Override
    public void listenToExportsFromOtherAgents(ListenerCommand command, Instance instance) throws IOException {

        // With RabbitMQ, and for agents, listening to others means
        // create a binding between the "agents" exchange and the agent's queue.
        for (String facetOrComponentName : VariableHelpers.findPrefixesForImportedVariables(instance)) {

            // On which routing key do export go? Those.that.import...
            String routingKey = THOSE_THAT_IMPORT + facetOrComponentName;
            String queueName = getQueueName();
            String exchangeName = RabbitMqUtils.buildExchangeName(this.applicationName, false);

            if (command == ListenerCommand.START) {
                this.logger.fine("Agent " + this.rootInstanceName + " starts listening exports from other agents.");
                this.channel.queueBind(queueName, exchangeName, routingKey);

            } else {
                this.logger.fine("Agent " + this.rootInstanceName + " stops listening exports from other agents.");
                this.channel.queueUnbind(queueName, exchangeName, routingKey);
            }
        }
    }

    /* (non-Javadoc)
     * @see net.roboconf.messaging.client.IAgentClient
     * #sendMessageToTheDm(net.roboconf.messaging.messages.Message)
     */
    @Override
    public void sendMessageToTheDm(Message message) throws IOException {

        this.logger.fine("Agent " + this.rootInstanceName + " is sending a " + message.getClass().getSimpleName()
                + " message to the DM.");
        this.channel.basicPublish(RabbitMqUtils.buildExchangeName(this.applicationName, true), "", null,
                SerializationUtils.serializeObject(message));
    }

    /*
     * (non-Javadoc)
     * @see net.roboconf.messaging.client.IAgentClient
     * #listenToTheDm(net.roboconf.messaging.client.IClient.ListenerCommand)
     */
    @Override
    public void listenToTheDm(ListenerCommand command) throws IOException {

        // Bind the root instance name with the queue
        String queueName = getQueueName();
        String exchangeName = RabbitMqUtils.buildExchangeName(this.applicationName, false);
        String routingKey = RabbitMqUtils.buildRoutingKeyForAgent(this.rootInstanceName);

        // queueBind is idem-potent
        if (command == ListenerCommand.START) {
            this.logger.fine("Agent " + this.rootInstanceName + " starts listening to the DM.");
            this.channel.queueBind(queueName, exchangeName, routingKey);

        } else {
            this.logger.fine("Agent " + this.rootInstanceName + " stops listening to the DM.");
            this.channel.queueUnbind(queueName, exchangeName, routingKey);
        }
    }

    private String getQueueName() {
        return this.applicationName + "." + this.rootInstanceName;
    }
}