All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.ebay.jetstream.messaging.MessageService Maven / Gradle / Ivy

/*******************************************************************************
 *  Copyright © 2012-2015 eBay Software Foundation
 *  This program is dual licensed under the MIT and Apache 2.0 licenses.
 *  Please see LICENSE for more information.
 *******************************************************************************/
package com.ebay.jetstream.messaging;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.ebay.jetstream.common.ShutDownable;
import com.ebay.jetstream.counter.LongCounter;
import com.ebay.jetstream.counter.LongEWMACounter;
import com.ebay.jetstream.management.Management;
import com.ebay.jetstream.messaging.config.ContextConfig;
import com.ebay.jetstream.messaging.config.MessageServiceProperties;
import com.ebay.jetstream.messaging.config.TransportConfig;
import com.ebay.jetstream.messaging.exception.MessageServiceException;
import com.ebay.jetstream.messaging.interfaces.IMessageListener;
import com.ebay.jetstream.messaging.interfaces.ITransportProvider;
import com.ebay.jetstream.messaging.messagetype.JetstreamMessage;
import com.ebay.jetstream.messaging.stats.MessageServiceStats;
import com.ebay.jetstream.messaging.stats.MessageServiceStatsController;
import com.ebay.jetstream.messaging.stats.StatsHarvestTimer;
import com.ebay.jetstream.messaging.stats.TransportStats;
import com.ebay.jetstream.messaging.topic.JetstreamTopic;
import com.ebay.jetstream.messaging.topic.TopicInfo;
import com.ebay.jetstream.util.RequestQueueProcessor;

/**
 * @author shmurthy
 * 
 */
@edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD")
public class MessageService implements ShutDownable {

	private static MessageService s_theInstance = new MessageService();

	// the following data structure is used to maintain the sequence Ids
	// corresponding to each of the topics broadcast from a publisher process.

	private static final Logger LOGGER = LoggerFactory
			.getLogger("com.ebay.jetstream.messaging");

	/**
	 * @return
	 */
	public static MessageService getInstance() {
		return s_theInstance;
	}

	// the following data structure holds data about publishers whose
	// messages are received by the receiver. The publishers
	// last sent sequenceID is stored in the publisherInfo.
	// the key is made up of guid and ip address of sender.

	private final ConcurrentHashMap m_dispatcherlist;

	private final ConcurrentHashMap m_publishedTopiclist;
	private final ConcurrentHashMap> m_listenerTable;
	private byte[] m_msgOrigAddr;
	private final AtomicBoolean m_initialized = new AtomicBoolean(false);
	RequestQueueProcessor m_msgProcessor;
	RequestQueueProcessor m_internalMsgProcessor;
	private final AtomicLong m_msgRcvCounter = new AtomicLong(0);

	private final Object m_lock = new Object();

	private final DispatchQueueStats m_queueStats = new DispatchQueueStats();
	private DispatchQueueMonitor m_dqm;
	private volatile MessageServiceProperties m_messageServiceProperties;

	// stats

	private final LongCounter m_totalMsgsSent = new LongCounter();
	private final AtomicLong m_msgsRcvdPerSec = new AtomicLong(0);
	private final LongCounter m_totalMsgsRcvd = new LongCounter();
	private final LongCounter m_totalMsgsDropped = new LongCounter();
	private final LongCounter m_totalMsgsDroppedByNoContext = new LongCounter();
	private final LongEWMACounter m_avgMsgsSentPerSec = new LongEWMACounter(60,
			MessageServiceTimer.sInstance().getTimer());
	private final LongEWMACounter m_avgMsgsRcvdPerSec = new LongEWMACounter(60,
			MessageServiceTimer.sInstance().getTimer());

	private StatsHarvestTimer m_statsHarvestTimer;

	private MessageServiceProxy m_proxy;

	private AtomicBoolean m_paused = new AtomicBoolean(
			false);

	private enum UpstreamQueueState {
		FULL, EMPTY
	};

	private int m_twentyPercentCapacity;

	public boolean isPaused() {
		return m_paused.get();
	}

	
	/**
	 * Although this class was intended to be a singleton, the default
	 * constructor has public access mainly because it needs to be instantiated
	 * by Spring.
	 */
	public MessageService() {
		s_theInstance = this; // this is done mainly so that Spring can load
								// message service - generally a bad practice
		m_listenerTable = new ConcurrentHashMap>();
		m_dispatcherlist = new ConcurrentHashMap();
		m_publishedTopiclist = new ConcurrentHashMap();

	}

	/**
	 * @param topic
	 * @param tml
	 */
	void addSubscriber(
			JetstreamTopic topic,
			com.ebay.jetstream.messaging.interfaces.IMessageListener tml) {

		if (!m_listenerTable.containsKey(topic.getRootContext())) {

			ConcurrentLinkedQueue llist = new ConcurrentLinkedQueue();

			ListenerInfo li = new ListenerInfo();

			li.m_listener = tml;
			li.m_topic = topic;

			llist.add(li);

			m_listenerTable.put(topic.getRootContext(), llist);

		} else {

			ConcurrentLinkedQueue llist = m_listenerTable
					.get(topic.getRootContext());
			if (llist == null)
				return;
			ListenerInfo li = new ListenerInfo();

			li.m_listener = tml;
			li.m_topic = topic;

			if (!llist.contains(li))
				llist.add(li);
			else
				li = null;
		}
	}

