org.smartdeveloperhub.harvesters.it.notification.CollectorController.java Source code

Java tutorial

Introduction

Here is the source code for org.smartdeveloperhub.harvesters.it.notification.CollectorController.java

Source

/**
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   This file is part of the Smart Developer Hub Project:
 *     http://www.smartdeveloperhub.org/
 *
 *   Center for Open Middleware
 *     http://www.centeropenmiddleware.com/
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Copyright (C) 2015-2016 Center for Open Middleware.
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   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.
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Artifact    : org.smartdeveloperhub.harvesters.it:it-harvester-notification:0.1.0
 *   Bundle      : it-harvester-notification-0.1.0.jar
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 */
package org.smartdeveloperhub.harvesters.it.notification;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

import java.io.IOException;
import java.util.Deque;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.smartdeveloperhub.harvesters.it.notification.event.Event;

import com.google.common.base.Optional;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.net.HttpHeaders;
import com.rabbitmq.client.AMQP.Queue.DeclareOk;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.MessageProperties;

final class CollectorController {

    interface Cleaner {

        void clean(Channel channel) throws IOException;

    }

    private static final Logger LOGGER = LoggerFactory.getLogger(CollectorController.class);

    private static final String EXCHANGE_TYPE = "topic";

    private final String queueName;
    private final CollectorConfiguration collector;
    private final BlockingQueue<SuspendedNotification> notificationQueue;

    private final Lock write;

    private final Deque<Cleaner> cleaners;
    private final List<NotificationConsumer> callbacks;

    private String actualQueueName;

    private final ConnectionManager manager;

    private CollectorController(final CollectorConfiguration collector, final String queueName,
            final BlockingQueue<SuspendedNotification> notificationQueue) {
        checkNotNull(collector, "Collector cannot be null");
        this.collector = collector;
        this.manager = new ConnectionManager(collector.getInstance(), collector.getBrokerHost(),
                collector.getBrokerPort(), collector.getVirtualHost());
        this.queueName = queueName;
        this.notificationQueue = notificationQueue;
        final ReadWriteLock lock = new ReentrantReadWriteLock();
        this.write = lock.writeLock();
        this.cleaners = Lists.newLinkedList();
        this.callbacks = Lists.newArrayList();
    }

    CollectorConfiguration collector() {
        return this.collector;
    }

    String queueName() {
        return this.queueName;
    }

    String actualQueueName() {
        this.write.lock();
        try {
            checkState(this.manager.isConnected(), "Not connected");
            return this.actualQueueName;
        } finally {
            this.write.unlock();
        }
    }

    void connect() throws ControllerException {
        this.write.lock();
        try {
            checkState(!this.manager.isConnected(), "Already connected");
            this.manager.connect();
            declareExchange();
            prepareQueue();
        } catch (final ControllerException e) {
            disconnectGracefully();
            throw e;
        } finally {
            this.write.unlock();
        }
    }

    void disconnect() {
        this.write.lock();
        try {
            disconnectGracefully();
        } finally {
            this.write.unlock();
        }
    }

    void publishEvent(final Event event) throws ControllerException {
        try {
            publishEvent(EventUtil.marshall(event), event.getClass().getSimpleName());
        } catch (final IOException e) {
            throw new ControllerException(this.collector.getBrokerHost(), this.collector.getBrokerPort(),
                    this.collector.getVirtualHost(), "Could not serialize event " + event, e);
        }
    }

    void publishEvent(final String event, final String eventType) throws ControllerException {
        this.write.lock();
        try {
            checkState(this.manager.isConnected(), "Not connected");
            final Channel aChannel = this.manager.currentChannel();
            aChannel.addReturnListener(new LoggingReturnListener());
            publish(aChannel, this.collector.getExchangeName(), Notifications.routingKey(eventType), event);
        } finally {
            this.write.unlock();
        }
    }

    private void publish(final Channel aChannel, final String exchangeName, final String routingKey,
            final String payload) throws ControllerException {
        try {
            LOGGER.trace("Publishing message to exchange '{}' and routing key '{}'. Payload: \n{}", exchangeName,
                    routingKey, payload);
            final Map<String, Object> headers = Maps.newLinkedHashMap();
            headers.put(HttpHeaders.CONTENT_TYPE, Notifications.MIME);
            aChannel.basicPublish(exchangeName, routingKey, true,
                    MessageProperties.MINIMAL_PERSISTENT_BASIC.builder().headers(headers).build(),
                    payload.getBytes());
        } catch (final IOException e) {
            this.manager.discardChannel();
            final String errorMessage = String.format(
                    "Could not publish message [%s] to exchange '%s' and routing key '%s' using broker %s:%s%s: %s",
                    payload, exchangeName, routingKey, this.collector.getBrokerHost(),
                    this.collector.getBrokerPort(), this.collector.getVirtualHost(), e.getMessage());
            LOGGER.error(errorMessage, e);
            throw new ControllerException(this.collector.getBrokerHost(), this.collector.getBrokerPort(),
                    this.collector.getVirtualHost(), errorMessage, e);
        } catch (final Exception e) {
            this.manager.discardChannel();
            final String errorMessage = String.format(
                    "Unexpected failure while publishing message [%s] to exchange '%s' and routing key '%s' using broker %s:%s%s: %s",
                    payload, exchangeName, routingKey, this.collector.getBrokerHost(),
                    this.collector.getBrokerPort(), this.collector.getVirtualHost(), e.getMessage());
            LOGGER.error(errorMessage, e);
            throw new ControllerException(this.collector.getBrokerHost(), this.collector.getBrokerPort(),
                    this.collector.getVirtualHost(), errorMessage, e);
        }
    }

