com.cisco.oss.foundation.message.HornetQMessagingFactory.java Source code

Java tutorial

Introduction

Here is the source code for com.cisco.oss.foundation.message.HornetQMessagingFactory.java

Source

/*
 * Copyright 2015 Cisco Systems, Inc.
 *
 *  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 com.cisco.oss.foundation.message;

import com.cisco.oss.foundation.configuration.ConfigUtil;
import com.cisco.oss.foundation.configuration.ConfigurationFactory;
import com.cisco.oss.foundation.configuration.FoundationConfigurationListener;
import com.cisco.oss.foundation.configuration.FoundationConfigurationListenerRegistry;
import com.google.common.collect.Lists;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.hornetq.api.core.HornetQException;
import org.hornetq.api.core.TransportConfiguration;
import org.hornetq.api.core.client.*;
import org.hornetq.api.core.management.ObjectNameBuilder;
import org.hornetq.api.jms.management.JMSServerControl;
import org.hornetq.core.remoting.impl.netty.NettyConnectorFactory;
import org.hornetq.utils.VersionLoader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.management.MBeanServerConnection;
import javax.management.MBeanServerInvocationHandler;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import java.text.MessageFormat;
import java.util.*;
import java.util.concurrent.*;

/**
 * This is the main API tp be used to instantiate new consumers and producers.
 * This class supports a Per Thread lifecycle for HornetQ session, consumers and producers
 * Created by Yair Ogen on 24/04/2014.
 */
public class HornetQMessagingFactory {

    private static final Logger LOGGER = LoggerFactory.getLogger(HornetQMessagingFactory.class);
    private static final List<ClientSession> sessions = new CopyOnWriteArrayList<>();
    public static ThreadLocal<List<Pair<ClientSession, SessionFailureListener>>> sessionThreadLocal = new ThreadLocal<List<Pair<ClientSession, SessionFailureListener>>>();
    public static CountDownLatch INIT_READY = new CountDownLatch(1);

    private static List<ServerLocator> serverLocators = new CopyOnWriteArrayList<>();
    private static List<ClientSession> clientSessions = new CopyOnWriteArrayList<>();
    private static Map<String, MessageConsumer> consumers = new ConcurrentHashMap<String, MessageConsumer>();
    private static Map<String, MessageProducer> producers = new ConcurrentHashMap<String, MessageProducer>();
    private static TransportConfiguration[] transportConfigurationsArray = null;

    static {

        FoundationConfigurationListenerRegistry
                .addFoundationConfigurationListener(new FoundationConfigurationListener() {
                    @Override
                    public void configurationChanged() {
                        HornetQMessageConsumer.consumerThreadLocal.remove();
                        sessionThreadLocal.remove();
                        init();
                    }
                });

        init();

        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                try {
                    //allow possible background work to finish
                    Thread.sleep(1000);
                    if (!sessions.isEmpty()) {
                        for (Map.Entry<String, MessageConsumer> consumer : consumers.entrySet()) {
                            consumer.getValue().close();
                        }
                        for (Map.Entry<String, MessageProducer> producer : producers.entrySet()) {
                            producer.getValue().close();
                        }
                        for (ClientSession session : sessions) {
                            session.close();
                        }

                        for (ClientSession session : clientSessions) {
                            session.close();
                        }
                    }
                } catch (Exception e) {
                    LOGGER.error("can't close HornetQ resources, error: {}", e, e);
                }
            }
        });
    }

    public static List<Pair<ClientSession, SessionFailureListener>> getSession(
            Class<? extends SessionFailureListener> sessionFailureListener) {

        if (sessionThreadLocal.get() == null) {
            try {
                LOGGER.debug("creating a new session");
                List<Pair<ClientSession, SessionFailureListener>> hornetQSessions = new ArrayList<>();
                for (ServerLocator serverLocator : serverLocators) {
                    ClientSession hornetQSession = serverLocator.createSessionFactory().createSession(true, true);
                    SessionFailureListener listener = null;
                    if (sessionFailureListener != null) {
                        listener = sessionFailureListener.newInstance();
                        hornetQSession.addFailureListener(listener);
                    }
                    hornetQSession.start();
                    sessions.add(hornetQSession);
                    hornetQSessions.add(Pair.of(hornetQSession, listener));
                }
                sessionThreadLocal.set(hornetQSessions);

            } catch (Exception e) {
                LOGGER.error("can't create hornetq session: {}", e, e);
                throw new QueueException(e);
            }
        }
        return sessionThreadLocal.get();

    }

    /**
     * create a new session if one doesn't already exist in the ThreadLocal
     *
     * @return a new hornetq session
     */
    public static List<Pair<ClientSession, SessionFailureListener>> getSession() {
        return getSession(null);
    }

    //    private static boolean sessionIsClosed(ClientSession clientSession) {
    //        if(clientSession != null){
    //            if(clientSession instanceof DelegatingSession){
    //                DelegatingSession delegatingSession = (DelegatingSession)clientSession;
    //                try {
    //                    Field closed = delegatingSession.getClass().getDeclaredField("closed");
    //                    closed.setAccessible(true);
    //                    return (boolean)closed.get(delegatingSession);
    //                } catch (Exception e) {
    //                    LOGGER.trace("unable to get closed state from session: {}", e, e);
    //                }
    //            }else{
    //                return clientSession.isClosed();
    //            }
    //        }
    //        return true;
    //    }

    /**
     * build once the hornetq service locator.
     * this is where we read the ost port list from configuration
     */
    private static void init() {
        try {

            cleanup();

            //            nettyFactory = HornetQClient.createServerLocatorWithoutHA(new TransportConfiguration(NettyConnectorFactory.class.getName())).createSessionFactory();
            Configuration configuration = ConfigurationFactory.getConfiguration();
            Configuration subset = configuration.subset("service.hornetq.connections");

            final Map<String, Map<String, String>> serverConnections = ConfigUtil
                    .parseComplexArrayStructure("service.hornetq.connections");
            boolean isVersionPrinted = false;
            if (serverConnections != null && !serverConnections.isEmpty()) {
                if (isActiveActiveMode(subset)) {
                    final ArrayList<String> serverConnectionKeys = Lists.newArrayList(serverConnections.keySet());
                    Collections.sort(serverConnectionKeys);
                    for (String serverConnectionKey : serverConnectionKeys) {
                        String host1Param = "service.hornetq.connections." + serverConnectionKey
                                + ".instance1.host";
                        String port1Param = "service.hornetq.connections." + serverConnectionKey
                                + ".instance1.port";
                        String jmxPort1Param = "service.hornetq.connections." + serverConnectionKey
                                + ".instance1.jmxPort";
                        String host2Param = "service.hornetq.connections." + serverConnectionKey
                                + ".instance2.host";
                        String port2Param = "service.hornetq.connections." + serverConnectionKey
                                + ".instance2.port";
                        String host1 = configuration.getString(host1Param, null);
                        String port1 = configuration.getString(port1Param, null);
                        String host2 = configuration.getString(host2Param, null);
                        String port2 = configuration.getString(port2Param, null);

                        if (StringUtils.isAnyBlank(host1Param, host2Param, port1Param, port2Param)) {
                            throw new IllegalArgumentException(
                                    "each HornetQ active active pair must contain all these suffixed {instance1.host, instance1.port, instance2.host, instance2.port} - but some are missing");
                        }

                        if (!isVersionPrinted) {
                            printHQVersion(host1, configuration.getString(jmxPort1Param, "3900"));
                        }
                        transportConfigurationsArray = new TransportConfiguration[2];
                        List<TransportConfiguration> transportConfigurations = new ArrayList<TransportConfiguration>();

                        Map<String, Object> map1 = new HashMap<String, Object>();
                        map1.put("host", host1);
                        map1.put("port", port1);

                        transportConfigurations
                                .add(new TransportConfiguration(NettyConnectorFactory.class.getName(), map1));

                        Map<String, Object> map2 = new HashMap<String, Object>();
                        map2.put("host", host2);
                        map2.put("port", port2);

                        transportConfigurations
                                .add(new TransportConfiguration(NettyConnectorFactory.class.getName(), map2));

                        transportConfigurations.toArray(transportConfigurationsArray);
                        connect();
                    }

                } else {

                    final ArrayList<String> serverConnectionKeys = Lists.newArrayList(serverConnections.keySet());
                    Collections.sort(serverConnectionKeys);

                    printHQVersion(serverConnections, serverConnectionKeys);

                    transportConfigurationsArray = new TransportConfiguration[serverConnectionKeys.size()];
                    List<TransportConfiguration> transportConfigurations = new ArrayList<TransportConfiguration>();

                    for (String serverConnectionKey : serverConnectionKeys) {

                        Map<String, String> serverConnection = serverConnections.get(serverConnectionKey);

                        Map<String, Object> map = new HashMap<String, Object>();

                        map.put("host", serverConnection.get("host"));
                        map.put("port", serverConnection.get("port"));

                        transportConfigurations
                                .add(new TransportConfiguration(NettyConnectorFactory.class.getName(), map));

                    }

                    transportConfigurations.toArray(transportConfigurationsArray);
                    connect();
                }

            } else {
                throw new IllegalArgumentException(
                        "'service.hornetq.connections' must contain at least on host/port pair.");
            }

            setupConsumers();

        } catch (Exception e) {
            LOGGER.error("can't create hornetq service locator: {}", e, e);
            throw new QueueException(e);
        }

    }

    private static void setupConsumers() {
        for (Map.Entry<String, MessageHandler> consumers : HornetQMessageConsumer.consumerInfo.entrySet()) {
            String consumerName = consumers.getKey();
            MessageHandler messageHandler = consumers.getValue();
            MessageConsumer consumer = HornetQMessagingFactory.createConsumer(consumerName);
            consumer.registerMessageHandler(messageHandler);
        }
    }

    private static void cleanup() {
        if (serverLocators != null) {
            for (ServerLocator serverLocator : serverLocators) {
                try {
                    serverLocator.close();
                } catch (Exception e) {
                    LOGGER.debug("can't close server locator: " + e.toString());
                }
            }
            serverLocators.clear();
        }

        if (sessions != null) {
            for (ClientSession session : sessions) {
                try {
                    session.close();
                } catch (HornetQException e) {
                    LOGGER.debug("can't close session: " + e.toString());
                }
            }
            sessions.clear();
        }

        if (clientSessions != null) {
            for (ClientSession session : clientSessions) {
                try {
                    session.close();
                } catch (HornetQException e) {
                    LOGGER.debug("can't close session: " + e.toString());
                }
            }
            clientSessions.clear();
        }

        if (HornetQMessageConsumer.consumers != null && !HornetQMessageConsumer.consumers.isEmpty()) {
            for (ClientConsumer consumer : HornetQMessageConsumer.consumers) {
                try {
                    consumer.close();
                } catch (HornetQException e) {
                    LOGGER.debug("can't close consumer: " + e.toString());
                }
            }
        }

        consumers.clear();
        producers.clear();

        //        HornetQMessageConsumer.consumerInfo.clear();
    }

    private static void printHQVersion(final String host, final String port) {
        LOGGER.info("HornetQ version: {}", VersionLoader.getVersion().getVersionName());

        Runnable getVersionFromServer = new Runnable() {
            @Override
            public void run() {
                try {

                    // Step 9. Retrieve the ObjectName of the queue. This is used to identify the server resources to manage
                    ObjectName on = ObjectNameBuilder.DEFAULT.getHornetQServerObjectName();

                    // Step 10. Create JMX Connector to connect to the server's MBeanServer
                    String url = MessageFormat.format("service:jmx:rmi://{0}/jndi/rmi://{0}:{1}/jmxrmi", host,
                            port == null ? "3900" : port);
                    LOGGER.debug("HornetQ Server jmx url: {}", url);
                    JMXConnector connector = JMXConnectorFactory.connect(new JMXServiceURL(url), new HashMap());

                    // Step 11. Retrieve the MBeanServerConnection
                    MBeanServerConnection mbsc = connector.getMBeanServerConnection();

                    // Step 12. Create a JMSQueueControl proxy to manage the queue on the server
                    JMSServerControl serverControl = MBeanServerInvocationHandler.newProxyInstance(mbsc, on,
                            JMSServerControl.class, false);

                    String serverControlVersion = serverControl.getVersion();
                    LOGGER.info("HornetQ Server version: {}", serverControlVersion);

                } catch (Exception e) {
                    LOGGER.info("can't log server version. error is: {}", e.toString());
                }
            }
        };

        try {

            new Thread(getVersionFromServer).start();

        } catch (Exception e) {
            LOGGER.info("can't log server version. error is: {}", e.toString());
        }
    }

    private static void printHQVersion(final Map<String, Map<String, String>> serverConnections,
            final ArrayList<String> serverConnectionKeys) {

        if (serverConnectionKeys != null && !serverConnectionKeys.isEmpty()) {

            String host = serverConnections.get(serverConnectionKeys.get(0)).get("host");
            String port = serverConnections.get(serverConnectionKeys.get(0)).get("jmxPort");
            if (port == null) {
                port = "3900";
            }

            printHQVersion(host, port);
        }
    }

    private static boolean isActiveActiveMode(Configuration subset) {
        Iterator<String> keys = subset.getKeys();
        while (keys.hasNext()) {
            String key = keys.next();
            if (key.contains(".instance")) {
                return true;
            }
        }
        return false;
    }

    private static void connect() {

        try {
            connectInternal();
            INIT_READY.countDown();
        } catch (Exception e) {
            LOGGER.warn("can't connect to hornetQ: {}", e);
            infiniteRetry();
        }

    }

    private static void connectInternal() throws Exception {
        ServerLocator serverLocator = HornetQClient.createServerLocatorWithHA(transportConfigurationsArray);
        //        serverLocator = serverLocatorWithHA;
        serverLocator.setRetryInterval(100);
        serverLocator.setRetryIntervalMultiplier(2);
        serverLocator.setReconnectAttempts(1);
        serverLocator.setInitialConnectAttempts(1);
        serverLocator.setFailoverOnInitialConnection(true);
        serverLocator.setClientFailureCheckPeriod(5000);
        serverLocator.setConnectionTTL(10000);
        serverLocator.setCallTimeout(10000);

        try {
            serverLocator.setAckBatchSize(1);
        } catch (Exception e) {
            LOGGER.debug("error trying to set ack batch size: {}", e);
        }

        serverLocators.add(serverLocator);

        ClientSession clientSession = serverLocator.createSessionFactory().createSession(true, true);
        clientSession.start();
        clientSession.addFailureListener(new FoundationQueueFailureListener(clientSession));

        clientSessions.add(clientSession);

    }

    static void infiniteRetry() {

        Thread thread = new Thread(new Runnable() {

            private boolean done = false;

            @Override
            public void run() {

                while (!done) {

                    LOGGER.trace("attempting to reconnect to HornetQ");
                    try {
                        connectInternal();
                        LOGGER.trace("reconnect to HornetQ is successful");
                        INIT_READY.countDown();
                        done = true;
                    } catch (Exception e) {
                        LOGGER.trace("failed to reconnect. retrying...", e);
                        try {
                            Thread.sleep(ConfigurationFactory.getConfiguration()
                                    .getInt("service.hornetq.attachRetryDelay", 10000));
                        } catch (InterruptedException e1) {
                            LOGGER.trace("thread interrupted!!!", e1);
                        }
                    }
                }
            }
        });
        thread.setName("hornetq-reconnect");
        thread.setDaemon(true);
        thread.start();
    }

    /**
     * create a new consumer if one doesn't already exist in the ThreadLocal
     * the consumer will bonded to an address with a queue-name as defined in the configuration.
     * the configuration subset is defined by finding a subset starting with the given consumer name.
     * E.g. consumer name = consumer1
     * Config:
     * consumer1.queue.name=consumer1
     * consumer1.queue.filter=key1='value2'
     * consumer1.queue.isSubscription=true
     * consumer1.queue.subscribedTo=myExample
     */
    public static MessageConsumer createConsumer(String consumerName) {
        if (!consumers.containsKey(consumerName)) {
            consumers.put(consumerName, new HornetQMessageConsumer(consumerName));
        }
        return consumers.get(consumerName);
    }

    /**
     * create a new producer if one doesn't already exist in the ThreadLocal
     * the producer will be bonded to an address with an address-name as defined in the configuration.
     * the configuration subset is defined by finding a subset starting with the given producer name.
     * E.g. producer name = example
     * Config:
     * example.queue.name=myExample
     */
    public static MessageProducer createProducer(String producerName) {
        if (!producers.containsKey(producerName)) {
            producers.put(producerName, new HornetQMessageProducer(producerName));
        }
        return producers.get(producerName);
    }

}