	/**
	 * @param context
	 * @throws Exception
	 */
	private void createDispatcherRegisterWithAllTransports(JetstreamTopic topic)
			throws Exception {
		Enumeration dispatcherlist = m_dispatcherlist
				.elements();

		while (dispatcherlist.hasMoreElements()) {
			DispatcherInfo dispinfo = dispatcherlist.nextElement();

			if (!dispinfo.isDispatcherCreated()) {

				try {
					dispinfo.getTransport().registerListener(m_proxy);
					dispinfo.getTransport().registerTopic(topic);
				} catch (Exception e) {

					String message = "Failed to register transport listener - ";
					message += e.getMessage();

					LOGGER.error( message);

					throw e;
				}

				dispinfo.setDispatcherCreated(true);
			}
		}
	}

	/**
	 * @param context
	 * @throws Exception
	 */
	void createDispatcherRegisterWithTransport(JetstreamTopic topic)
			throws Exception {

		String context = topic.getRootContext();

		if (context.equals("/")) {
			createDispatcherRegisterWithAllTransports(topic);
			return;
		}

		DispatcherInfo dispinfo = m_dispatcherlist.get(context);

		if (dispinfo == null) {

			String message = "Did not find any transport from config for context - ";
			message += context;

			LOGGER.error( message);

			throw new MessageServiceException(
					MessageServiceException.TRANSPORT_ERROR, message);

		}

		if (!dispinfo.isDispatcherCreated()) {

			try {
				dispinfo.getTransport().registerListener(m_proxy);
				dispinfo.getTransport().registerTopic(topic);

			} catch (Exception e) {

				if (LOGGER.isWarnEnabled()) {
					String message = "Failed to register listner - ";
					message += e.getMessage();

					LOGGER.warn( message);
				}

				throw e;
			}

			dispinfo.setDispatcherCreated(true);
		} else
			dispinfo.getTransport().registerTopic(topic);

	}

	/**
	 * @param tm
	 * @return
	 */
	boolean dispatch(JetstreamMessage tm) {

		// first disptach for all subscibers who have registered to listen to
		// all messages

		dispatchMessageForContext("/", tm);

		// next disptach to subscriber who have registered interest
		// in a specific context only

		return dispatchMessageForContext(tm.getTopic().getRootContext(), tm);

	}

	/**
	 * @param topic
	 * @param tm
	 * @throws Exception
	 */
	void dispatchDownStream(JetstreamTopic topic, JetstreamMessage tm)
			throws Exception {

		if (tm != null && topic != null) {
			tm.setTopic(topic);
			tm.setMsgOrigination(m_msgOrigAddr);
			TopicInfo ti = getTopicInfo(topic.getTopicName());
			tm.setSequenceId(ti.incSeqId());
			tm.setGuid(ti.m_id);
		} else
			throw new NullPointerException("Topic or jetstreamMessage is null");

		try {

			DispatcherInfo dispinfo = m_dispatcherlist.get(topic
					.getRootContext());

			if (dispinfo == null) {
				m_totalMsgsDroppedByNoContext.increment();
				// we did not find the context something is seriously wrong.
				// report an error and return

				if (LOGGER.isDebugEnabled()) {
					String message = "Did not find any transport from config for context - ";
					message += topic.getRootContext();
					message += " - Dropping message!!";
					LOGGER.debug( message);
				}

				return;

			}

			dispinfo.getTransport().send(tm);

			m_avgMsgsSentPerSec.increment();
			m_totalMsgsSent.increment();

		} catch (Throwable e) {

			String message = "Transport Exception - ";
			message += e.getMessage();
			message += Arrays.toString(e.getStackTrace());

			if (LOGGER.isDebugEnabled()) {

				LOGGER.debug( e.getMessage());
			}

			throw new MessageServiceException(
					MessageServiceException.TRANSPORT_ERROR, message);
		}
	}

	/**
	 * @param rootContext
	 * @param tm
	 * @return
	 */
	boolean dispatchMessageForContext(String rootContext, JetstreamMessage tm) {

		ConcurrentLinkedQueue llist = null;

		Iterator iter = null;

		if (m_listenerTable.containsKey(rootContext)) {

			llist = m_listenerTable.get(rootContext);

			if (llist == null) {

				return false;
			}

			try {
				if (llist.size() > 0) {

					iter = llist.iterator();

					while (iter.hasNext()) {

						ListenerInfo li = iter.next();

						if (tm.getTopic().matches(li.m_topic)) {
							li.m_listener.onMessage(tm);
						}

					}

				}
			} catch (IndexOutOfBoundsException ibe) {
				return false;
			} catch (Throwable e) {
				String message = "Exception while dispatching message for topic - "
						+ tm.getTopic().getTopicName();
				message += " - Exception - ";
				message += e.getMessage();

				LOGGER.error( message);
				LOGGER.error( e.toString());

			}
		}
		return false;
	}

	/**
	 * find contexts that were previuosly provisioned but removed as part of the
	 * config change
	 * 
	 * @param msp
	 * @return
	 */
	private List findDeletedContexts(MessageServiceProperties msp) {

		LinkedList deletedContexts = new LinkedList();

		Enumeration contextlist = m_dispatcherlist.keys();

		LinkedList newContexts = new LinkedList();

		Iterator itr = msp.getTransports().iterator();

		while (itr.hasNext()) {

			TransportConfig tc = itr.next();

			Iterator contextItr = tc.getContextList().iterator();

			while (contextItr.hasNext()) {

				ContextConfig cc = contextItr.next();

				newContexts.add(cc.getContextname());

				if (m_dispatcherlist.containsKey(cc.getContextname())) {

					DispatcherInfo dispinfo = m_dispatcherlist.get(cc
							.getContextname());

					if (dispinfo != null) {
						if (!dispinfo.getTransport().getTransportConfig()
								.equals(tc)) // this checks for change to all
												// properties other than context
												// list
							deletedContexts.add(cc.getContextname());
						else if (!dispinfo.getTransport().getContextConfig()
								.equals(cc)) // this checks if there is a
												// specific change to context
												// list
							deletedContexts.add(cc.getContextname());
					}
				}

			}

		}

		// Now we will see if a context has been removed from the original list

		while (contextlist.hasMoreElements()) {

			String context = contextlist.nextElement();

			if (!newContexts.contains(context))
				deletedContexts.add(context);

		}

		return deletedContexts;
	}

	/**
	 * @return
	 */
	DispatchQueueStats getDispatchQueueStats() {

		DispatchQueueStats stats = new DispatchQueueStats();

		stats.setHighPriorityQueueDepth((int) m_msgProcessor
				.getPendingRequests());
		stats.setLowPriorityQueueDepth((int) m_msgProcessor
				.getPendingRequests());
		stats.setMaxQueueDepth(m_msgProcessor.getMaxQueueSz());

		return stats;
	}

	/**
	 * @return
	 */
	public MessageServiceProperties getMessageServiceProperties() {
		return m_messageServiceProperties;
	}

	/**
	 * @return
	 */
	public MessageServiceStats getStats() {

		MessageServiceStats stats = new MessageServiceStats();

		stats.setMsgsSentPerSec(m_avgMsgsSentPerSec.get());
		stats.setTotalMsgsSent(m_totalMsgsSent.get());
		stats.setMsgsRcvdPerSec(m_avgMsgsRcvdPerSec.get());
		stats.setTotalMsgsRcvd(m_totalMsgsRcvd.get());
		stats.setTotalMsgsLostByNoContext(m_totalMsgsDroppedByNoContext.get());

		stats.setHighPriorityQueueDepth((int) m_msgProcessor
				.getPendingRequests());
		stats.setLowPriorityQueueDepth((int) m_msgProcessor
				.getPendingRequests());

		stats.setTotalMsgsLost(m_totalMsgsDropped.get());

		stats.setPaused(isPaused());

		return stats;
	}

	/**
	 * @param topicname
	 * @return
	 */
	private TopicInfo getTopicInfo(String topicname) {
		TopicInfo ti = null;

		if (m_publishedTopiclist.containsKey(topicname)) {
			ti = m_publishedTopiclist.get(topicname);

		} else {
			synchronized (m_lock) {
				if (!m_publishedTopiclist.containsKey(topicname)) {
					ti = new TopicInfo();
					m_publishedTopiclist.put(topicname, ti);
				} else
					ti = m_publishedTopiclist.get(topicname);

			}
		}

		return ti;
	}

	/**
	 * @return
	 */
	public List getTransportStats()
			throws IllegalStateException {

		if (!isInitialized()) {
			throw new IllegalStateException("MessageService Not Initialized");
		}

		LinkedList tportStats = new LinkedList();

		Enumeration dispatcherlist = m_dispatcherlist
				.elements();

		while (dispatcherlist.hasMoreElements()) {
			tportStats.add(dispatcherlist.nextElement().getTransport()
					.getStats());
		}

		return tportStats;
	}

	/**
 * 
 */
	public void harvestStats() {

		Iterator> itr = m_dispatcherlist
				.entrySet().iterator();

		while (itr.hasNext()) {
			Entry entry = itr.next();
			entry.getValue().getTransport().harvestStats();
		}
	}

	/**
	 * @param msp
	 * @throws MessageServiceException
	 * @throws Exception
	 */
	public void init(MessageServiceProperties msp)
			throws MessageServiceException, Exception {
		setMessageServiceProperties(msp);
	}

	/**
	 * @param configMap
	 * @throws Exception
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 * @throws ClassNotFoundException
	 */
	private void initializeMessageService() throws Exception,
			InstantiationException, IllegalAccessException,
			ClassNotFoundException {
		if (m_initialized.get())
			return;
		MessageServiceProperties messageServiceProperties = getMessageServiceProperties();
		try {
			m_msgOrigAddr = java.net.InetAddress.getLocalHost().getAddress();
		} catch (Exception e) {
			if (LOGGER.isWarnEnabled()) {
				String message = "Failed to get Local Host Address - ";
				message += e.getMessage();

				LOGGER.warn( message);
			}

			throw e;
		}

		// TODO : get capacity of queue form MessageServiceProperties.
		// for now hardcode

		m_msgProcessor = new RequestQueueProcessor(
				messageServiceProperties.getUpstreamDispatchQueueSize(),
				messageServiceProperties.getUpstreamDispatchThreadPoolSize(),
				"UpstreamMessageProcessor");
		m_twentyPercentCapacity = (int) (m_msgProcessor.getMaxQueueSz() * 0.2);
		
		m_internalMsgProcessor = new RequestQueueProcessor(50000, 1,
				"InternalMsgProcessor");
		m_dqm = new DispatchQueueMonitor();
		m_dqm.start();

		m_proxy = new MessageServiceProxy(this);

		if (installTransports(messageServiceProperties)) {
			m_initialized.set(true);

			// register with monitoring and control service
			Management.removeBeanOrFolder("MessageService/stats");
			Management.addBean("MessageService/stats",
					new MessageServiceStatsController(this));
			m_statsHarvestTimer = new StatsHarvestTimer();
			MessageServiceTimer.sInstance().schedulePeriodicTask(
					m_statsHarvestTimer, 1000); // harvest stats every second

		} else {
			m_initialized.set(false);
			throw new MessageServiceException(
					MessageServiceException.INITIALIZATION_ERROR,
					"Can not install Transports");
		}

	}

	/**
	 * @param msp
	 * @return
	 * @throws Exception
	 */
	private boolean installTransports(MessageServiceProperties msp)
			throws Exception {

		if (msp == null) {

			LOGGER.error(
					"Service Unavailable!! MessageServiceProperties not specified - can not load transports");

			return false;
		}

		Map dnsContextMap = msp.getContextMap();

		if (dnsContextMap == null)
			return false;

		Iterator itr = msp.getTransports().iterator();

		while (itr.hasNext()) {

			TransportConfig tke = itr.next();

			List contextList = tke.getContextList();

			for (int i = 0; i < contextList.size(); i++) {

				ContextConfig cc = contextList.get(i);

				DispatcherInfo dispInfo = m_dispatcherlist.get(cc
						.getContextname());

				if (dispInfo == null)
					dispInfo = new DispatcherInfo();
				else if (dispInfo.getTransport().getContextConfig().equals(cc)) {
					dispInfo.getTransport().setMessageServiceProperties(msp);
					continue;
				} else {
					dispInfo.getTransport().shutdown();
					dispInfo.setTransport(null);
				}

				ITransportProvider jetstreamTransport = instantiateTransport(tke
						.getTransportClass());

				if (jetstreamTransport == null)
					continue;

				jetstreamTransport.setContextConfig(cc);

				// now get address from DNS for this context.
				// and then set the address and port pair.

				String value;

				if (tke.requireDNS()) {

					value = dnsContextMap.get(cc.getContextname()); // get IP
																	// address
																	// and port
																	// pair from
																	// DNS

					if (value == null) {
						String message = "Unable to find DNS entry for context - ";
						message += cc.getContextname();

						LOGGER.error( message);

						continue;

					}
				} else {
					value = cc.getHostAndPort();
				}

				StringTokenizer addressAndPort = new StringTokenizer(value, ",");

				// when we use DNS we will get address and port from DNS and
				// supply it to transport
				// for non-dns, the transport could get it from transport or
				// context config depending on transport

				jetstreamTransport.setAddr(addressAndPort.nextToken());
				jetstreamTransport.setPort(Integer.parseInt(addressAndPort
						.nextToken()));

				jetstreamTransport.setMessageServiceProperties(msp);

				dispInfo.setTransport(jetstreamTransport);

				try {
					dispInfo.getTransport().init(tke, msp.getNicUsage(),
							msp.getDnsMap(), m_proxy);
					dispInfo.getTransport().registerListener(m_proxy);

					// this transport may be installed as part of a config
					// change. The service
					// might already have been up. Let's check in the listener
					// table if we have
					// subscriptions for the context associated with this
					// transport. if we do then
					// it is time to register that

					ConcurrentLinkedQueue listeners = m_listenerTable
							.get(cc.getContextname());

					if (listeners != null) {
						Iterator listenerItr = listeners
								.iterator();

						while (listenerItr.hasNext()) {
							ListenerInfo linfo = listenerItr.next();
							dispInfo.getTransport()
									.registerTopic(linfo.m_topic);

						}
					}

					// we need to now handle any publishers that might have
					// called prepare to publish
					// and act as a proxy for them

					Enumeration topicNames = m_publishedTopiclist
							.keys();

					while (topicNames.hasMoreElements()) {
						String topicname = topicNames.nextElement();

						JetstreamTopic topic = new JetstreamTopic(topicname);

						if (dispInfo.getTransport().getContextConfig()
								.getContextname()
								.equals(topic.getRootContext())) {
							dispInfo.getTransport().prepareToPublish(
									topic.getRootContext());
						}
					}

				} catch (Exception e) {
					LOGGER.error(
							"Failed to install Transport for context - "
									+ cc.getContextname() + " - "
									+ e.getLocalizedMessage());
					throw new Exception(
							"Failed to install Transport for context - "
									+ cc.getContextname() + " - "
									+ e.getLocalizedMessage(), e);
				}

				dispInfo.setDispatcherCreated(true); // BUG FIX (SRM) - Oct 25,
														// 2013 - was false
														// before - now changing
														// to true.

				m_dispatcherlist.put(cc.getContextname(), dispInfo);
			}
		}

		return true;

	}

	/**
	 * @param className
	 * @return
	 */

	ITransportProvider instantiateTransport(String className) {

		ITransportProvider tport = null;

		try {
			tport = (ITransportProvider) Class.forName(className).newInstance();
		} catch (InstantiationException e) {

			LOGGER.error( "Exception while instatiating class - "
					+ e.getMessage());

			return null;
		} catch (IllegalAccessException e) {

			LOGGER.error( "Exception while instatiating class - "
					+ e.getLocalizedMessage());

			return null;

		} catch (ClassNotFoundException e) {
			String message = "Exception while instatiating class - ";

			message += e.getMessage();

			LOGGER.error( message);

			return null;
		}

		return tport;
	}

	/**
	 * @return
	 */
	public boolean isInitialized() {
		return m_initialized.get();
	}

	/**
	 * 
	 * Signal to message service to stop sending on the specified topic. This
	 * method will unregister the TOPIC from the associated transport without
	 * unregistering the listener. This way no more events are delivered by the
	 * producer however whatever is in the pipeline will get delivered upstream.
	 * 
	 * @param topic
	 */
	public void pause(JetstreamTopic topic) throws Exception {

		if (topic == null)
			throw new NullPointerException(
					"one or more input arguments is NULL");

		DispatcherInfo dispinfo = m_dispatcherlist.get(topic.getRootContext());

		if (dispinfo != null && dispinfo.isDispatcherCreated()) {
			dispinfo.getTransport().pause(topic);
		}

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.ebay.jetstream.messaging.TransportListener#postAdvise(com.ebay.jetstream
	 * .messaging.jetstreamMessage)
	 */
	void postAdvise(JetstreamMessage tm) {

		try {
			postMessage(tm, null);
		} catch (MessageServiceException e) {

			String message = "Exception while posting advise - ";

			message += e.getMessage();

			LOGGER.error( message);

		}
	}

	/**
   * 
   */
	void postDispatchQueueStats() {

		Enumeration dispatcherlist = m_dispatcherlist
				.elements();

		m_queueStats.setHighPriorityQueueDepth((int) m_msgProcessor
				.getPendingRequests());
		m_queueStats.setLowPriorityQueueDepth((int) m_msgProcessor
				.getPendingRequests());
		m_queueStats.setMaxQueueDepth(m_msgProcessor.getMaxQueueSz());

		while (dispatcherlist.hasMoreElements()) {
			DispatcherInfo dispinfo = dispatcherlist.nextElement();

			if (dispinfo.getTransport() != null)
				dispinfo.getTransport().setUpstreamDispatchQueueStats(
						m_queueStats);

		}
		
		if (m_paused.get()) {
			try {
				if (m_msgProcessor.hasAvailableCapacity(m_msgProcessor.getMaxQueueSz()))
					resumeTraffic();
			} catch (Exception e) {
				LOGGER.error( "Failed to resume traffic after pausing");
			}
		}

	}

	/**
	 * Post a batch of messages.
	 * 
	 * @param msgs
	 * @param stats
	 * @throws MessageServiceException
	 */
	void postMessage(List msgs, DispatchQueueStats stats)
			throws MessageServiceException {

		m_msgRcvCounter.addAndGet(msgs.size());

		if ((monitorUpstreamQueueAndPauseTraffic() == UpstreamQueueState.FULL) &&
				(m_paused.get())){
				if (!m_msgProcessor.hasAvailableCapacity(m_twentyPercentCapacity)) {
					m_totalMsgsDropped.increment();
					return;
				}
		}
			
		List requests = new ArrayList(msgs.size());

		for (int i = 0, t = msgs.size(); i < t; i++) {
			JetstreamMessage tm = msgs.get(i);
			if (tm.getTopic() == null) {
				m_totalMsgsDropped.increment();
				if (LOGGER.isDebugEnabled()) {
					LOGGER.debug(
							"Topic is not present in incoming message");
				}
				continue;
			}
			MessageServiceRequest msr = new MessageServiceRequest(tm);

			msr.setPriority(tm.getPriority());
			msr.setSequenceid(tm.getSequenceId());

			if (msr.getPriority() == JetstreamMessage.INTERNAL_MSG_PRIORITY) {

				if (!m_internalMsgProcessor.processRequest(msr)) {
					m_totalMsgsDropped.increment();
					throw new MessageServiceException(
							MessageServiceException.BUFFER_FULL,
							"Dispatch Queue Full");
				}
				if (m_msgsRcvdPerSec.addAndGet(1) < 0)
					m_msgsRcvdPerSec.set(0);
				m_totalMsgsRcvd.increment();

			} else {
				requests.add(msr);
			}
		}

		if (!requests.isEmpty()) {
			int batchsize = requests.size();
			// SRM - July 9, 2014. we will have 1 queue for both high and lo
			// priority messages. This is because we don't have priority queue
			// with disruptor
			if (!m_msgProcessor.processBatch(requests)) {
				m_totalMsgsDropped.addAndGet(batchsize);
				throw new MessageServiceException(
						MessageServiceException.BUFFER_FULL,
						"High Priority Dispatch Queue Full - "
								+ " Requested capacity = " + batchsize
								+ " : available capacity = "
								+ m_msgProcessor.getAvailableCapacity());

			}
			m_avgMsgsRcvdPerSec.add(batchsize);
			m_totalMsgsRcvd.addAndGet(batchsize);
		}
		if (stats != null) {
			stats.setHighPriorityQueueDepth((int) m_msgProcessor
					.getPendingRequests());
			stats.setLowPriorityQueueDepth((int) m_msgProcessor
					.getPendingRequests());
			stats.setMaxQueueDepth((int) m_msgProcessor.getMaxQueueSz());

		}
	}

	private void pauseTraffic() {

		for (Entry> entry : m_listenerTable
				.entrySet()) {
			String context = entry.getKey();
			ConcurrentLinkedQueue listeners = entry.getValue();
			if (listeners != null) {
				Iterator listenerItr = listeners.iterator();
				while (listenerItr.hasNext()) {
					ListenerInfo linfo = listenerItr.next();
					try {
						if (!linfo.m_topic.equals(new JetstreamTopic(
								linfo.m_topic.getRootContext()
										+ "/InternalStateAdvisory")))
							pause(linfo.m_topic);
					} catch (Exception e) {
						LOGGER.error( "Could NOT Pause topic : "
								+ linfo.m_topic);
					}
				}
			}
		}

	}

	private UpstreamQueueState getUpstreamQueueStatus() {
		if (m_msgProcessor.hasAvailableCapacity(m_msgProcessor.getMaxQueueSz() >> 1))
				return UpstreamQueueState.EMPTY;
		else
			    return UpstreamQueueState.FULL;
	}
	
	
	/**
	 * - checks MessageService dispatch queue depth(highwatermark/ lowwatermark)
	 * and raise pause/resume to producer if needed
	 * 
	 * @throws Exception
	 * 
	 */

	private UpstreamQueueState monitorUpstreamQueueAndPauseTraffic() {

		if (getUpstreamQueueStatus() == UpstreamQueueState.FULL) {

			if (m_paused.compareAndSet(false, true)) {

				pauseTraffic();
								
			}
			
			return UpstreamQueueState.FULL;
		}

		return UpstreamQueueState.EMPTY;

	}

	/**
	 * @param tm
	 * @param stats
	 * @throws MessageServiceException
	 * @throws Exception
	 */
	void postMessage(JetstreamMessage tm, DispatchQueueStats stats)
			throws MessageServiceException {

		if (tm.getTopic() == null) {
			m_totalMsgsDropped.increment();
			if (LOGGER.isDebugEnabled()) {
				LOGGER.debug(
						"Topic is not present in incoming message");
			}
			return; // cannot forward message without topic
		}

		m_msgRcvCounter.getAndIncrement();

		if ((monitorUpstreamQueueAndPauseTraffic() == UpstreamQueueState.FULL) &&
			(m_paused.get())){
			if (!m_msgProcessor.hasAvailableCapacity(m_twentyPercentCapacity)) {
				m_totalMsgsDropped.increment();
				return;
			}
		}

		MessageServiceRequest msr = new MessageServiceRequest(tm);

		msr.setPriority(tm.getPriority());
		msr.setSequenceid(tm.getSequenceId());

		if (msr.getPriority() != JetstreamMessage.INTERNAL_MSG_PRIORITY) {

			// SRM - July 9, 2014. we will have 1 queue for both high and low
			// priority messages. This is because we don't have priority queues
			// with disruptors
			if (!m_msgProcessor.processRequest(msr)) {
				m_totalMsgsDropped.increment();
				throw new MessageServiceException(
						MessageServiceException.BUFFER_FULL,
						"High Priority Dispatch Queue Full - "
								+ " Requested capacity = " + 1L
								+ " : available capacity = "
								+ m_msgProcessor.getAvailableCapacity());
			}

			m_avgMsgsRcvdPerSec.increment();
			m_totalMsgsRcvd.increment();

		} else {
			if (!m_internalMsgProcessor.processRequest(msr)) {
				m_totalMsgsDropped.increment();
				throw new MessageServiceException(
						MessageServiceException.BUFFER_FULL,
						"Dispatch Queue Full");
			}
			if (m_msgsRcvdPerSec.addAndGet(1) < 0)
				m_msgsRcvdPerSec.set(0);
			m_totalMsgsRcvd.increment();
		}

		if (stats != null) {
			stats.setHighPriorityQueueDepth((int) m_msgProcessor
					.getPendingRequests());
			stats.setLowPriorityQueueDepth((int) m_msgProcessor
					.getPendingRequests());
			stats.setMaxQueueDepth((int) m_msgProcessor.getPendingRequests());

		}

	}

