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

Java tutorial

Introduction

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

Source

/**
 * Copyright (c) 2013-2016, The SeedStack authors <http://seedstack.org>
 *
 * 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.jms.internal;

import com.google.common.collect.Lists;
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.jms.DestinationType;
import org.seedstack.jms.JmsMessageListener;
import org.seedstack.jms.spi.*;
import org.seedstack.seed.Application;
import org.seedstack.seed.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.transaction.internal.TransactionPlugin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.jms.*;
import javax.naming.Context;
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.jms";
    public static final String ERROR_CONNECTION_NAME = "connectionName";
    public static final String ERROR_MESSAGE_LISTENER_NAME = "messageListenerName";
    public static final String ERROR_DESTINATION_TYPE = "destinationType";

    @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 Application application;
    private Configuration jmsConfiguration;
    private TransactionPlugin transactionPlugin;

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

    @Override
    public InitState init(InitContext initContext) {
        transactionPlugin = initContext.dependency(TransactionPlugin.class);
        application = initContext.dependency(ApplicationPlugin.class).getApplication();
        jmsConfiguration = application.getConfiguration().subset(JmsPlugin.JMS_PLUGIN_CONFIGURATION_PREFIX);
        Map<String, Context> jndiContexts = initContext.dependency(JndiPlugin.class).getJndiContexts();

        jmsFactory = new JmsFactoryImpl(application.getId(), 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<?>> requiredPlugins() {
        return Lists.<Class<?>>newArrayList(ApplicationPlugin.class, TransactionPlugin.class, JndiPlugin.class);
    }

    @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());
    }

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

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

    private void configureMessageListeners(Collection<Class<?>> listenerCandidates) {
        for (Class<?> candidate : listenerCandidates) {
            if (MessageListener.class.isAssignableFrom(candidate)) {
                //noinspection unchecked
                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;
                DestinationType destinationType;

                if (!annotation.destinationTypeStr().isEmpty()) {
                    try {
                        destinationType = DestinationType
                                .valueOf(application.substituteWithConfiguration(annotation.destinationTypeStr()));
                    } catch (IllegalArgumentException e) {
                        throw SeedException.wrap(e, JmsErrorCodes.UNKNOWN_DESTINATION_TYPE)
                                .put(ERROR_DESTINATION_TYPE, annotation.destinationTypeStr())
                                .put(ERROR_CONNECTION_NAME, annotation.connection())
                                .put(ERROR_MESSAGE_LISTENER_NAME, messageListenerName);
                    }
                } else {
                    destinationType = annotation.destinationType();
                }
                try {
                    switch (destinationType) {
                    case QUEUE:
                        destination = session
                                .createQueue(application.substituteWithConfiguration(annotation.destinationName()));
                        break;
                    case TOPIC:
                        destination = session
                                .createTopic(application.substituteWithConfiguration(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_DESTINATION_TYPE, destinationType.name())
                            .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,
                        application.substituteWithConfiguration(annotation.connection()), session, destination,
                        application.substituteWithConfiguration(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;
    }
}