org.seedstack.seed.jms.internal.JmsPlugin.java Source code

Java tutorial

Introduction

Here is the source code for org.seedstack.seed.jms.internal.JmsPlugin.java

Source

/**
 * Copyright (c) 2013-2015 by The SeedStack authors. All rights reserved.
 *
 * This file is part of SeedStack, An enterprise-oriented full development stack.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */
package org.seedstack.seed.jms.internal;

import io.nuun.kernel.api.Plugin;
import io.nuun.kernel.api.plugin.InitState;
import io.nuun.kernel.api.plugin.context.InitContext;
import io.nuun.kernel.api.plugin.request.ClasspathScanRequest;
import io.nuun.kernel.core.AbstractPlugin;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.lang.StringUtils;
import org.kametic.specifications.Specification;
import org.seedstack.seed.core.api.SeedException;
import org.seedstack.seed.core.internal.application.ApplicationPlugin;
import org.seedstack.seed.core.internal.jndi.JndiPlugin;
import org.seedstack.seed.core.utils.SeedCheckUtils;
import org.seedstack.seed.jms.api.JmsMessageListener;
import org.seedstack.seed.jms.spi.ConnectionDefinition;
import org.seedstack.seed.jms.spi.JmsErrorCodes;
import org.seedstack.seed.jms.spi.JmsExceptionHandler;
import org.seedstack.seed.jms.spi.JmsFactory;
import org.seedstack.seed.jms.spi.MessageListenerDefinition;
import org.seedstack.seed.jms.spi.MessagePoller;
import org.seedstack.seed.transaction.internal.TransactionPlugin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.jms.Connection;
import javax.jms.Destination;
import javax.jms.ExceptionListener;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.Session;
import javax.naming.Context;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * This plugin provides JMS support through JNDI or plain configuration.
 *
 * @author emmanuel.vinel@mpsa.com
 * @author adrien.lauer@mpsa.com
 * @author redouane.loulou@ext.mpsa.com
 */
public class JmsPlugin extends AbstractPlugin {
    private static final Logger LOGGER = LoggerFactory.getLogger(JmsPlugin.class);

    public static final String JMS_PLUGIN_CONFIGURATION_PREFIX = "org.seedstack.seed.jms";
    public static final String ERROR_CONNECTION_NAME = "connectionName";
    public static final String ERROR_MESSAGE_LISTENER_NAME = "messageListenerName";

    @SuppressWarnings("unchecked")
    private final Specification<Class<?>> messageListenerSpec = and(classImplements(MessageListener.class),
            classAnnotatedWith(JmsMessageListener.class));
    private final Specification<Class<?>> exceptionListenerSpec = classImplements(ExceptionListener.class);
    private final Specification<Class<?>> exceptionHandlerSpec = classImplements(JmsExceptionHandler.class);

    private final ConcurrentMap<String, MessageListenerDefinition> messageListenerDefinitions = new ConcurrentHashMap<String, MessageListenerDefinition>();
    private final ConcurrentMap<String, ConnectionDefinition> connectionDefinitions = new ConcurrentHashMap<String, ConnectionDefinition>();

    private final ConcurrentMap<String, Connection> connections = new ConcurrentHashMap<String, Connection>();
    private final ConcurrentMap<String, MessagePoller> pollers = new ConcurrentHashMap<String, MessagePoller>();

    private final AtomicBoolean shouldStartConnections = new AtomicBoolean(false);

    private JmsFactory jmsFactory;
    private Configuration jmsConfiguration;
    private TransactionPlugin transactionPlugin;
    private Map<String, Context> jndiContexts;

    @Override
    public String name() {
        return "seed-jms-plugin";
    }

    @Override
    public InitState init(InitContext initContext) {
        String applicationId = null;
        transactionPlugin = null;
        for (Plugin plugin : initContext.pluginsRequired()) {
            if (plugin instanceof ApplicationPlugin) {
                jmsConfiguration = ((ApplicationPlugin) plugin).getApplication().getConfiguration()
                        .subset(JmsPlugin.JMS_PLUGIN_CONFIGURATION_PREFIX);
                applicationId = ((ApplicationPlugin) plugin).getApplication().getId();
            } else if (plugin instanceof TransactionPlugin) {
                transactionPlugin = ((TransactionPlugin) plugin);
            } else if (plugin instanceof JndiPlugin) {
                jndiContexts = ((JndiPlugin) plugin).getJndiContexts();
            }
        }

        if (jmsConfiguration == null || applicationId == null) {
            throw SeedException.createNew(JmsErrorCodes.PLUGIN_NOT_FOUND).put("plugin", "application");
        }
        if (transactionPlugin == null) {
            throw SeedException.createNew(JmsErrorCodes.PLUGIN_NOT_FOUND).put("plugin", "transaction");
        }

        jmsFactory = new JmsFactoryImpl(applicationId, jmsConfiguration, jndiContexts);

        configureConnections(jmsConfiguration.getStringArray("connections"));

        configureMessageListeners(initContext.scannedTypesBySpecification().get(messageListenerSpec));

        return InitState.INITIALIZED;
    }

    @Override
    public void start(io.nuun.kernel.api.plugin.context.Context context) {
        shouldStartConnections.set(true);

        for (Map.Entry<String, Connection> connection : this.connections.entrySet()) {
            try {
                connection.getValue().start();
            } catch (JMSException e) {
                throw SeedException.wrap(e, JmsErrorCodes.UNABLE_TO_START_JMS_CONNECTION).put(ERROR_CONNECTION_NAME,
                        connection.getKey());
            }
        }

        for (MessagePoller messagePoller : pollers.values()) {
            messagePoller.start();
        }
    }

    @Override
    public void stop() {
        shouldStartConnections.set(false);

        for (MessagePoller messagePoller : pollers.values()) {
            messagePoller.stop();
        }

        for (Map.Entry<String, Connection> connection : this.connections.entrySet()) {
            try {
                connection.getValue().close();
            } catch (JMSException e) {
                LOGGER.error("Unable to cleanly stop JMS connection " + connection.getKey(), e);
            }
        }
    }

    @Override
    public Collection<Class<? extends Plugin>> requiredPlugins() {
        Collection<Class<? extends Plugin>> plugins = new ArrayList<Class<? extends Plugin>>();
        plugins.add(ApplicationPlugin.class);
        plugins.add(TransactionPlugin.class);
        plugins.add(JndiPlugin.class);
        return plugins;
    }

    @Override
    public Collection<ClasspathScanRequest> classpathScanRequests() {
        return classpathScanRequestBuilder().specification(messageListenerSpec).specification(exceptionListenerSpec)
                .specification(exceptionHandlerSpec).build();
    }

    @Override
    public Object nativeUnitModule() {
        return new JmsModule(jmsFactory, connections, connectionDefinitions, messageListenerDefinitions,
                pollers.values());
    }

    @SuppressWarnings("unchecked")
    private void configureConnections(String[] connectionNames) {
        for (String connectionName : connectionNames) {
            try {
                ConnectionDefinition connectionDefinition = jmsFactory.createConnectionDefinition(connectionName,
                        jmsConfiguration.subset("connection." + connectionName), null);

                registerConnection(jmsFactory.createConnection(connectionDefinition), connectionDefinition);
            } catch (JMSException e) {
                throw SeedException.wrap(e, JmsErrorCodes.UNABLE_TO_CREATE_JMS_CONNECTION)
                        .put(ERROR_CONNECTION_NAME, connectionName);
            }
        }
    }