	/**
	 * @param topic
	 * @param tm
	 * @throws MessageServiceException
	 * @throws Exception
	 */
	public void publish(List topics, JetstreamMessage tm)
			throws MessageServiceException, Exception {

		if ((topics == null) || (tm == null))
			throw new NullPointerException("One of the input arguments is null");

		Iterator itr = topics.iterator();

		while (itr.hasNext()) {
			publish(itr.next(), tm);
		}
	}

	/**
	 * @param topic
	 * @param tm
	 * @throws MessageServiceException
	 * @throws Exception
	 */
	public void publish(JetstreamTopic topic, JetstreamMessage tm)
			throws MessageServiceException, Exception {

		if (!m_initialized.get()) {
			throw new MessageServiceException(
					MessageServiceException.SERVICE_UNINITIALIZED,
					"Message service not Initialized");
		}

		if (tm.getPriority() != JetstreamMessage.HI_PRIORITY
				&& tm.getPriority() != JetstreamMessage.LOW_PRIORITY)
			throw new MessageServiceException(
					MessageServiceException.UNSUPPORTED_MSG_PRIORITY,
					"Message Priority out of range - "
							+ Integer.toString(tm.getPriority()));

		dispatchDownStream(topic, tm);

	}

	/**
	 * This method is expected to be called by a publisher to do signal to
	 * underlying transports bound to the specified context to get ready to
	 * receive messages to be published. The transport in turn must perform all
	 * initialization required to publish a message.
	 * 
	 * @param Context
	 */
	public void prepareToPublish(JetstreamTopic topic) {

		if (topic == null)
			throw new NullPointerException(
					"one or more input arguments is NULL");

		DispatcherInfo dispinfo = m_dispatcherlist.get(topic.getRootContext());

		if (dispinfo != null) {
			if (dispinfo.getTransport() != null)
				dispinfo.getTransport()
						.prepareToPublish(topic.getRootContext());
		}
	}

	/**
   * 
   */
	public void resetStats() {

		m_avgMsgsSentPerSec.reset();
		m_totalMsgsSent.reset();
		m_avgMsgsRcvdPerSec.reset();
		m_totalMsgsRcvd.reset();
		m_totalMsgsDropped.reset();
		m_totalMsgsDroppedByNoContext.reset();
	}

	/**
	 * 
	 * Signal to message service to resume sending on the specified topic.
	 * Reregister the TOPIC with the associated transport with the assumption
	 * that the listener is still registered. This way the pipe will be reopened
	 * to deliver events upstream
	 * 
	 * @param topic
	 */
	public void resume(JetstreamTopic topic) {

		DispatcherInfo dispinfo = m_dispatcherlist.get(topic.getRootContext());

		if (dispinfo != null && dispinfo.isDispatcherCreated()) {
			dispinfo.getTransport().resume(topic);
		}
	}

	void resumeTraffic() throws Exception {

		if (m_paused.compareAndSet(true, false)) {

			for (Entry> entry : m_listenerTable
					.entrySet()) {

				String context = entry.getKey();
				ConcurrentLinkedQueue listeners = entry
						.getValue();

				if (listeners != null) {
					Iterator listenerItr = listeners.iterator();
					while (listenerItr.hasNext()) {
						ListenerInfo linfo = listenerItr.next();
						try {
							if (!linfo.m_topic.equals(new JetstreamTopic(
									linfo.m_topic.getRootContext()
											+ "/InternalStateAdvisory")))
								resume(linfo.m_topic);
						} catch (Exception e) {
							LOGGER.error(
									"Could NOT Resume topic : " + linfo.m_topic);

						}
					}
				}
			}
		}

	}

	/**
	 * @param messageServiceProperties
	 * @throws Exception
	 */
	public void setMessageServiceProperties(
			MessageServiceProperties messageServiceProperties) throws Exception {

		if (messageServiceProperties == null)
			throw new NullPointerException("MessageServiceProperties is null");

		m_messageServiceProperties = messageServiceProperties;

		if (!isInitialized())
			initializeMessageService();
		else {

			try {
				// first shutdown deleted contexts
				shutdownDeletedContexts(messageServiceProperties);

				// add any new contexts that have been added to configuration
				installTransports(messageServiceProperties);
			} catch (Throwable t) {
				LOGGER.error(
						"failed to apply Message Service Properties - "
								+ t.getLocalizedMessage());
			}
		}

	}

