All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.roboconf.messaging.internal.client.rabbitmq.RabbitMqClientDm Maven / Gradle / Ivy

The newest version!
/**
 * Copyright 2014-2015 Linagora, Université Joseph Fourier, Floralis
 *
 * The present code is developed in the scope of the joint LINAGORA -
 * Université Joseph Fourier - Floralis research program and is designated
 * as a "Result" pursuant to the terms and conditions of the LINAGORA
 * - Université Joseph Fourier - Floralis research program. Each copyright
 * holder of Results enumerated here above fully & independently holds complete
 * ownership of the complete Intellectual Property rights applicable to the whole
 * of said Results, and may freely exploit it in any manner which does not infringe
 * the moral rights of the other copyright holders.
 *
 * 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.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.logging.Logger;

import net.roboconf.core.model.beans.Application;
import net.roboconf.core.model.beans.Instance;
import net.roboconf.core.model.helpers.InstanceHelpers;
import net.roboconf.core.model.helpers.VariableHelpers;
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 net.roboconf.messaging.messages.from_agent_to_agent.MsgCmdRemoveImport;

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

/**
 * The RabbitMQ client for the DM.
 * @author Vincent Zurczak - Linagora
 * @author Pierre Bourret - Université Joseph Fourier
 */
public class RabbitMqClientDm implements IDmClient {

	private static final String DM_NEUTRAL_QUEUE_NAME = "roboconf.dm.neutral";

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

	String neutralConsumerTag;
	final Map applicationNameToConsumerTag = new HashMap ();
	Channel channel;
	QueueingConsumer consumer;


	/*
	 * (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
	 * #setMessageQueue(java.util.concurrent.LinkedBlockingQueue)
	 */
	@Override
	public void setMessageQueue( LinkedBlockingQueue messageQueue ) {
		this.messageQueue = messageQueue;
	}


	/*
	 * (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()
	 */
	@Override
	public void openConnection() throws IOException {

		// Already connected? Do nothing
		this.logger.info( "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.messageServerUsername, this.messageServerPassword );
		this.channel = factory.newConnection().createChannel();
		this.logger.info( "The DM established a new connection with RabbitMQ. Channel # " + this.channel.getChannelNumber());

		// Be notified when a message does not arrive in a queue (i.e. nobody is listening)
		this.channel.addReturnListener( new DmReturnListener());

		// Declare the DM debug-dedicated queue.
		this.channel.queueDeclare( DM_NEUTRAL_QUEUE_NAME, true, false, true, null );

		// Start listening to messages.
		this.consumer = new QueueingConsumer( this.channel );
		String threadName = "Roboconf - Queue listener for the DM";
		String id = "The DM";
		new ListeningThread( threadName, this.logger, this.consumer, this.messageQueue, id ).start();
	}


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

		StringBuilder sb = new StringBuilder( "The DM is closing its connection to RabbitMQ." );
		if( this.channel != null )
			sb.append( " Channel # " + this.channel.getChannelNumber());

		this.logger.info( sb.toString());
		if( isConnected())
			RabbitMqUtils.closeConnection( this.channel );

		this.channel = null;
	}


	/*
	 * (non-Javadoc)
	 * @see net.roboconf.messaging.client.IDmClient
	 * #publishMessageToAgent(net.roboconf.core.model.beans.Application, net.roboconf.core.model.beans.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.beans.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())) {
				this.logger.finer( "Application " + application + " is already listened to by a messaging client." );
				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
			String consumerTag = this.channel.basicConsume( queueName, true, this.consumer );
			this.applicationNameToConsumerTag.put( application.getName(), consumerTag );
		}
	}


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

		// The DM can send messages to itself (e.g. for debug).
		// This method could also be used to broadcast information to (potential) other DMs.
		this.logger.fine( "The DM sends a message to the DM's neutral queue." );
		this.channel.queueDeclare( DM_NEUTRAL_QUEUE_NAME, true, false, true, null );

		// To prevent spamming and residual messages, messages sent by the DM
		// (to itself or its siblings) have a life span of 500 ms. If there is no
		// client connected during this period, the message will be dropped.
		this.channel.basicPublish(
				"", DM_NEUTRAL_QUEUE_NAME,
				new BasicProperties.Builder().expiration( "500" ).build(),
				SerializationUtils.serializeObject( msg ));

		this.logger.fine( "The DM sent a message to the DM's neutral queue." );
	}


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

		if( command == ListenerCommand.START ) {
			if( this.neutralConsumerTag != null ) {
				this.logger.finer( "The DM is already listening to the neutral queue." );
				return;
			}

			this.channel.queueDeclare( DM_NEUTRAL_QUEUE_NAME, true, false, true, null );

			// Create the debug message consumer and start consuming.
			// No auto-ACK. Messages must be acknowledged manually by the consumer.
			this.neutralConsumerTag = this.channel.basicConsume(
					DM_NEUTRAL_QUEUE_NAME,  // queue
					true,                   // auto ACK
					DM_NEUTRAL_QUEUE_NAME,  // consumer tag set to the queue name
					false,                  // get local messages (ESSENTIAL!)
					false,                  // consumer is not exclusive
					null,                   // no parameters
					this.consumer );        		// the consumer

		} else {
			this.logger.fine( "The DM stops listening to the neutral queue." );
			if ( this.neutralConsumerTag != null
					&& this.channel != null
					&& this.channel.isOpen())
				this.channel.basicCancel( this.neutralConsumerTag );

			this.neutralConsumerTag = null;
		}
	}


	/* (non-Javadoc)
	 * @see net.roboconf.messaging.client.IDmClient
	 * #deleteMessagingServerArtifacts(net.roboconf.core.model.beans.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
	}


	/*
	 * (non-Javadoc)
	 * @see net.roboconf.messaging.client.IDmClient
	 * #propagateAgentTermination(net.roboconf.core.model.beans.Application, net.roboconf.core.model.beans.Instance)
	 */
	@Override
	public void propagateAgentTermination( Application application, Instance rootInstance )
	throws IOException {

		this.logger.fine( "The DM is propagating the termination of agent '" + rootInstance + "'." );

		// The messages will go through JUST like if they were coming from other agents.
		String exchangeName = RabbitMqUtils.buildExchangeName( application, false );

		// Start with the deepest instances
		List instances = InstanceHelpers.buildHierarchicalList( rootInstance );
		Collections.reverse( instances );

		// Roughly, we unpublish all the variables for all the instances that were on the agent's machine.
		// This code is VERY similar to ...ClientAgent#unpublishExports
		for( Instance instance : instances ) {
			for( String facetOrComponentName : VariableHelpers.findPrefixesForExportedVariables( instance )) {

				MsgCmdRemoveImport message = new MsgCmdRemoveImport(
						facetOrComponentName,
						InstanceHelpers.computeInstancePath( instance ));

				this.channel.basicPublish(
						exchangeName,
						RabbitMqClientAgent.THOSE_THAT_IMPORT + facetOrComponentName,
						null,
						SerializationUtils.serializeObject( message ));
			}
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy