net.roboconf.messaging.rabbitmq.internal.RabbitMqClient.java Source code

Java tutorial

Introduction

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

Source

/**
 * Copyright 2014-2017 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.rabbitmq.internal;

import static net.roboconf.messaging.rabbitmq.RabbitMqConstants.RABBITMQ_SSL_AS_USER_DATA;
import static net.roboconf.messaging.rabbitmq.RabbitMqConstants.RABBITMQ_SSL_KEY_MNGR_FACTORY;
import static net.roboconf.messaging.rabbitmq.RabbitMqConstants.RABBITMQ_SSL_KEY_STORE_PASSPHRASE;
import static net.roboconf.messaging.rabbitmq.RabbitMqConstants.RABBITMQ_SSL_KEY_STORE_PATH;
import static net.roboconf.messaging.rabbitmq.RabbitMqConstants.RABBITMQ_SSL_KEY_STORE_TYPE;
import static net.roboconf.messaging.rabbitmq.RabbitMqConstants.RABBITMQ_SSL_PROTOCOL;
import static net.roboconf.messaging.rabbitmq.RabbitMqConstants.RABBITMQ_SSL_TRUST_MNGR_FACTORY;
import static net.roboconf.messaging.rabbitmq.RabbitMqConstants.RABBITMQ_SSL_TRUST_STORE_PASSPHRASE;
import static net.roboconf.messaging.rabbitmq.RabbitMqConstants.RABBITMQ_SSL_TRUST_STORE_PATH;
import static net.roboconf.messaging.rabbitmq.RabbitMqConstants.RABBITMQ_SSL_TRUST_STORE_TYPE;
import static net.roboconf.messaging.rabbitmq.RabbitMqConstants.RABBITMQ_USE_SSL;

import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

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

import net.roboconf.core.model.beans.Application;
import net.roboconf.core.userdata.UserDataHelpers;
import net.roboconf.messaging.api.MessagingConstants;
import net.roboconf.messaging.api.extensions.IMessagingClient;
import net.roboconf.messaging.api.extensions.MessagingContext;
import net.roboconf.messaging.api.extensions.MessagingContext.RecipientKind;
import net.roboconf.messaging.api.jmx.RoboconfMessageQueue;
import net.roboconf.messaging.api.messages.Message;
import net.roboconf.messaging.api.reconfigurables.ReconfigurableClient;
import net.roboconf.messaging.api.utils.MessagingUtils;
import net.roboconf.messaging.api.utils.SerializationUtils;
import net.roboconf.messaging.rabbitmq.RabbitMqConstants;
import net.roboconf.messaging.rabbitmq.internal.impl.RoboconfConsumer;
import net.roboconf.messaging.rabbitmq.internal.impl.RoboconfRecoveryListener;
import net.roboconf.messaging.rabbitmq.internal.impl.RoboconfReturnListener;
import net.roboconf.messaging.rabbitmq.internal.utils.RabbitMqUtils;

/**
 * Common RabbitMQ client-related stuffs.
 * @author Pierre Bourret - Universit Joseph Fourier
 * @author Vincent Zurczak - Linagora
 */
public class RabbitMqClient implements IMessagingClient {

    private final Logger logger = Logger.getLogger(getClass().getName());
    private final Map<String, String> configuration;
    private final WeakReference<ReconfigurableClient<?>> reconfigurable;

    private RoboconfMessageQueue messageQueue;
    private RecipientKind ownerKind;
    private String applicationName, scopedInstancePath, domain;

    String consumerTag;
    Channel channel;

    /**
     * Constructor.
     * @param reconfigurable
     * @param messagingProperties
     */
    protected RabbitMqClient(ReconfigurableClient<?> reconfigurable, Map<String, String> messagingProperties) {
        this(reconfigurable, messagingProperties, reconfigurable.getOwnerKind());
    }

    /**
     * Constructor.
     * @param reconfigurable
     * @param messagingProperties
     * @param ownerKind
     */
    protected RabbitMqClient(ReconfigurableClient<?> reconfigurable, Map<String, String> messagingProperties,
            RecipientKind ownerKind) {
        this.reconfigurable = new WeakReference<ReconfigurableClient<?>>(reconfigurable);
        this.ownerKind = ownerKind;

        Map<String, String> copy = new LinkedHashMap<>(messagingProperties);
        copy.put(MessagingConstants.MESSAGING_TYPE_PROPERTY, RabbitMqConstants.FACTORY_RABBITMQ);
        this.configuration = Collections.unmodifiableMap(copy);
    }

    /**
     * @return the wrapping reconfigurable client (may be {@code null}).
     */
    public final ReconfigurableClient<?> getReconfigurableClient() {
        return this.reconfigurable.get();
    }

    @Override
    public final void setMessageQueue(RoboconfMessageQueue messageQueue) {
        this.messageQueue = messageQueue;
    }

    @Override
    public final synchronized boolean isConnected() {
        return this.channel != null;
    }

    @Override
    public final String getMessagingType() {
        return RabbitMqConstants.FACTORY_RABBITMQ;
    }

    @Override
    public final Map<String, String> getConfiguration() {

        // Filter SSL parameters so that there are not
        // written in user data if the configuration says so.
        Map<String, String> result = new HashMap<>(this.configuration);
        String useSsl = this.configuration.get(RABBITMQ_USE_SSL);

        List<String> allSslProperties = new ArrayList<>(Arrays.asList(RABBITMQ_SSL_KEY_STORE_PASSPHRASE,
                RABBITMQ_SSL_KEY_STORE_PATH, RABBITMQ_SSL_KEY_STORE_TYPE, RABBITMQ_SSL_KEY_MNGR_FACTORY,
                RABBITMQ_SSL_TRUST_MNGR_FACTORY, RABBITMQ_SSL_TRUST_STORE_PASSPHRASE, RABBITMQ_SSL_TRUST_STORE_PATH,
                RABBITMQ_SSL_TRUST_STORE_TYPE, RABBITMQ_SSL_AS_USER_DATA, RABBITMQ_SSL_PROTOCOL, RABBITMQ_USE_SSL));

        // SSL disabled? Remove all the SSL properties
        if (useSsl == null || "false".equalsIgnoreCase(useSsl)) {
            result.keySet().removeAll(allSslProperties);
        }

        // SSL but do not send stores as user data
        else if ("false".equalsIgnoreCase(this.configuration.get(RABBITMQ_SSL_AS_USER_DATA))) {
            allSslProperties.remove(RABBITMQ_SSL_AS_USER_DATA);
            allSslProperties.remove(RABBITMQ_SSL_PROTOCOL);
            allSslProperties.remove(RABBITMQ_USE_SSL);
            result.keySet().removeAll(allSslProperties);
        }

        // Otherwise (SSL enabled and full user data)
        else {
            result.put(RABBITMQ_SSL_AS_USER_DATA, "true");

            // Indicate which properties point to files whose content should be sent to agents
            result.put(UserDataHelpers.ENCODE_FILE_CONTENT_PREFIX + RABBITMQ_SSL_KEY_STORE_PATH, "");
            result.put(UserDataHelpers.ENCODE_FILE_CONTENT_PREFIX + RABBITMQ_SSL_TRUST_STORE_PATH, "");
        }

        return result;
    }

    @Override
    public void setOwnerProperties(RecipientKind ownerKind, String domain, String applicationName,
            String scopedInstancePath) {

        this.ownerKind = ownerKind;
        this.applicationName = applicationName;
        this.scopedInstancePath = scopedInstancePath;
        this.domain = domain;

        this.logger.fine("Owner properties changed to " + getId());
    }

    @Override
    public void openConnection() throws IOException {

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

        // Initialize the connection
        ConnectionFactory factory = new ConnectionFactory();
        RabbitMqUtils.configureFactory(factory, this.configuration);
        this.channel = factory.newConnection().createChannel();
        this.logger.info(getId() + " 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 RoboconfReturnListener());

        // Add a recoverable listener (when broken connections are recovered).
        // Given the way the RabbitMQ factory is configured, the channel should be "recoverable".
        ((Recoverable) this.channel).addRecoveryListener(new RoboconfRecoveryListener());

        // Declare the exchanges.
        RabbitMqUtils.declareGlobalExchanges(this.domain, this.channel);
        RabbitMqUtils.declareApplicationExchanges(this.domain, this.applicationName, this.channel);

        // Declare the dedicated queue.
        String queueName = getQueueName();
        this.channel.queueDeclare(queueName, true, false, true, null);

        // Start listening to messages.
        RoboconfConsumer consumer = new RoboconfConsumer(getId(), this.channel, this.messageQueue);
        consumer.handleConsumeOk(queueName);
        this.consumerTag = this.channel.basicConsume(queueName, true, consumer);
        this.logger.finer("A new consumer tag was created: " + this.consumerTag);
    }

    @Override
    public void closeConnection() throws IOException {

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

        this.logger.info(sb.toString());

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

            this.channel.basicCancel(this.consumerTag);
            this.logger.finer("A consumer tag was cancelled: " + this.consumerTag);
        }

        // Close the connection
        this.consumerTag = null;
        if (isConnected()) {
            this.logger.finer("Closing the connection and the channel # " + this.channel.getChannelNumber());
            RabbitMqUtils.closeConnection(this.channel);
        }

        this.channel = null;
    }

    @Override
    public void deleteMessagingServerArtifacts(Application application) throws IOException {

        // We delete the application exchanges. There is only one now.
        // The DM and inter-applications stuff have each one a single exchange, shared by all the applications.
        this.channel.exchangeDelete(RabbitMqUtils.buildExchangeNameForAgent(this.domain, application.getName()));
        this.logger.fine("Messaging artifacts were deleted for application " + application);
        // Queues are deleted automatically by RabbitMQ.
    }

    @Override
    public void publish(MessagingContext ctx, Message msg) throws IOException {

        // To which exchange?
        String exchangeName = RabbitMqUtils.buildExchangeName(ctx);

        // With which routing key?
        String routingKey = ctx.getTopicName();

        // Log a trace.
        this.logger.fine(
                "A message is about to be published to " + exchangeName + " with routing key = " + routingKey);

        // Special case for the DM sending to the DM.
        // 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.
        BasicProperties props = null;
        if (ctx.getKind() == RecipientKind.DM) {
            props = new BasicProperties.Builder().expiration("500").build();
        }

        // Do we want to be notified when a message is not delivered to anyone?
        // Yes, when the DM sends a message to an agent or when an agent sends a
        // message to the DM. If the message is not delivered to any queue,
        // the client will be notified (RoboconfReturnListener).
        boolean mandatory = false;
        if (this.ownerKind == RecipientKind.DM && this.ownerKind != ctx.getKind()
                || this.ownerKind == RecipientKind.AGENTS && ctx.getKind() == RecipientKind.DM)
            mandatory = true;

        // Send the message.
        this.channel.basicPublish(exchangeName, // The exchange name
                routingKey, // The routing key
                mandatory, // Mandatory => we want it to be delivered
                false, // Useless, RabbitMQ does not support it for now.
                props, // The publish properties
                SerializationUtils.serializeObject(msg));
    }

    @Override
    public void subscribe(MessagingContext ctx) throws IOException {

        // Subscribing means creating a routing key between an exchange and a queue.
        String exchangeName = RabbitMqUtils.buildExchangeName(ctx);
        String queueName = getQueueName();
        this.logger.fine("Binding queue " + queueName + " and exchange " + exchangeName + " with routing key = "
                + ctx.getTopicName());
        this.channel.queueBind(queueName, exchangeName, ctx.getTopicName());
    }

    @Override
    public void unsubscribe(MessagingContext ctx) throws IOException {

        // Un-subscribing means deleting a routing key between an exchange and a queue.
        String exchangeName = RabbitMqUtils.buildExchangeName(ctx);
        String queueName = getQueueName();
        this.logger.fine("Unbinding queue " + queueName + " and exchange " + exchangeName + " with routing key = "
                + ctx.getTopicName());
        this.channel.queueUnbind(queueName, exchangeName, ctx.getTopicName());

    }

    String getQueueName() {

        StringBuilder queueName = new StringBuilder();
        queueName.append(this.domain);
        queueName.append(".");
        if (this.ownerKind == RecipientKind.DM) {
            queueName.append("roboconf-dm");

        } else {
            queueName.append(this.applicationName);
            queueName.append(".");
            queueName.append(MessagingUtils.escapeInstancePath(this.scopedInstancePath));
        }

        return queueName.toString();
    }

    String getId() {
        return MessagingUtils.buildId(this.ownerKind, this.domain, this.applicationName, this.scopedInstancePath);
    }
}