    @SuppressWarnings("unchecked")
    private void configureMessageListeners(Collection<Class<?>> listenerCandidates) {
        for (Class<?> candidate : listenerCandidates) {
            if (MessageListener.class.isAssignableFrom(candidate)) {
                Class<? extends MessageListener> messageListenerClass = (Class<? extends MessageListener>) candidate;
                String messageListenerName = messageListenerClass.getCanonicalName();
                JmsMessageListener annotation = messageListenerClass.getAnnotation(JmsMessageListener.class);

                boolean isTransactional;
                try {
                    isTransactional = transactionPlugin
                            .isTransactional(messageListenerClass.getMethod("onMessage", Message.class));
                } catch (NoSuchMethodException e) {
                    throw SeedException.wrap(e, JmsErrorCodes.UNEXPECTED_EXCEPTION);
                }

                Connection listenerConnection = connections.get(annotation.connection());

                if (listenerConnection == null) {
                    throw SeedException.createNew(JmsErrorCodes.MISSING_CONNECTION_FACTORY)
                            .put(ERROR_CONNECTION_NAME, annotation.connection())
                            .put(ERROR_MESSAGE_LISTENER_NAME, messageListenerName);
                }

                Session session;
                try {
                    session = listenerConnection.createSession(isTransactional, Session.AUTO_ACKNOWLEDGE);
                } catch (JMSException e) {
                    throw SeedException.wrap(e, JmsErrorCodes.UNABLE_TO_CREATE_SESSION)
                            .put(ERROR_CONNECTION_NAME, annotation.connection())
                            .put(ERROR_MESSAGE_LISTENER_NAME, messageListenerName);
                }

                Destination destination;
                try {
                    switch (annotation.destinationType()) {
                    case QUEUE:
                        destination = session.createQueue(annotation.destinationName());
                        break;
                    case TOPIC:
                        destination = session.createTopic(annotation.destinationName());
                        break;
                    default:
                        throw SeedException.createNew(JmsErrorCodes.UNKNOWN_DESTINATION_TYPE)
                                .put(ERROR_CONNECTION_NAME, annotation.connection())
                                .put(ERROR_MESSAGE_LISTENER_NAME, messageListenerName);
                    }
                } catch (JMSException e) {
                    throw SeedException.wrap(e, JmsErrorCodes.UNABLE_TO_CREATE_DESTINATION)
                            .put(ERROR_CONNECTION_NAME, annotation.connection())
                            .put(ERROR_MESSAGE_LISTENER_NAME, messageListenerName);
                }

                Class<? extends MessagePoller> messagePollerClass = null;
                if (annotation.poller().length > 0) {
                    messagePollerClass = annotation.poller()[0];
                }

                registerMessageListener(new MessageListenerDefinition(messageListenerName, annotation.connection(),
                        session, destination, annotation.selector(), messageListenerClass, messagePollerClass));
            }
        }
    }

    private MessageConsumer createMessageConsumer(MessageListenerDefinition messageListenerDefinition)
            throws JMSException {
        LOGGER.debug("Creating JMS consumer for listener {}", messageListenerDefinition.getName());

        MessageConsumer consumer;
        Session session = messageListenerDefinition.getSession();

        if (StringUtils.isNotBlank(messageListenerDefinition.getSelector())) {
            consumer = session.createConsumer(messageListenerDefinition.getDestination(),
                    messageListenerDefinition.getSelector());
        } else {
            consumer = session.createConsumer(messageListenerDefinition.getDestination());
        }

        MessagePoller messagePoller;
        if (messageListenerDefinition.getPoller() != null) {
            try {
                LOGGER.debug("Creating poller for JMS listener {}", messageListenerDefinition.getName());

                Connection connection = connections.get(messageListenerDefinition.getConnectionName());

                messagePoller = messageListenerDefinition.getPoller().newInstance();
                messagePoller.setSession(session);
                messagePoller.setMessageConsumer(consumer);
                messagePoller.setMessageListener(new MessageListenerAdapter(messageListenerDefinition.getName()));

                if (connection instanceof ManagedConnection) {
                    messagePoller.setExceptionListener((ExceptionListener) connection);
                } else {
                    messagePoller.setExceptionListener(connection.getExceptionListener());
                }
            } catch (Exception e) {
                throw SeedException.wrap(e, JmsErrorCodes.UNABLE_TO_CREATE_POLLER).put("pollerClass",
                        messageListenerDefinition.getPoller());
            }

            pollers.put(messageListenerDefinition.getName(), messagePoller);
        } else {
            consumer.setMessageListener(new MessageListenerAdapter(messageListenerDefinition.getName()));
        }

        return consumer;
    }

    /**
     * Register an existing JMS connection to be managed by the JMS plugin.
     *
     * @param connection           the connection.
     * @param connectionDefinition the connection definition.
     */
    public void registerConnection(Connection connection, ConnectionDefinition connectionDefinition) {
        SeedCheckUtils.checkIfNotNull(connection);
        SeedCheckUtils.checkIfNotNull(connectionDefinition);

        if (this.connectionDefinitions.putIfAbsent(connectionDefinition.getName(), connectionDefinition) != null) {
            throw SeedException.createNew(JmsErrorCodes.DUPLICATE_CONNECTION_NAME).put(ERROR_CONNECTION_NAME,
                    connectionDefinition.getName());
        }

        if (this.connections.putIfAbsent(connectionDefinition.getName(), connection) != null) {
            throw SeedException.createNew(JmsErrorCodes.DUPLICATE_CONNECTION_NAME).put(ERROR_CONNECTION_NAME,
                    connectionDefinition.getName());
        }

        if (shouldStartConnections.get()) {
            try {
                connection.start();
            } catch (JMSException e) {
                throw SeedException.wrap(e, JmsErrorCodes.UNABLE_TO_START_JMS_CONNECTION).put(ERROR_CONNECTION_NAME,
                        connectionDefinition.getName());
            }
        }
    }

    /**
     * Register a message listener definition to be managed by the JMS plugin.
     *
     * @param messageListenerDefinition the message listener definition.
     */
    public void registerMessageListener(MessageListenerDefinition messageListenerDefinition) {
        SeedCheckUtils.checkIfNotNull(messageListenerDefinition);

        ConnectionDefinition connectionDefinition = connectionDefinitions
                .get(messageListenerDefinition.getConnectionName());
        if (connectionDefinition.isJeeMode() && messageListenerDefinition.getPoller() == null) {
            throw SeedException.createNew(JmsErrorCodes.MESSAGE_POLLER_REQUIRED_IN_JEE_MODE)
                    .put(ERROR_CONNECTION_NAME, connectionDefinition.getName())
                    .put(ERROR_MESSAGE_LISTENER_NAME, messageListenerDefinition.getName());
        }

        try {
            createMessageConsumer(messageListenerDefinition);
        } catch (JMSException e) {
            throw SeedException.wrap(e, JmsErrorCodes.UNABLE_TO_CREATE_MESSAGE_CONSUMER)
                    .put(ERROR_MESSAGE_LISTENER_NAME, messageListenerDefinition.getName());
        }

        if (messageListenerDefinitions.putIfAbsent(messageListenerDefinition.getName(),
                messageListenerDefinition) != null) {
            throw SeedException.createNew(JmsErrorCodes.DUPLICATE_MESSAGE_LISTENER_DEFINITION_NAME)
                    .put(ERROR_MESSAGE_LISTENER_NAME, messageListenerDefinition.getName());
        }
    }

    /**
     * Retrieve a connection by name.
     *
     * @param name the name of the connection to retrieve.
     * @return the connection or null if it doesn't exists.
     */
    public Connection getConnection(String name) {
        return connections.get(name);
    }

    /**
     * Return the factory used to create JMS objects.
     *
     * @return the JMS factory.
     */
    public JmsFactory getJmsFactory() {
        return jmsFactory;
    }
}