terrastore.event.impl.ActiveMQEventBus.java Source code

Java tutorial

Introduction

Here is the source code for terrastore.event.impl.ActiveMQEventBus.java

Source

/**
 * Copyright 2009 - 2011 Sergio Bossa (sergio.bossa@gmail.com)
 *
 *    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 terrastore.event.impl;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.ObjectMessage;
import javax.jms.Session;
import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.advisory.DestinationSource;
import org.apache.activemq.command.ActiveMQQueue;
import org.apache.activemq.pool.PooledConnectionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;
import org.springframework.jms.listener.DefaultMessageListenerContainer;
import terrastore.event.ActionExecutor;
import terrastore.event.Event;
import terrastore.event.EventBus;
import terrastore.event.EventListener;

/**
 * Durable and reliable {@link terrastore.event.EventBus} implementation based on the ActiveMQ message broker.
 * <br><br>
 * All events related to the same bucket are synchronously enqueued in the same JMS queue, named terrastore.<em>bucketName</em>,
 * and asynchronously processed by configured {@link terrastore.event.EventListener}s on a casual Terrastore node;
 * so, events enqueued on a given node may be concurrently processed on different nodes.<br>
 * However, {@link terrastore.event.Event}s referring to the same key in the same bucket are guaranteed to be sequentially processed in FIFO order,
 * to preserve per-document consistency.
 * <br><br>
 * Failing {@link terrastore.event.EventListener}s cause event redelivery: as a consequence, the same event may be processed several times by previously
 * successful listeners, and you should implement your listeners to be idempotent (for example by taking {@link terrastore.event.Event#getId()} into account).
 *
 * @author Sergio Bossa
 */
public class ActiveMQEventBus implements EventBus {

    private static final String TERRASTORE_QUEUE_PREFIX = "terrastore.";
    //
    private static final Logger LOG = LoggerFactory.getLogger(ActiveMQEventBus.class);
    //
    private final ConcurrentMap<String, DefaultMessageListenerContainer> consumers = new ConcurrentHashMap<String, DefaultMessageListenerContainer>();
    private final Lock stateLock = new ReentrantLock();
    //
    private final ConnectionFactory jmsConnectionFactory;
    private final JmsTemplate producer;
    private final List<EventListener> eventListeners;
    private final ActionExecutor actionExecutor;
    private final boolean enabled;
    private volatile boolean shutdown;

    public ActiveMQEventBus(List<EventListener> eventListeners, ActionExecutor actionExecutor, String broker)
            throws Exception {
        LOG.info("Configuring event bus: {}", this.getClass().getName());
        this.eventListeners = eventListeners;
        this.actionExecutor = actionExecutor;
        this.jmsConnectionFactory = new PooledConnectionFactory(broker);
        this.producer = new JmsTemplate(jmsConnectionFactory);
        this.enabled = this.eventListeners.size() > 0;
        initListeners(this.eventListeners);
        initConsumers(broker);
    }

    @Override
    public ActionExecutor getActionExecutor() {
        return actionExecutor;
    }

    @Override
    public List<EventListener> getEventListeners() {
        return Collections.unmodifiableList(eventListeners);
    }

    @Override
    public void shutdown() {
        if (!shutdown) {
            stateLock.lock();
            try {
                for (DefaultMessageListenerContainer consumer : consumers.values()) {
                    consumer.shutdown();
                }
                shutdown = true;
            } finally {
                stateLock.unlock();
            }
        } else {
            throw new IllegalStateException("The bus has been shutdown!");
        }
    }

    @Override
    public void publish(Event event) {
        if (enabled && !shutdown) {
            LOG.debug("Publishing event for bucket {} and value {}", event.getBucket(), event.getKey());
            createConsumer(event);
            enqueue(event);
        } else if (shutdown) {
            throw new IllegalStateException("The bus has been shutdown!");
        }
    }

    @Override
    public boolean isEnabled() {
        return eventListeners.size() > 0;
    }

    private void initListeners(List<EventListener> eventListeners) {
        for (EventListener listener : eventListeners) {
            LOG.info("Configuring listener: {}", listener.getClass().getName());
            listener.init();
        }
    }

    private void initConsumers(String broker) throws Exception {
        ActiveMQConnection connection = null;
        DestinationSource destinations = null;
        try {
            connection = ActiveMQConnection.makeConnection(broker);
            connection.start();
            //
            destinations = connection.getDestinationSource();
            destinations.start();
            //
            Set<ActiveMQQueue> queues = destinations.getQueues();
            for (ActiveMQQueue queue : queues) {
                if (queue.getQueueName().startsWith(TERRASTORE_QUEUE_PREFIX)) {
                    LOG.info("Listening to queue: {}", queue.getQueueName());
                    DefaultMessageListenerContainer consumer = new DefaultMessageListenerContainer();
                    consumer.setConnectionFactory(jmsConnectionFactory);
                    consumer.setSessionTransacted(true);
                    consumer.setMessageListener(new EventProcessor(eventListeners, actionExecutor));
                    consumer.setDestinationName(queue.getQueueName());
                    consumer.start();
                    consumer.initialize();
                    consumers.put(queue.getQueueName(), consumer);
                }
            }
        } finally {
            if (destinations != null) {
                destinations.stop();
            }
            if (connection != null) {
                connection.stop();
                connection.close();
            }
        }
    }

    private void createConsumer(Event event) {
        String queueName = TERRASTORE_QUEUE_PREFIX + event.getBucket();
        if (!consumers.containsKey(queueName)) {
            stateLock.lock();
            try {
                if (!shutdown && !consumers.containsKey(queueName)) {
                    DefaultMessageListenerContainer consumer = new DefaultMessageListenerContainer();
                    consumer.setConnectionFactory(jmsConnectionFactory);
                    consumer.setSessionTransacted(true);
                    consumer.setMessageListener(new EventProcessor(eventListeners, actionExecutor));
                    consumer.setDestinationName(queueName);
                    consumer.start();
                    consumer.initialize();
                    consumers.put(queueName, consumer);
                }
            } finally {
                stateLock.unlock();
            }
        }
    }

    private void enqueue(final Event event) {
        String queueName = TERRASTORE_QUEUE_PREFIX + event.getBucket();
        producer.send(queueName, new MessageCreator() {

            @Override
            public Message createMessage(Session session) throws JMSException {
                Message message = session.createObjectMessage(event);
                message.setStringProperty("JMSXGroupID", new StringBuilder().append(event.getBucket()).append(":")
                        .append(event.getKey()).toString());
                return message;
            }

        });
    }

    private static class EventProcessor implements MessageListener {

        private final List<EventListener> eventListeners;
        private final ActionExecutor actionExecutor;

        public EventProcessor(List<EventListener> eventListeners, ActionExecutor actionExecutor) {
            this.eventListeners = new ArrayList<EventListener>(eventListeners);
            this.actionExecutor = actionExecutor;
        }

        @Override
        public void onMessage(Message message) {
            try {
                Event event = (Event) ((ObjectMessage) message).getObject();
                dispatch(event);
            } catch (JMSException ex) {
                // TODO: better handling?
                LOG.warn(ex.getMessage(), ex);
            }
        }

        private void dispatch(Event event) {
            for (EventListener listener : eventListeners) {
                if (listener.observes(event.getBucket())) {
                    event.dispatch(listener, actionExecutor);
                }
            }
        }

    }
}