/*******************************************************************************
$Source: /cvs/repositories/openii3/project/java/source/org/openeai/jms/producer/PubSubProducer.java,v $
$Revision: 1.26 $
*******************************************************************************/
/**********************************************************************
This file is part of the OpenEAI Application Foundation or
OpenEAI Message Object API created by Tod Jackson
(tod@openeai.org) and Steve Wheat (steve@openeai.org) at
the University of Illinois Urbana-Champaign.
Copyright (C) 2002 The OpenEAI Software Foundation
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
For specific licensing details and examples of how this software
can be used to build commercial integration software or to implement
integrations for your enterprise, visit http://www.OpenEai.org/licensing.
*/
package org.openeai.jms.producer;
// JNDI Stuff
import javax.naming.*;
import javax.naming.directory.*;
// Java Messaging Service
import javax.jms.*;
// General
import java.util.*;
import java.io.*;
// JDOM
import org.jdom.Document;
import org.jdom.output.XMLOutputter;
import org.openeai.config.*;
import org.openeai.transport.ProducerId;
import org.openeai.transport.SyncService;
import org.openeai.transport.TransportException;
import org.openeai.moa.ActionableEnterpriseObject;
import org.openeai.moa.XmlEnterpriseObject;
/**
* The PubSubProducer produces messages to a JMS Topic.
* <P>
* @author Tod Jackson (tod@openeai.org)
* @author Steve Wheat (steve@openeai.org)
* @version 3.0 - 4 February 2003
* @see PointToPointProducer
* @see org.openeai.jms.consumer.PubSubConsumer
*/
public class PubSubProducer
extends MessageProducer implements SyncService {
private TopicConnectionFactory m_tcf = null;
private Topic m_topic = null;
private TopicConnection m_topicConnection;
private TopicSession m_topicSession;
private TopicPublisher m_topicPublisher;
private PubSubProducer m_loggingProducer = null;
private boolean m_monitorRunning = false;
public PubSubProducer() {
setAppName("Pub/Sub Producer v1.0");
}
/**
* As AppConfig reads through an application's deployment document, it will build a
* ProducerConfig Java object and pass that object to this constructor. Then
* this producer will have all the information it needs to initialize itself which.
* <P>
* @param pConfig org.openeai.config.ProducerConfig
* @see org.openeai.config.ProducerConfig
* @see org.openeai.jms.producer.PointToPointProducer
**/
public PubSubProducer(ProducerConfig pConfig) throws IOException, JMSException {
setAppName("Pub/Sub Producer v1.0");
init(pConfig.getProperties());
if (getStartOnInitialization()) {
startPublisher();
}
setLoggingProducer(pConfig.getLoggingProducer());
}
// StartGetter/Setters
/**
* Returns the Producers's TopicConnectionFactory object.
* <P>
* See the JMS Specification to learn more about JMS objects.
* <P>
* @return javax.jms.TopicConnectionFactory
**/
public final TopicConnectionFactory getTopicConnectionFactory() {
return m_tcf;
}
/**
* Sets the Producers's TopicConnectionFactory object.
* <P>
* See the JMS Specification to learn more about JMS objects.
* <P>
* @param tcf javax.jms.TopicConnectionFactory
**/
public final void setTopicConnectionFactory(TopicConnectionFactory tcf) {
m_tcf = tcf;
}
/**
* Returns the Producers's Topic object.
* <P>
* See the JMS Specification to learn more about JMS objects.
* <P>
* @return javax.jms.Topic
**/
public final Topic getTopic() {
return m_topic;
}
/**
* Sets the Producers's Topic object.
* <P>
* See the JMS Specification to learn more about JMS objects.
* <P>
* @param topic javax.jms.Topic
**/
public final void setTopic(Topic topic) {
m_topic = topic;
}
/**
* Returns the Producers's TopicConnection object.
* <P>
* See the JMS Specification to learn more about JMS objects.
* <P>
* @return javax.jms.TopicConnection
**/
public final TopicConnection getTopicConnection() {
return m_topicConnection;
}
/**
* Sets the Producers's TopicConnection object.
* <P>
* See the JMS Specification to learn more about JMS objects.
* <P>
* @param topicConnection javax.jms.TopicConnection
**/
public final void setTopicConnection(TopicConnection topicConnection) {
m_topicConnection = topicConnection;
}
/**
* Returns the Producers's TopicSession object.
* <P>
* See the JMS Specification to learn more about JMS objects.
* <P>
* @return javax.jms.TopicSession
**/
public final TopicSession getTopicSession() {
return m_topicSession;
}
/**
* Sets the Producers's TopicSession object.
* <P>
* See the JMS Specification to learn more about JMS objects.
* <P>
* @param session javax.jms.TopicSession
**/
public final void setTopicSession(TopicSession session) {
m_topicSession = session;
}
/**
* Returns the Producers's TopicPublisher object.
* <P>
* See the JMS Specification to learn more about JMS objects.
* <P>
* @return javax.jms.TopicPublisher
**/
public final TopicPublisher getTopicPublisher() {
return m_topicPublisher;
}
/**
* Sets the Producers's TopicPublisher object.
* <P>
* See the JMS Specification to learn more about JMS objects.
* <P>
* @return javax.jms.TopicPublisher
**/
public final void setTopicPublisher(TopicPublisher topicPublisher) {
m_topicPublisher = topicPublisher;
}
// End Getter/Setters
/**
* Sets the Logging Producer associated to this producer which is simply another
* PubSubProducer that may be configured in the deployment document to publish
* messages published by this producer to a second "logging" destination.
* <P>
* @param producer PubSubProducer the "logging producer"
* @see org.openeai.config.ProducerConfig
**/
public final void setLoggingProducer(PubSubProducer producer) {
m_loggingProducer = producer;
}
/**
* Returns the Logging Producer associated to this producer which is simply another
* PubSubProducer that may be configured in the deployment document to publish
* messages published by this producer to a second "logging" destination.
* <P>
* @return PubSubProducer the "logging producer"
* @see org.openeai.config.ProducerConfig
**/
public final PubSubProducer getLoggingProducer() {
return m_loggingProducer;
}
/**
* This method stops the Producer's "Monitor Thread" so it won't attempt
* to restart the producer.
* <P>
* When the producer is started it starts a Thread that monitors the Producer's
* connection to the broker. If that connection is broken for some reason, that
* "Monitor Thread" will attempt to restart the producer. This continues indefinitely
* until the producer is able to re-connect to the broker.
* <P>
* This method allows an application to in effect stop that monitor thread so they can
* shut the producer down without it restarting itself.
**/
public void stopMonitor() {
m_monitorRunning = false;
}
/**
* This method starts the Producer's "Monitor Thread". This is a thread that runs
* for the life of the producer and checks the status of the producer's connection
* to the broker every thirty seconds. If that connection is broken
* for some reason, the Monitor Thread will attempt to restart the producer,
* re-connecting it to the broker. It will continue to do this until either the
* producer is able to re-connect or the producer is shutdown.
**/
public void startMonitor() {
if (m_monitorRunning == false) {
MonitorProducer monitorProducer = new MonitorProducer(30000);
new Thread(monitorProducer).start();
m_monitorRunning = true;
}
}
public final void stop() {
stopPublisher();
}
/**
* Attempts to cleanly shutdown the Producer. This includes closing all JMS
* resources (TopicPublisher, TopicSession and TopicConnection). If errors
* occur, it will log those errors as warnings. However, regardless of the
* outcome of the "clean" shutdown attempt, the producer will be stopped. This
* method is called anytime the producer detects connection problems to the broker
* or when specifically called from an application or gateway using this Producer.
* <P>
* @see MonitorProducer
**/
public final void stopPublisher() {
boolean exceptionOccurred = false;
setProducerStatus(STOPPING);
stopMonitor();
try {
if (m_topicPublisher != null) {
m_topicPublisher.close();
}
}
catch (Exception jmse) {
exceptionOccurred = true;
logger.warn("Error closing TopicPublisher: " + jmse.getMessage());
}
try {
if (m_topicSession != null) {
m_topicSession.close();
}
}
catch (Exception jmse) {
exceptionOccurred = true;
logger.warn("Error closing TopicSession: " + jmse.getMessage());
}
try {
if (m_topicConnection != null) {
m_topicConnection.close();
}
}
catch (Exception jmse) {
exceptionOccurred = true;
logger.warn("Error closing TopicConnection: " + jmse.getMessage());
}
if (exceptionOccurred) {
logger.info("Everything was stopped but there were exceptions.");
}
else {
logger.info("Everything was stopped successfully!");
}
setProducerStatus(STOPPED);
if (getLoggingProducer() != null) {
if (getLoggingProducer().isStarted()) {
getLoggingProducer().stopPublisher();
}
}
}
/**
* Commit any transactions pending on this publisher and on its LoggingProducer.
**/
public final void commit() throws JMSException {
// commit any pending transactions on this producer's session.
getTopicSession().commit();
if (getLoggingProducer() != null) {
if (getLoggingProducer().isStarted()) {
// commit any pending transactions on the logging producer's session.
if (getLoggingProducer().getTransacted()) {
getLoggingProducer().commit();
}
}
}
}
/**
* Rollback any transactions pending on this publisher and on its LoggingProducer.
**/
public final void rollback() throws JMSException {
// commit any pending transactions on this producer's session.
getTopicSession().rollback();
if (getLoggingProducer() != null) {
if (getLoggingProducer().isStarted()) {
// commit any pending transactions on the logging producer's session.
if (getLoggingProducer().getTransacted()) {
getLoggingProducer().rollback();
}
}
}
}
public final boolean start() throws JMSException {
return startPublisher();
}
/**
* Starts the producer making it ready to publish messages to the Topic that
* it connects to. This follows the typical JMS pattern of starting a message producer.
* This includes:
* <ul>
* <li>Retrieving the JMS Administered objects (TopicConnectionFactory and Topic)
* from a directory server or other JNDI source
* <li>Creating a TopicConnection with the TopicConnectionFactory
* <li>Creating a TopicSession with the TopicConnection
* <li>Creating a TopicPublisher with the TopicSession and Topic
* </ul>
* <P>
* @return boolean indicating whether or not the start was successful.
* @throws JMSException
* @see PointToPointProducer#startProducer
**/
public final boolean startPublisher() throws JMSException {
setProducerStatus(STOPPED);
logger.info("I'm the " + getProducerName() + " JMS Pub/Sub Producer");
if (getConnectionFactoryName() == null || getDestinationName() == null ||
getProviderUrl() == null || getInitialContextFactory() == null) {
logger.fatal("Nothing's been initialized, can't start.");
return false;
}
if (getProducerId(null) == null) {
try {
logger.debug("Getting a ProducerId object...");
setProducerId(new ProducerId(getProducerIdUrl()));
logger.debug("Producer id is " + getProducerId(null).getId());
}
catch (IOException ioe) {
logger.fatal(ioe.getMessage(), ioe);
return false;
}
}
if (m_tcf == null || m_topic == null) {
// Create InitialContext object
DirContext ic = null;
// Assume the m_providerUrl and m_initCtxFactory variables have already
// been set.
try {
ic = getInitialContext();
if (ic == null) {
logger.fatal("Error creating initial context");
return false;
}
logger.debug("Created initial context");
}
catch (NamingException ne) {
logger.fatal(ne.getMessage(), ne);
throw new JMSException(ne.getMessage());
}
// Lookup TopicConnectionFactory and Topic names
try {
logger.debug("Looking up topic connection factory name " + getConnectionFactoryName());
m_tcf = (TopicConnectionFactory)ic.lookup(getConnectionFactoryName());
logger.debug("Looking up topic name " + getDestinationName());
m_topic = (Topic)ic.lookup(getDestinationName());
// Close InitialContext resources
logger.debug("Closing initial context");
ic.close();
}
catch (NamingException ne) {
logger.fatal(ne.getMessage(), ne);
throw new JMSException(ne.getMessage());
}
catch (ClassCastException ce) {
logger.fatal(ce.getMessage(), ce);
throw new JMSException(ce.getMessage());
}
catch (Exception e) {
logger.fatal(e.getMessage(), e);
throw new JMSException(e.getMessage());
}
}
else {
logger.info("No need to create TCF or Topic.");
}
try {
logger.debug("Creating topic connection");
if (getUserName() == null || getUserName().length() < 1) {
m_topicConnection = m_tcf.createTopicConnection();
}
else {
m_topicConnection = m_tcf.createTopicConnection(getUserName(), getPassword());
}
logger.debug("Creating topic session");
m_topicSession = m_topicConnection.createTopicSession(getTransacted(),TopicSession.AUTO_ACKNOWLEDGE);
logger.debug("Creating topic publisher");
m_topicPublisher = m_topicSession.createPublisher(m_topic);
m_topicPublisher.setPriority(9); // Default to highest priority unless it's overridden.
logger.debug("Starting topic connection");
m_topicConnection.start();
setProducerStatus(STARTED);
logger.info(getProducerName() + " - Pub/Sub Producer Started successfully.");
}
catch (javax.jms.JMSException jmse) {
logger.fatal(jmse.getMessage(), jmse);
logger.fatal("Username: " + getUserName() + " Password: " + getPassword());
throw jmse;
}
catch (Exception e) {
logger.fatal(e.getMessage(), e);
logger.fatal("Username: " + getUserName() + " Password: " + getPassword());
throw new JMSException(e.getMessage());
}
catch (Throwable te) {
logger.fatal(te.getMessage(), te);
logger.fatal("Username: " + getUserName() + " Password: " + getPassword());
throw new JMSException(te.getMessage());
}
// Start the monitor if it isn't already running.
startMonitor();
return true;
}
/**
* Convenience method that allows calling applications to use this producer to
* create a JMS TextMessage that is used during message production. This is used
* most commonly by the OpenEAI Message Object API (MOA) foundation.
* <P>
* @return a JMS TextMessage
**/
public final TextMessage createTextMessage() {
TextMessage message = null;
try {
message = m_topicSession.createTextMessage();
}
catch (JMSException jmse) {
logger.fatal(jmse.getMessage(), jmse);
}
return message;
}
/**
* Publishes the message passed in to the Topic the producer
* is connected to. If an error occurs
* publishing the message the first time, the producer will be restarted and
* the producer will attempt to publish the message again. If it cannot successfully
* restart and re-publish the message, an exception will be thrown.
* <P>
* If the "logging producer" is started, the message will
* also be published to the Topic it is connected to.
* @param aMessage javax.jms.Message the message to produce
* @throws JMSException if errors occur.
**/
public final synchronized boolean publishMessage(Message aMessage) throws JMSException {
try {
logger.debug("Started - Publishing message through " + m_topic.getTopicName());
if (getDeliveryMode().equals(PERSISTENT_DELIVERY)) {
m_topicPublisher.publish(aMessage, javax.jms.DeliveryMode.PERSISTENT, 9, 0);
}
else {
m_topicPublisher.publish(aMessage, javax.jms.DeliveryMode.NON_PERSISTENT, 9, 0);
}
logger.debug("Done - Published message through " + m_topic.getTopicName());
incrementMessageSequence();
publishMessageToLog(aMessage);
}
catch (JMSException jmse) {
try {
// Attempt to restart the publisher and re-publish the message...
logger.warn("Error publishing message: " + jmse.getMessage() +
" attempting to restart publisher and re-publish the message.");
try {
stopPublisher();
startPublisher();
logger.info("Publisher was re-started successfully, attempting to re-publish the message.");
m_topicPublisher.publish(aMessage);
incrementMessageSequence();
logger.debug("Published message through " + m_topic.getTopicName());
publishMessageToLog(aMessage);
}
catch (JMSException e) {
logger.fatal("Attempt to restart the producer failed. Exception: " + e.getMessage());
throw e;
}
}
catch (JMSException je) {
logger.fatal("Error rolling transaction back or determining the transaction mode.");
logger.fatal(je.getMessage(), je);
throw je;
}
}
catch (Exception e) {
logger.fatal("Unknown exception occurred while attempting to publish the message. Exception: " + e.getMessage());
logger.fatal(e.getMessage(), e);
TextMessage tMsg = (TextMessage)aMessage;
logger.fatal("Message contents: \n" + tMsg.getText());
throw new JMSException(e.getMessage());
}
return true;
}
/**
* Publishes the message passed in to the Topic the producer
* is connected to. If an error occurs
* publishing the message the first time, the producer will be restarted and
* the producer will attempt to publish the message again. If it cannot successfully
* restart and re-publish the message, an exception will be thrown.
* <P>
* If the "logging producer" is started, the message will
* also be published to the Topic it is connected to.
* @param theObject the ActionableEnterpriseObject on which the action is being performed
* @param doc the XML document that has been built from the contents of theObject
* @throws TransportExceptom if errors occur.
**/
public boolean publishMessage(ActionableEnterpriseObject theObject, Document doc)
throws TransportException {
TextMessage outMessage = createTextMessage();
XMLOutputter xmlOut = new XMLOutputter();
String messageBody = xmlOut.outputString(doc);
String xmlMsgId = theObject.getMessageId().toString();
logger.debug("PubSubProducer: ProducerId is: " + getProducerId(null).getId());
logger.debug("PubSubProducer: MessageId is: " + xmlMsgId);
try {
logger.debug("Started - Publishing message through " + m_topic.getTopicName());
outMessage.setText(messageBody);
outMessage.setStringProperty(MessageProducer.COMMAND_NAME, theObject.getCommandName());
outMessage.setStringProperty(MessageProducer.MESSAGE_NAME, theObject.getCommandName()); // backward compatibility
outMessage.setStringProperty(MessageProducer.MESSAGE_ID, xmlMsgId);
if (getDeliveryMode().equals(PERSISTENT_DELIVERY)) {
m_topicPublisher.publish(outMessage, javax.jms.DeliveryMode.PERSISTENT, 9, 0);
}
else {
m_topicPublisher.publish(outMessage, javax.jms.DeliveryMode.NON_PERSISTENT, 9, 0);
}
logger.debug("Done - Published message through " + m_topic.getTopicName());
incrementMessageSequence();
publishMessageToLog(outMessage);
}
catch (JMSException jmse) {
try {
// Attempt to restart the publisher and re-publish the message...
logger.warn("Error publishing message: " + jmse.getMessage() +
" attempting to restart publisher and re-publish the message.");
try {
stopPublisher();
startPublisher();
logger.info("Publisher was re-started successfully, attempting to re-publish the message.");
m_topicPublisher.publish(outMessage);
incrementMessageSequence();
logger.debug("Published message through " + m_topic.getTopicName());
publishMessageToLog(outMessage);
}
catch (JMSException e) {
logger.fatal("Attempt to restart the producer failed. Exception: " + e.getMessage());
throw e;
}
}
catch (JMSException je) {
logger.fatal("Error rolling transaction back or determining the transaction mode.");
logger.fatal(je.getMessage(), je);
throw new TransportException(je.getMessage(), je);
}
}
catch (Exception e) {
logger.fatal("Unknown exception occurred while attempting to publish the message. Exception: " + e.getMessage());
logger.fatal(e.getMessage(), e);
TextMessage tMsg = (TextMessage)outMessage;
try {
logger.fatal("Message contents: \n" + tMsg.getText());
}
catch (JMSException je2) { }
throw new TransportException(e.getMessage(), e);
}
return true;
}
/**
* @param theObject the ActionableEnterpriseObject on which the action is being performed
* @param doc the XML document that has been built from the contents of theObject
* @throws TransportExceptom if errors occur.
**/
private final void publishMessageToLog(ActionableEnterpriseObject theObject, Document doc)
throws TransportException {
if (getLoggingProducer() != null) {
if (getLoggingProducer().isStarted()) {
getLoggingProducer().publishMessage(theObject, doc);
}
}
}
/**
* @param aMessage javax.jms.Message the message to produce
* @throws JMSException if errors occur.
**/
private final void publishMessageToLog(Message aMessage) throws JMSException {
if (getLoggingProducer() != null) {
if (getLoggingProducer().isStarted()) {
if (getLoggingProducer().getDefaultCommandName().length() == 0 ||
getLoggingProducer().getDefaultCommandName() == null) {
getLoggingProducer().setDefaultCommandName("EnterpriseSyncLogger");
}
String messageId = aMessage.getStringProperty("MESSAGE_ID");
aMessage.clearProperties();
aMessage.setStringProperty("COMMAND_NAME",getLoggingProducer().getDefaultCommandName());
aMessage.setStringProperty("MESSAGE_ID",messageId);
getLoggingProducer().publishMessage(aMessage);
}
}
}
public void createSync(ActionableEnterpriseObject theObject)
throws TransportException {
try {
theObject.createSync(this);
}
catch (Exception e) {
String errMessage = "Error processing the create sync for object " +
getClass().getName() + " Exception: " + e.getMessage();
throw new TransportException(errMessage, e);
}
}
public void updateSync(ActionableEnterpriseObject theObject)
throws TransportException {
try {
theObject.updateSync(this);
}
catch (Exception e) {
String errMessage = "Error processing the update sync for object " +
getClass().getName() + " Exception: " + e.getMessage();
throw new TransportException(errMessage, e);
}
}
public void deleteSync(String deleteAction, ActionableEnterpriseObject theObject)
throws TransportException {
try {
theObject.deleteSync(deleteAction, this);
}
catch (Exception e) {
String errMessage = "Error processing the delete sync for object " +
getClass().getName() + " Exception: " + e.getMessage();
throw new TransportException(errMessage, e);
}
}
/**
* Invokes MessageProducer.init(Properties). Additionally, adds the shutdown hook.
* <P>
* @param props Properties
* @throws IOException
* @see MessageProducer#init(Properties)
**/
protected void init(Properties props) throws IOException {
super.init(props);
Runtime.getRuntime().addShutdownHook(new ProducerShutdownHook());
}
/**
* This Thread will sleep for 30 seconds and then wake up
* and check the status of the producer by attempting to create/delete a TemporaryTopic.
* If the creation of the TemporaryTopic fails, it assumes there is something wrong
* with the producer's connection to the broker and it will therefore not be able to produce
* any messages. When that happens, it attempts to do a "clean" shutdown on the producer and
* then restarts the producer which will re-establish its connection to the broker
* and it will be able to produce messages again. This means, if we have to take down
* a broker for any reason (on purpose or not), we will NOT have to restart all our
* "long running" producers when the broker comes back up, rather, they will do that themselves.
* <P>
* The thread is started when the producer is started the first time and continues
* this process for the life of the producer.
* <P>
* @author Tod Jackson
*/
protected class MonitorProducer implements java.lang.Runnable {
private int m_sleepInterval = 30000; // thirty seconds
public MonitorProducer(int sleepInterval) {
m_sleepInterval = sleepInterval;
}
private void restartProducer() {
if (getProducerStatus().equalsIgnoreCase(STOPPING) == false) {
stopPublisher();
try {
startPublisher();
}
catch (Exception e1) {
logger.fatal("Error restarting producer. Exception: " + e1.getMessage());
}
}
else {
logger.info("Publisher " + getProducerName() + " is in the process of being restarted, can't restart it in the MonitorProducer thread now.");
}
}
public void run() {
// sleep for m_sleepInterval
// wake up, try to do something with the session
// if an exception occurs, restart the producer
boolean stayAlive = true;
while(stayAlive) {
try {
Thread.sleep(m_sleepInterval);
}
catch (Exception e) {
logger.fatal("Error sleeping...");
}
// wake up and try to access the session
if (m_monitorRunning == false) {
logger.info("Monitor has been stopped. Returning from Monitor Thread.");
return;
}
try {
if (m_topicSession != null) {
TemporaryTopic tb = m_topicSession.createTemporaryTopic();
tb.delete();
logger.debug("Session is okay.");
}
else {
logger.warn("Session is null, need to restart the " + getProducerName() + " producer.");
restartProducer();
}
}
catch(JMSException e) {
logger.warn("Session is not usable, need to restart the " + getProducerName() + " producer. Exception: " + e.getMessage());
restartProducer();
}
}
}
}
/**
* This Thread will be started when the producer receives a shutdown signal from the os.
* It is established via the Runtime.getRuntime().addShutdownHook(new ProducerShutdownHook());
* in the init() method. The purpose of this is to allow a "clean" shutdown of the producer.
* <P>
* @author Tod Jackson
*/
protected class ProducerShutdownHook extends Thread {
public void run() {
logger.info(getProducerName() + " - Producer shutdown hook, stopping producer");
m_monitorRunning = false;
stopPublisher();
logger.info(getProducerName() + " - Producer shutdown hook, producer stopped, now exiting.");
}
}
}