Java tutorial
/** * 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.utils; import java.io.IOException; import java.util.AbstractMap; import java.util.Map; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import net.roboconf.core.model.helpers.InstanceHelpers; 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.messages.Message; import com.rabbitmq.client.Channel; import com.rabbitmq.client.ConnectionFactory; import com.rabbitmq.client.ConsumerCancelledException; import com.rabbitmq.client.QueueingConsumer; import com.rabbitmq.client.ShutdownSignalException; /** * @author Vincent Zurczak - Linagora */ public final class RabbitMqUtils { /** * Constructor. */ private RabbitMqUtils() { // nothing } /** * Builds the exchange name for RabbitMQ. * @param applicationName the application name * @param dm true if we want the exchange name for the DM, false for the agents * @return a non-null string */ public static String buildExchangeName(String applicationName, boolean dm) { return applicationName + (dm ? ".admin" : ".agents"); } /** * Builds the exchange name for RabbitMQ. * @param application an application * @param dm true if we want the exchange name for the DM, false for the agents * @return a non-null string */ public static String buildExchangeName(Application application, boolean dm) { return buildExchangeName(application.getName(), dm); } /** * Builds the routing key for an agent. * @param instance an instance managed by the agent * @return a non-null string */ public static String buildRoutingKeyForAgent(Instance instance) { Instance rootInstance = InstanceHelpers.findRootInstance(instance); return buildRoutingKeyForAgent(rootInstance.getName()); } /** * Builds the routing key for an agent. * @param rootInstanceName the name of the root instance associated with the agent * @return a non-null string */ public static String buildRoutingKeyForAgent(String rootInstanceName) { return "machine." + rootInstanceName; } /** * Configures the connection factory with the right settings. * @param factory the connection factory * @param messageServerIp the message server IP (can contain a port) * @param messageServerUsername the user name for the message server * @param messageServerPassword the password for the message server * @throws IOException if something went wrong */ public static void configureFactory(ConnectionFactory factory, String messageServerIp, String messageServerUsername, String messageServerPassword) throws IOException { Map.Entry<String, Integer> entry = findUrlAndPort(messageServerIp); factory.setHost(entry.getKey()); if (entry.getValue() > 0) factory.setPort(entry.getValue()); factory.setUsername(messageServerUsername); factory.setPassword(messageServerPassword); } /** * Parses a raw URL and extracts the host and port. * @param messageServerIp a raw URL (here, the message server's IP) * @return a non-null map entry (key = host URL without the port, value = the port, -1 if not specified) */ public static Map.Entry<String, Integer> findUrlAndPort(String messageServerIp) { Matcher m = Pattern.compile(".*(:\\d+).*").matcher(messageServerIp); String portAsString = m.find() ? m.group(1).substring(1) : null; Integer port = portAsString == null ? -1 : Integer.parseInt(portAsString); String address = portAsString == null ? messageServerIp : messageServerIp.replace(m.group(1), ""); return new AbstractMap.SimpleEntry<String, Integer>(address, port); } /** * Closes the connection to a channel. * @param channel the channel to close (can be null) * @throws IOException if something went wrong */ public static void closeConnection(Channel channel) throws IOException { if (channel != null) { if (channel.isOpen()) { // channel.basicCancel( consumerTag ); channel.close(); } if (channel.getConnection().isOpen()) channel.getConnection().close(); } } /** * Declares the required exchanges for an application. * <p> * Every time the DM or an agent must send a message, we must be sure * all the exchanges have been declared. Otherwise, it will result in * an error in the client. And this error will close the channel. * </p> * <p> * To PREVENT stupid errors, it is really important to declare * both exchanges at once! * </p> * * @param applicationName the application name * @param channel the RabbitMQ channel * @throws IOException if an error occurs */ public static void declareApplicationExchanges(String applicationName, Channel channel) throws IOException { // Exchange declaration is idem-potent String dmExchangeName = buildExchangeName(applicationName, true); channel.exchangeDeclare(dmExchangeName, "fanout"); // "fanout" is a keyword for RabbitMQ. // It broadcasts all the messages to all the queues this exchange knows. String agentExchangeName = buildExchangeName(applicationName, false); channel.exchangeDeclare(agentExchangeName, "topic"); // "topic" is a keyword for RabbitMQ. } /** * Listens to RabbitMQ messages. * <p> * Be careful, this method aims at avoiding duplicate code. It starts an * (almost) infinite loop and should be used with caution. * </p> * * @param sourceName the source name (DM, agent name...) * @param logger the logger * @param consumer the RabbitMQ consumer * @param messageProcessor the message processor */ public static void listenToRabbitMq(String sourceName, Logger logger, QueueingConsumer consumer, AbstractMessageProcessor messageProcessor) { // We listen to messages until the consumer is cancelled logger.fine(sourceName + " starts listening to new messages."); for (;;) { try { QueueingConsumer.Delivery delivery = consumer.nextDelivery(); Message message = SerializationUtils.deserializeObject(delivery.getBody()); StringBuilder sb = new StringBuilder(); sb.append(sourceName); sb.append(" received a message "); sb.append(message.getClass().getSimpleName()); sb.append(" on routing key '"); sb.append(delivery.getEnvelope().getRoutingKey()); sb.append("'."); logger.finer(sb.toString()); messageProcessor.storeMessage(message); } catch (ShutdownSignalException e) { logger.warning(sourceName + ": the message server is shutting down."); logger.finest(Utils.writeException(e)); break; } catch (ConsumerCancelledException e) { logger.info(sourceName + " stops listening to new messages."); break; } catch (InterruptedException e) { logger.finest(Utils.writeException(e)); break; } catch (ClassNotFoundException e) { logger.severe(sourceName + ": a message could not be deserialized. Class cast exception."); logger.finest(Utils.writeException(e)); } catch (IOException e) { logger.severe(sourceName + ": a message could not be deserialized. I/O exception."); logger.finest(Utils.writeException(e)); } } } }