    private void prepareQueue() throws ControllerException {
        if (this.notificationQueue != null) {
            this.actualQueueName = declareQueue();
            bindQueue(this.actualQueueName);
            try {
                final Channel currentChannel = this.manager.channel();
                final NotificationConsumer callback = new NotificationConsumer(currentChannel,
                        this.notificationQueue);
                currentChannel.basicConsume(this.actualQueueName, false, callback);
                this.callbacks.add(callback);
            } catch (final IOException e) {
                throw new ControllerException(this.collector.getBrokerHost(), this.collector.getBrokerPort(),
                        this.collector.getVirtualHost(),
                        "Could not register consumer for queue '" + this.actualQueueName + "'", e);
            }
        }
    }

    /**
     * The a durable topic exchange is always declared
     */
    private void declareExchange() throws ControllerException {
        try {
            this.manager.channel().exchangeDeclare(this.collector.getExchangeName(), EXCHANGE_TYPE, true);
        } catch (final IOException e) {
            if (!FailureAnalyzer.isExchangeDeclarationRecoverable(e)) {
                throw new ControllerException(this.collector.getBrokerHost(), this.collector.getBrokerPort(),
                        this.collector.getVirtualHost(),
                        "Could not create exchange named '" + this.collector.getExchangeName() + "'", e);
            }
        }
    }

    /**
     * The declared queues are durable, exclusive, and auto-delete, and expire if no
     * client uses them after 1 second.
     */
    private String declareQueue() throws ControllerException {
        final String targetQueueName = Optional.fromNullable(this.queueName).or("");
        try {
            final Map<String, Object> args = ImmutableMap.<String, Object>builder().put("x-expires", 1000).build();
            final DeclareOk ok = this.manager.channel().queueDeclare(targetQueueName, true, true, true, args);
            final String declaredQueueName = ok.getQueue();
            this.cleaners.push(CleanerFactory.queueDelete(declaredQueueName));
            return declaredQueueName;
        } catch (final IOException e) {
            throw new ControllerException(this.collector.getBrokerHost(), this.collector.getBrokerPort(),
                    this.collector.getVirtualHost(), "Could not create queue named '" + targetQueueName + "'", e);
        }
    }

    private void bindQueue(final String queueName) throws ControllerException {
        try {
            this.manager.channel().queueBind(queueName, this.collector.getExchangeName(),
                    Notifications.ROUTING_KEY_PATTERN);
            this.cleaners.push(CleanerFactory.queueUnbind(this.collector.getExchangeName(), queueName,
                    Notifications.ROUTING_KEY_PATTERN));
        } catch (final IOException e) {
            throw new ControllerException(this.collector.getBrokerHost(), this.collector.getBrokerPort(),
                    this.collector.getVirtualHost(),
                    "Could not bind queue '" + queueName + "' to exchange '" + this.collector.getExchangeName()
                            + "' using routing key '" + Notifications.ROUTING_KEY_PATTERN + "'",
                    e);
        }
    }

    private void disconnectGracefully() {
        if (this.manager.isConnected()) {
            cleanUp();
            this.callbacks.clear();
            this.actualQueueName = null;
            this.manager.disconnect();
        }
    }

    private void cleanUp() {
        LOGGER.debug("Cleaning up broker ({})...", this.cleaners.size());
        while (!this.cleaners.isEmpty()) {
            final Cleaner cleaner = this.cleaners.pop();
            try {
                cleaner.clean(this.manager.channel());
                LOGGER.trace("{} completed", cleaner);
            } catch (final Exception e) {
                LOGGER.warn("{} failed. Full stacktrace follows", cleaner, e);
            }
        }
        LOGGER.debug("Broker clean-up completed.", this.cleaners.size());
    }

    static CollectorController createPublisher(final CollectorConfiguration collector) {
        return new CollectorController(collector, null, null);
    }

    static CollectorController createAnonymousReceiver(final CollectorConfiguration collector,
            final BlockingQueue<SuspendedNotification> queue) {
        checkNotNull(queue, "Notification queue cannot be null");
        return new CollectorController(collector, null, queue);
    }

    static CollectorController createNamedReceiver(final CollectorConfiguration collector, final String queueName,
            final BlockingQueue<SuspendedNotification> queue) {
        Amqp.validateName(queueName, "Queue name");
        checkNotNull(queue, "Notification queue cannot be null");
        return new CollectorController(collector, queueName, queue);
    }

}