Java tutorial
/** * 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<Message> messageQueue; String neutralConsumerTag; final Map<String, String> applicationNameToConsumerTag = new HashMap<String, String>(); 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<Message> 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<Instance> 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)); } } } }