	/**
	 * @throws Exception
	 */
	public void shutDown() {

		LOGGER.warn( "Message Service Shutting Down");

		if (m_statsHarvestTimer != null)
			m_statsHarvestTimer.cancel();

		m_avgMsgsRcvdPerSec.destroy();
		m_avgMsgsSentPerSec.destroy();

		if (m_msgProcessor != null)
			m_msgProcessor.shutdown();

		if (m_internalMsgProcessor != null)
			m_internalMsgProcessor.shutdown();

		if (m_dqm != null)
			m_dqm.shutdown();

		Enumeration dispatcherlist = m_dispatcherlist
				.elements();

		while (dispatcherlist.hasMoreElements()) {
			DispatcherInfo dispinfo = dispatcherlist.nextElement();

			try {
				dispinfo.getTransport().shutdown();
			} catch (Exception e) {
				if (LOGGER.isWarnEnabled()) {
					String message = "Error while shutting down - ";
					message += e.getMessage();

					LOGGER.warn( message);
				}

			}
		}

		m_dispatcherlist.clear();

		m_listenerTable.clear();

		MessageServiceTimer.sInstance().shutdown();

		m_initialized.set(false);
	}

	/**
	 * This method must be called when a config changed is being applied. It
	 * compares the context defined in the passed MessageServiceProperties with
	 * that in the disptacher list. If a context that is present in the
	 * dispatcher list but not present in the new config will be removed from
	 * the dispatcher list and the associated transport instance will be
	 * shutdown
	 * 
	 * @param msp
	 */
	private void shutdownDeletedContexts(MessageServiceProperties msp) {
		List deletedContextList = findDeletedContexts(msp);

		if (!deletedContextList.isEmpty()) {

			Iterator deletedContextItr = deletedContextList.iterator();

			while (deletedContextItr.hasNext()) {
				String context = deletedContextItr.next();
				try {
					DispatcherInfo dinfo = m_dispatcherlist.remove(context);
					;
					if (dinfo != null)
						dinfo.getTransport().shutdown();
				} catch (Exception e) {
					if (LOGGER.isErrorEnabled()) {
						String message = "Error while applying new config and shutting down transport for context - ";
						message += context;
						message += " - ";
						message += e.getMessage();

						LOGGER.warn( message);
					}
				}
			}
		}
	}

	/**
	 * @param topic
	 * @param tml
	 * @throws MessageServiceException
	 * @throws Exception
	 */
	public void subscribe(JetstreamTopic topic, IMessageListener tml)
			throws MessageServiceException, Exception {

		if ((topic == null) || (tml == null))
			throw new NullPointerException(
					"one or more input arguments is NULL");

		if (!m_initialized.get()) {
			throw new MessageServiceException(
					MessageServiceException.SERVICE_UNINITIALIZED,
					"Message service not Initialized");
		}

		addSubscriber(topic, tml);

		createDispatcherRegisterWithTransport(topic);

	}

	public List getTransportProviders() {

		Enumeration dispatcherlist = m_dispatcherlist
				.elements();

		List transports = new CopyOnWriteArrayList();
		while (dispatcherlist.hasMoreElements()) {
			DispatcherInfo dispinfo = dispatcherlist.nextElement();
			transports.add(dispinfo.getTransport());
		}

		return transports;

	}

	/**
	 * @param topic
	 * @param tml
	 * @throws MessageServiceException
	 * @throws Exception
	 */
	public void unsubscribe(
			JetstreamTopic topic,
			com.ebay.jetstream.messaging.interfaces.IMessageListener tml)
			throws MessageServiceException, Exception {

		if ((topic == null) || (tml == null))
			throw new NullPointerException(
					"one or more input arguments is NULL");

		if (!m_initialized.get()) {
			throw new MessageServiceException(
					MessageServiceException.SERVICE_UNINITIALIZED,
					"Message service not Initialized");
		}

		if (m_listenerTable.containsKey(topic.getRootContext())) {

			ConcurrentLinkedQueue llist = m_listenerTable
					.get(topic.getRootContext());

			if (llist == null)
				return;

			ListenerInfo li = new ListenerInfo();

			li.m_listener = tml;
			li.m_topic = topic;

			llist.remove(li);

			if (llist.isEmpty()) {

				DispatcherInfo dispinfo = m_dispatcherlist.get(topic
						.getRootContext());

				if (dispinfo == null) {

					if (LOGGER.isErrorEnabled()) {
						String message = "Did not find any transport from config for context - ";
						message += topic.getRootContext();

						LOGGER.error( message);

						return;
					}
				} else {
					if (dispinfo.isDispatcherCreated()) {

						dispinfo.getTransport().unregisterTopic(topic);

					}
				}

			}
		}
	}

	@Override
	public int getPendingEvents() {
		return 0;
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy