/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.activemq;

import java.net.URI;
import java.net.URISyntaxException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.RejectedExecutionHandler;

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.ExceptionListener;
import javax.jms.JMSException;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.TopicConnection;
import javax.jms.TopicConnectionFactory;
import javax.naming.Context;

import org.apache.activemq.blob.BlobTransferPolicy;
import org.apache.activemq.broker.region.policy.RedeliveryPolicyMap;
import org.apache.activemq.jndi.JNDIBaseStorable;
import org.apache.activemq.management.JMSStatsImpl;
import org.apache.activemq.management.StatsCapable;
import org.apache.activemq.management.StatsImpl;
import org.apache.activemq.thread.TaskRunnerFactory;
import org.apache.activemq.transport.Transport;
import org.apache.activemq.transport.TransportFactory;
import org.apache.activemq.transport.TransportListener;
import org.apache.activemq.util.IdGenerator;
import org.apache.activemq.util.IntrospectionSupport;
import org.apache.activemq.util.JMSExceptionSupport;
import org.apache.activemq.util.URISupport;
import org.apache.activemq.util.URISupport.CompositeData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ActiveMQConnectionFactory extends JNDIBaseStorable implements ConnectionFactory, QueueConnectionFactory, TopicConnectionFactory, StatsCapable, Cloneable {

   private static final Logger LOG = LoggerFactory.getLogger(ActiveMQConnectionFactory.class);
   private static final String DEFAULT_BROKER_HOST;
   private static final int DEFAULT_BROKER_PORT;
   private static URI defaultTcpUri;

   static {
      String host = null;
      String port = null;
      try {
         host = AccessController.doPrivileged(new PrivilegedAction<String>() {
            @Override
            public String run() {
               String result = System.getProperty("org.apache.activemq.AMQ_HOST");
               result = (result == null || result.isEmpty()) ? System.getProperty("AMQ_HOST", "localhost") : result;
               return result;
            }
         });
         port = AccessController.doPrivileged(new PrivilegedAction<String>() {
            @Override
            public String run() {
               String result = System.getProperty("org.apache.activemq.AMQ_PORT");
               result = (result == null || result.isEmpty()) ? System.getProperty("AMQ_PORT", "61616") : result;
               return result;
            }
         });
      }
      catch (Throwable e) {
         LOG.debug("Failed to look up System properties for host and port", e);
      }
      host = (host == null || host.isEmpty()) ? "localhost" : host;
      port = (port == null || port.isEmpty()) ? "61616" : port;
      DEFAULT_BROKER_HOST = host;
      DEFAULT_BROKER_PORT = Integer.parseInt(port);
   }

   public static final String DEFAULT_BROKER_BIND_URL;

   static {
      final String defaultURL = "tcp://" + DEFAULT_BROKER_HOST + ":" + DEFAULT_BROKER_PORT;
      String bindURL = null;

      try {
         bindURL = AccessController.doPrivileged(new PrivilegedAction<String>() {
            @Override
            public String run() {
               String result = System.getProperty("org.apache.activemq.BROKER_BIND_URL");
               result = (result == null || result.isEmpty()) ? System.getProperty("BROKER_BIND_URL", defaultURL) : result;
               return result;
            }
         });
      }
      catch (Throwable e) {
         LOG.debug("Failed to look up System properties for host and port", e);
      }
      bindURL = (bindURL == null || bindURL.isEmpty()) ? defaultURL : bindURL;
      DEFAULT_BROKER_BIND_URL = bindURL;
      try {
         defaultTcpUri = new URI(defaultURL);
      }
      catch (URISyntaxException e) {
         LOG.debug("Failed to build default tcp url", e);
      }

   }

   public static final String DEFAULT_BROKER_URL = "failover://" + DEFAULT_BROKER_BIND_URL;
   public static final String DEFAULT_USER = null;
   public static final String DEFAULT_PASSWORD = null;
   public static final int DEFAULT_PRODUCER_WINDOW_SIZE = 0;

   protected URI brokerURL;
   protected URI vmBrokerUri;
   protected String userName;
   protected String password;
   protected String clientID;
   protected boolean dispatchAsync = true;
   protected boolean alwaysSessionAsync = true;

   JMSStatsImpl factoryStats = new JMSStatsImpl();

   private IdGenerator clientIdGenerator;
   private String clientIDPrefix;
   private IdGenerator connectionIdGenerator;
   private String connectionIDPrefix;

   // client policies
   private ActiveMQPrefetchPolicy prefetchPolicy = new ActiveMQPrefetchPolicy();
   private RedeliveryPolicyMap redeliveryPolicyMap = new RedeliveryPolicyMap();

   {
      redeliveryPolicyMap.setDefaultEntry(new RedeliveryPolicy());
   }

   private BlobTransferPolicy blobTransferPolicy = new BlobTransferPolicy();
   private MessageTransformer transformer;

   private boolean disableTimeStampsByDefault;
   private boolean optimizedMessageDispatch = true;
   private long optimizeAcknowledgeTimeOut = 300;
   private long optimizedAckScheduledAckInterval = 0;
   private boolean copyMessageOnSend = true;
   private boolean useCompression;
   private boolean objectMessageSerializationDefered;
   private boolean useAsyncSend;
   private boolean optimizeAcknowledge;
   private int closeTimeout = 15000;
   private boolean useRetroactiveConsumer;
   private boolean exclusiveConsumer;
   private boolean nestedMapAndListEnabled = true;
   private boolean alwaysSyncSend;
   private boolean watchTopicAdvisories = true;
   private int producerWindowSize = DEFAULT_PRODUCER_WINDOW_SIZE;
   private long warnAboutUnstartedConnectionTimeout = 500L;
   private int sendTimeout = 0;
   private boolean sendAcksAsync = true;
   private TransportListener transportListener;
   private ExceptionListener exceptionListener;
   private int auditDepth = ActiveMQMessageAuditNoSync.DEFAULT_WINDOW_SIZE;
   private int auditMaximumProducerNumber = ActiveMQMessageAuditNoSync.MAXIMUM_PRODUCER_COUNT;
   private boolean useDedicatedTaskRunner;
   private long consumerFailoverRedeliveryWaitPeriod = 0;
   private boolean checkForDuplicates = true;
   private ClientInternalExceptionListener clientInternalExceptionListener;
   private boolean messagePrioritySupported = false;
   private boolean transactedIndividualAck = false;
   private boolean nonBlockingRedelivery = false;
   private int maxThreadPoolSize = ActiveMQConnection.DEFAULT_THREAD_POOL_SIZE;
   private TaskRunnerFactory sessionTaskRunner;
   private RejectedExecutionHandler rejectedTaskHandler = null;
   protected int xaAckMode = -1; // ensure default init before setting via brokerUrl introspection in sub class
   private boolean rmIdFromConnectionId = false;
   private boolean consumerExpiryCheckEnabled = true;

   // /////////////////////////////////////////////
   //
   // ConnectionFactory, QueueConnectionFactory, TopicConnectionFactory Methods
   //
   // /////////////////////////////////////////////

   public ActiveMQConnectionFactory() {
      this(DEFAULT_BROKER_URL);
   }

   public ActiveMQConnectionFactory(String brokerURL) {
      this(createURI(brokerURL));
      try {
         URI uri = new URI(brokerURL);
         String scheme = uri.getScheme();
         if ("vm".equals(scheme)) {
            Map<String, String> params = URISupport.parseParameters(uri);
            params.clear();

            this.vmBrokerUri = URISupport.createRemainingURI(uri, params);
         }
      }
      catch (URISyntaxException e) {
      }

   }

   public ActiveMQConnectionFactory(URI brokerURL) {
      setBrokerURL(brokerURL.toString());
   }

   public ActiveMQConnectionFactory(String userName, String password, URI brokerURL) {
      setUserName(userName);
      setPassword(password);
      setBrokerURL(brokerURL.toString());
   }

   public ActiveMQConnectionFactory(String userName, String password, String brokerURL) {
      setUserName(userName);
      setPassword(password);
      setBrokerURL(brokerURL);
   }

   public ActiveMQConnectionFactory copy() {
      try {
         return (ActiveMQConnectionFactory) super.clone();
      }
      catch (CloneNotSupportedException e) {
         throw new RuntimeException("This should never happen: " + e, e);
      }
   }

   private static URI createURI(String brokerURL) {
      try {
         URI uri = new URI(brokerURL);
         String scheme = uri.getScheme();
         if ("vm".equals(scheme)) {
            Map<String, String> params = URISupport.parseParameters(uri);
            //EMPTY_MAP is immutable, so use a normal map instead.
            if (params == Collections.EMPTY_MAP) {
               params = new HashMap<>();
            }
            params.put("invmBrokerId", uri.getHost() == null ? "localhost" : uri.getHost());
            defaultTcpUri = URISupport.createRemainingURI(defaultTcpUri, params);
            return defaultTcpUri;
         }
         return uri;
      }
      catch (URISyntaxException e) {
         throw (IllegalArgumentException) new IllegalArgumentException("Invalid broker URI: " + brokerURL).initCause(e);
      }
   }

   @Override
   public Connection createConnection() throws JMSException {
      return createActiveMQConnection();
   }

   @Override
   public Connection createConnection(String userName, String password) throws JMSException {
      return createActiveMQConnection(userName, password);
   }

   @Override
   public QueueConnection createQueueConnection() throws JMSException {
      return createActiveMQConnection().enforceQueueOnlyConnection();
   }

   @Override
   public QueueConnection createQueueConnection(String userName, String password) throws JMSException {
      return createActiveMQConnection(userName, password).enforceQueueOnlyConnection();
   }

   @Override
   public TopicConnection createTopicConnection() throws JMSException {
      return createActiveMQConnection();
   }

   @Override
   public TopicConnection createTopicConnection(String userName, String password) throws JMSException {
      return createActiveMQConnection(userName, password);
   }

   @Override
   public StatsImpl getStats() {
      return this.factoryStats;
   }

   // /////////////////////////////////////////////
   //
   // Implementation methods.
   //
   // /////////////////////////////////////////////

   protected ActiveMQConnection createActiveMQConnection() throws JMSException {
      return createActiveMQConnection(userName, password);
   }

   protected Transport createTransport() throws JMSException {
      try {
         System.out.println("xxxxxcreating conn: " + brokerURL.toString());
         Transport t = TransportFactory.connect(brokerURL);
         System.out.println("xxxxxxxxxxxx created transport" + t);
         return t;
      }
      catch (Exception e) {
         throw JMSExceptionSupport.create("Could not create Transport. Reason: " + e, e);
      }
   }

   protected ActiveMQConnection createActiveMQConnection(String userName, String password) throws JMSException {
      if (brokerURL == null) {
         throw new ConfigurationException("brokerURL not set.");
      }
      ActiveMQConnection connection = null;
      try {
         Transport transport = createTransport();
         connection = createActiveMQConnection(transport, factoryStats);

         connection.setUserName(userName);
         connection.setPassword(password);

         configureConnection(connection);

         transport.start();

         if (clientID != null) {
            connection.setDefaultClientID(clientID);
         }

         return connection;
      }
      catch (JMSException e) {
         // Clean up!
         try {
            connection.close();
         }
         catch (Throwable ignore) {
         }
         throw e;
      }
      catch (Exception e) {
         // Clean up!
         try {
            connection.close();
         }
         catch (Throwable ignore) {
         }
         throw JMSExceptionSupport.create("Could not connect to broker URL: " + brokerURL + ". Reason: " + e, e);
      }
   }

   protected ActiveMQConnection createActiveMQConnection(Transport transport, JMSStatsImpl stats) throws Exception {
      ActiveMQConnection connection = new ActiveMQConnection(transport, getClientIdGenerator(), getConnectionIdGenerator(), stats);
      return connection;
   }

   protected void configureConnection(ActiveMQConnection connection) throws JMSException {
      connection.setPrefetchPolicy(getPrefetchPolicy());
      connection.setDisableTimeStampsByDefault(isDisableTimeStampsByDefault());
      connection.setOptimizedMessageDispatch(isOptimizedMessageDispatch());
      connection.setCopyMessageOnSend(isCopyMessageOnSend());
      connection.setUseCompression(isUseCompression());
      connection.setObjectMessageSerializationDefered(isObjectMessageSerializationDefered());
      connection.setDispatchAsync(isDispatchAsync());
      connection.setUseAsyncSend(isUseAsyncSend());
      connection.setAlwaysSyncSend(isAlwaysSyncSend());
      connection.setAlwaysSessionAsync(isAlwaysSessionAsync());
      connection.setOptimizeAcknowledge(isOptimizeAcknowledge());
      connection.setOptimizeAcknowledgeTimeOut(getOptimizeAcknowledgeTimeOut());
      connection.setOptimizedAckScheduledAckInterval(getOptimizedAckScheduledAckInterval());
      connection.setUseRetroactiveConsumer(isUseRetroactiveConsumer());
      connection.setExclusiveConsumer(isExclusiveConsumer());
      connection.setRedeliveryPolicyMap(getRedeliveryPolicyMap());
      connection.setTransformer(getTransformer());
      connection.setBlobTransferPolicy(getBlobTransferPolicy().copy());
      connection.setWatchTopicAdvisories(isWatchTopicAdvisories());
      connection.setProducerWindowSize(getProducerWindowSize());
      connection.setWarnAboutUnstartedConnectionTimeout(getWarnAboutUnstartedConnectionTimeout());
      connection.setSendTimeout(getSendTimeout());
      connection.setCloseTimeout(getCloseTimeout());
      connection.setSendAcksAsync(isSendAcksAsync());
      connection.setAuditDepth(getAuditDepth());
      connection.setAuditMaximumProducerNumber(getAuditMaximumProducerNumber());
      connection.setUseDedicatedTaskRunner(isUseDedicatedTaskRunner());
      connection.setConsumerFailoverRedeliveryWaitPeriod(getConsumerFailoverRedeliveryWaitPeriod());
      connection.setCheckForDuplicates(isCheckForDuplicates());
      connection.setMessagePrioritySupported(isMessagePrioritySupported());
      connection.setTransactedIndividualAck(isTransactedIndividualAck());
      connection.setNonBlockingRedelivery(isNonBlockingRedelivery());
      connection.setMaxThreadPoolSize(getMaxThreadPoolSize());
      connection.setSessionTaskRunner(getSessionTaskRunner());
      connection.setRejectedTaskHandler(getRejectedTaskHandler());
      connection.setNestedMapAndListEnabled(isNestedMapAndListEnabled());
      connection.setRmIdFromConnectionId(isRmIdFromConnectionId());
      connection.setConsumerExpiryCheckEnabled(isConsumerExpiryCheckEnabled());
      if (transportListener != null) {
         connection.addTransportListener(transportListener);
      }
      if (exceptionListener != null) {
         connection.setExceptionListener(exceptionListener);
      }
      if (clientInternalExceptionListener != null) {
         connection.setClientInternalExceptionListener(clientInternalExceptionListener);
      }
   }

   // /////////////////////////////////////////////
   //
   // Property Accessors
   //
   // /////////////////////////////////////////////

   public String getBrokerURL() {
      System.out.println("vm uri: " + vmBrokerUri);
      if (vmBrokerUri != null)
         return vmBrokerUri.toString();
      return brokerURL == null ? null : brokerURL.toString();
   }

   public void setBrokerURL(String brokerURL) {
      URI uri = null;
      try {
         uri = new URI(brokerURL);
         String scheme = uri.getScheme();
         if ("vm".equals(scheme)) {
            this.vmBrokerUri = uri;
         }
      }
      catch (URISyntaxException e) {
      }
      this.brokerURL = createURI(brokerURL);

      // Use all the properties prefixed with 'jms.' to set the connection
      // factory
      // options.
      if (this.brokerURL.getQuery() != null) {
         // It might be a standard URI or...
         try {

            Map<String, String> map = URISupport.parseQuery(this.brokerURL.getQuery());
            Map<String, Object> jmsOptionsMap = IntrospectionSupport.extractProperties(map, "jms.");
            if (buildFromMap(jmsOptionsMap)) {
               if (!jmsOptionsMap.isEmpty()) {
                  String msg = "There are " + jmsOptionsMap.size() + " jms options that couldn't be set on the ConnectionFactory." + " Check the options are spelled correctly." + " Unknown parameters=[" + jmsOptionsMap + "]." + " This connection factory cannot be started.";
                  throw new IllegalArgumentException(msg);
               }

               this.brokerURL = URISupport.createRemainingURI(this.brokerURL, map);
            }

         }
         catch (URISyntaxException e) {
         }

      }
      else {

         // It might be a composite URI.
         try {
            CompositeData data = URISupport.parseComposite(this.brokerURL);
            Map<String, Object> jmsOptionsMap = IntrospectionSupport.extractProperties(data.getParameters(), "jms.");
            if (buildFromMap(jmsOptionsMap)) {
               if (!jmsOptionsMap.isEmpty()) {
                  String msg = "There are " + jmsOptionsMap.size() + " jms options that couldn't be set on the ConnectionFactory." + " Check the options are spelled correctly." + " Unknown parameters=[" + jmsOptionsMap + "]." + " This connection factory cannot be started.";
                  throw new IllegalArgumentException(msg);
               }

               this.brokerURL = data.toURI();
            }
         }
         catch (URISyntaxException e) {
         }
      }
   }

   public String getClientID() {
      return clientID;
   }

   public void setClientID(String clientID) {
      this.clientID = clientID;
   }

   public boolean isCopyMessageOnSend() {
      return copyMessageOnSend;
   }

   public void setCopyMessageOnSend(boolean copyMessageOnSend) {
      this.copyMessageOnSend = copyMessageOnSend;
   }

   public boolean isDisableTimeStampsByDefault() {
      return disableTimeStampsByDefault;
   }

   public void setDisableTimeStampsByDefault(boolean disableTimeStampsByDefault) {
      this.disableTimeStampsByDefault = disableTimeStampsByDefault;
   }

   public boolean isOptimizedMessageDispatch() {
      return optimizedMessageDispatch;
   }

   public void setOptimizedMessageDispatch(boolean optimizedMessageDispatch) {
      this.optimizedMessageDispatch = optimizedMessageDispatch;
   }

   public String getPassword() {
      return password;
   }

   public void setPassword(String password) {
      this.password = password;
   }

   public ActiveMQPrefetchPolicy getPrefetchPolicy() {
      return prefetchPolicy;
   }

   public void setPrefetchPolicy(ActiveMQPrefetchPolicy prefetchPolicy) {
      this.prefetchPolicy = prefetchPolicy;
   }

   public boolean isUseAsyncSend() {
      return useAsyncSend;
   }

   public BlobTransferPolicy getBlobTransferPolicy() {
      return blobTransferPolicy;
   }

   public void setBlobTransferPolicy(BlobTransferPolicy blobTransferPolicy) {
      this.blobTransferPolicy = blobTransferPolicy;
   }

   public void setUseAsyncSend(boolean useAsyncSend) {
      this.useAsyncSend = useAsyncSend;
   }

   public synchronized boolean isWatchTopicAdvisories() {
      return watchTopicAdvisories;
   }

   public synchronized void setWatchTopicAdvisories(boolean watchTopicAdvisories) {
      this.watchTopicAdvisories = watchTopicAdvisories;
   }

   public boolean isAlwaysSyncSend() {
      return this.alwaysSyncSend;
   }

   public void setAlwaysSyncSend(boolean alwaysSyncSend) {
      this.alwaysSyncSend = alwaysSyncSend;
   }

   public String getUserName() {
      return userName;
   }

   public void setUserName(String userName) {
      this.userName = userName;
   }

   public boolean isUseRetroactiveConsumer() {
      return useRetroactiveConsumer;
   }

   public void setUseRetroactiveConsumer(boolean useRetroactiveConsumer) {
      this.useRetroactiveConsumer = useRetroactiveConsumer;
   }

   public boolean isExclusiveConsumer() {
      return exclusiveConsumer;
   }

   public void setExclusiveConsumer(boolean exclusiveConsumer) {
      this.exclusiveConsumer = exclusiveConsumer;
   }

   public RedeliveryPolicy getRedeliveryPolicy() {
      return redeliveryPolicyMap.getDefaultEntry();
   }

   public void setRedeliveryPolicy(RedeliveryPolicy redeliveryPolicy) {
      this.redeliveryPolicyMap.setDefaultEntry(redeliveryPolicy);
   }

   public RedeliveryPolicyMap getRedeliveryPolicyMap() {
      return this.redeliveryPolicyMap;
   }

   public void setRedeliveryPolicyMap(RedeliveryPolicyMap redeliveryPolicyMap) {
      this.redeliveryPolicyMap = redeliveryPolicyMap;
   }

   public MessageTransformer getTransformer() {
      return transformer;
   }

   public int getSendTimeout() {
      return sendTimeout;
   }

   public void setSendTimeout(int sendTimeout) {
      this.sendTimeout = sendTimeout;
   }

   public boolean isSendAcksAsync() {
      return sendAcksAsync;
   }

   public void setSendAcksAsync(boolean sendAcksAsync) {
      this.sendAcksAsync = sendAcksAsync;
   }

   public boolean isMessagePrioritySupported() {
      return this.messagePrioritySupported;
   }

   public void setMessagePrioritySupported(boolean messagePrioritySupported) {
      this.messagePrioritySupported = messagePrioritySupported;
   }

   public void setTransformer(MessageTransformer transformer) {
      this.transformer = transformer;
   }

   @SuppressWarnings({"unchecked", "rawtypes"})
   @Override
   public void buildFromProperties(Properties properties) {

      if (properties == null) {
         properties = new Properties();
      }

      String temp = properties.getProperty(Context.PROVIDER_URL);
      if (temp == null || temp.length() == 0) {
         temp = properties.getProperty("brokerURL");
      }
      if (temp != null && temp.length() > 0) {
         setBrokerURL(temp);
      }

      Map<String, Object> p = new HashMap(properties);
      buildFromMap(p);
   }

   public boolean buildFromMap(Map<String, Object> properties) {
      boolean rc = false;

      ActiveMQPrefetchPolicy p = new ActiveMQPrefetchPolicy();
      if (IntrospectionSupport.setProperties(p, properties, "prefetchPolicy.")) {
         setPrefetchPolicy(p);
         rc = true;
      }

      RedeliveryPolicy rp = new RedeliveryPolicy();
      if (IntrospectionSupport.setProperties(rp, properties, "redeliveryPolicy.")) {
         setRedeliveryPolicy(rp);
         rc = true;
      }

      BlobTransferPolicy blobTransferPolicy = new BlobTransferPolicy();
      if (IntrospectionSupport.setProperties(blobTransferPolicy, properties, "blobTransferPolicy.")) {
         setBlobTransferPolicy(blobTransferPolicy);
         rc = true;
      }

      rc |= IntrospectionSupport.setProperties(this, properties);

      return rc;
   }

   @Override
   public void populateProperties(Properties props) {
      props.setProperty("dispatchAsync", Boolean.toString(isDispatchAsync()));

      if (getBrokerURL() != null) {
         props.setProperty(Context.PROVIDER_URL, getBrokerURL());
         props.setProperty("brokerURL", getBrokerURL());
      }

      if (getClientID() != null) {
         props.setProperty("clientID", getClientID());
      }

      IntrospectionSupport.getProperties(getPrefetchPolicy(), props, "prefetchPolicy.");
      IntrospectionSupport.getProperties(getRedeliveryPolicy(), props, "redeliveryPolicy.");
      IntrospectionSupport.getProperties(getBlobTransferPolicy(), props, "blobTransferPolicy.");

      props.setProperty("copyMessageOnSend", Boolean.toString(isCopyMessageOnSend()));
      props.setProperty("disableTimeStampsByDefault", Boolean.toString(isDisableTimeStampsByDefault()));
      props.setProperty("objectMessageSerializationDefered", Boolean.toString(isObjectMessageSerializationDefered()));
      props.setProperty("optimizedMessageDispatch", Boolean.toString(isOptimizedMessageDispatch()));

      if (getPassword() != null) {
         props.setProperty("password", getPassword());
      }

      props.setProperty("useAsyncSend", Boolean.toString(isUseAsyncSend()));
      props.setProperty("useCompression", Boolean.toString(isUseCompression()));
      props.setProperty("useRetroactiveConsumer", Boolean.toString(isUseRetroactiveConsumer()));
      props.setProperty("watchTopicAdvisories", Boolean.toString(isWatchTopicAdvisories()));

      if (getUserName() != null) {
         props.setProperty("userName", getUserName());
      }

      props.setProperty("closeTimeout", Integer.toString(getCloseTimeout()));
      props.setProperty("alwaysSessionAsync", Boolean.toString(isAlwaysSessionAsync()));
      props.setProperty("optimizeAcknowledge", Boolean.toString(isOptimizeAcknowledge()));
      props.setProperty("statsEnabled", Boolean.toString(isStatsEnabled()));
      props.setProperty("alwaysSyncSend", Boolean.toString(isAlwaysSyncSend()));
      props.setProperty("producerWindowSize", Integer.toString(getProducerWindowSize()));
      props.setProperty("sendTimeout", Integer.toString(getSendTimeout()));
      props.setProperty("sendAcksAsync", Boolean.toString(isSendAcksAsync()));
      props.setProperty("auditDepth", Integer.toString(getAuditDepth()));
      props.setProperty("auditMaximumProducerNumber", Integer.toString(getAuditMaximumProducerNumber()));
      props.setProperty("checkForDuplicates", Boolean.toString(isCheckForDuplicates()));
      props.setProperty("messagePrioritySupported", Boolean.toString(isMessagePrioritySupported()));
      props.setProperty("transactedIndividualAck", Boolean.toString(isTransactedIndividualAck()));
      props.setProperty("nonBlockingRedelivery", Boolean.toString(isNonBlockingRedelivery()));
      props.setProperty("maxThreadPoolSize", Integer.toString(getMaxThreadPoolSize()));
      props.setProperty("nestedMapAndListEnabled", Boolean.toString(isNestedMapAndListEnabled()));
      props.setProperty("consumerFailoverRedeliveryWaitPeriod", Long.toString(getConsumerFailoverRedeliveryWaitPeriod()));
      props.setProperty("rmIdFromConnectionId", Boolean.toString(isRmIdFromConnectionId()));
      props.setProperty("consumerExpiryCheckEnabled", Boolean.toString(isConsumerExpiryCheckEnabled()));
   }

   public boolean isUseCompression() {
      return useCompression;
   }

   public void setUseCompression(boolean useCompression) {
      this.useCompression = useCompression;
   }

   public boolean isObjectMessageSerializationDefered() {
      return objectMessageSerializationDefered;
   }

   public void setObjectMessageSerializationDefered(boolean objectMessageSerializationDefered) {
      this.objectMessageSerializationDefered = objectMessageSerializationDefered;
   }

   public boolean isDispatchAsync() {
      return dispatchAsync;
   }

   public void setDispatchAsync(boolean asyncDispatch) {
      this.dispatchAsync = asyncDispatch;
   }

   public int getCloseTimeout() {
      return closeTimeout;
   }

   public void setCloseTimeout(int closeTimeout) {
      this.closeTimeout = closeTimeout;
   }

   public boolean isAlwaysSessionAsync() {
      return alwaysSessionAsync;
   }

   public void setAlwaysSessionAsync(boolean alwaysSessionAsync) {
      this.alwaysSessionAsync = alwaysSessionAsync;
   }

   public boolean isOptimizeAcknowledge() {
      return optimizeAcknowledge;
   }

   public void setOptimizeAcknowledge(boolean optimizeAcknowledge) {
      this.optimizeAcknowledge = optimizeAcknowledge;
   }

   public void setOptimizeAcknowledgeTimeOut(long optimizeAcknowledgeTimeOut) {
      this.optimizeAcknowledgeTimeOut = optimizeAcknowledgeTimeOut;
   }

   public long getOptimizeAcknowledgeTimeOut() {
      return optimizeAcknowledgeTimeOut;
   }

   public boolean isNestedMapAndListEnabled() {
      return nestedMapAndListEnabled;
   }

   public void setNestedMapAndListEnabled(boolean structuredMapsEnabled) {
      this.nestedMapAndListEnabled = structuredMapsEnabled;
   }

   public String getClientIDPrefix() {
      return clientIDPrefix;
   }

   public void setClientIDPrefix(String clientIDPrefix) {
      this.clientIDPrefix = clientIDPrefix;
   }

   protected synchronized IdGenerator getClientIdGenerator() {
      if (clientIdGenerator == null) {
         if (clientIDPrefix != null) {
            clientIdGenerator = new IdGenerator(clientIDPrefix);
         }
         else {
            clientIdGenerator = new IdGenerator();
         }
      }
      return clientIdGenerator;
   }

   protected void setClientIdGenerator(IdGenerator clientIdGenerator) {
      this.clientIdGenerator = clientIdGenerator;
   }

   public void setConnectionIDPrefix(String connectionIDPrefix) {
      this.connectionIDPrefix = connectionIDPrefix;
   }

   protected synchronized IdGenerator getConnectionIdGenerator() {
      if (connectionIdGenerator == null) {
         if (connectionIDPrefix != null) {
            connectionIdGenerator = new IdGenerator(connectionIDPrefix);
         }
         else {
            connectionIdGenerator = new IdGenerator();
         }
      }
      return connectionIdGenerator;
   }

   protected void setConnectionIdGenerator(IdGenerator connectionIdGenerator) {
      this.connectionIdGenerator = connectionIdGenerator;
   }

   public boolean isStatsEnabled() {
      return this.factoryStats.isEnabled();
   }

   public void setStatsEnabled(boolean statsEnabled) {
      this.factoryStats.setEnabled(statsEnabled);
   }

   public synchronized int getProducerWindowSize() {
      return producerWindowSize;
   }

   public synchronized void setProducerWindowSize(int producerWindowSize) {
      this.producerWindowSize = producerWindowSize;
   }

   public long getWarnAboutUnstartedConnectionTimeout() {
      return warnAboutUnstartedConnectionTimeout;
   }

   public void setWarnAboutUnstartedConnectionTimeout(long warnAboutUnstartedConnectionTimeout) {
      this.warnAboutUnstartedConnectionTimeout = warnAboutUnstartedConnectionTimeout;
   }

   public TransportListener getTransportListener() {
      return transportListener;
   }

   public void setTransportListener(TransportListener transportListener) {
      this.transportListener = transportListener;
   }

   public ExceptionListener getExceptionListener() {
      return exceptionListener;
   }

   public void setExceptionListener(ExceptionListener exceptionListener) {
      this.exceptionListener = exceptionListener;
   }

   public int getAuditDepth() {
      return auditDepth;
   }

   public void setAuditDepth(int auditDepth) {
      this.auditDepth = auditDepth;
   }

   public int getAuditMaximumProducerNumber() {
      return auditMaximumProducerNumber;
   }

   public void setAuditMaximumProducerNumber(int auditMaximumProducerNumber) {
      this.auditMaximumProducerNumber = auditMaximumProducerNumber;
   }

   public void setUseDedicatedTaskRunner(boolean useDedicatedTaskRunner) {
      this.useDedicatedTaskRunner = useDedicatedTaskRunner;
   }

   public boolean isUseDedicatedTaskRunner() {
      return useDedicatedTaskRunner;
   }

   public void setConsumerFailoverRedeliveryWaitPeriod(long consumerFailoverRedeliveryWaitPeriod) {
      this.consumerFailoverRedeliveryWaitPeriod = consumerFailoverRedeliveryWaitPeriod;
   }

   public long getConsumerFailoverRedeliveryWaitPeriod() {
      return consumerFailoverRedeliveryWaitPeriod;
   }

   public ClientInternalExceptionListener getClientInternalExceptionListener() {
      return clientInternalExceptionListener;
   }

   public void setClientInternalExceptionListener(ClientInternalExceptionListener clientInternalExceptionListener) {
      this.clientInternalExceptionListener = clientInternalExceptionListener;
   }

   public boolean isCheckForDuplicates() {
      return this.checkForDuplicates;
   }

   public void setCheckForDuplicates(boolean checkForDuplicates) {
      this.checkForDuplicates = checkForDuplicates;
   }

   public boolean isTransactedIndividualAck() {
      return transactedIndividualAck;
   }

   public void setTransactedIndividualAck(boolean transactedIndividualAck) {
      this.transactedIndividualAck = transactedIndividualAck;
   }

   public boolean isNonBlockingRedelivery() {
      return nonBlockingRedelivery;
   }

   public void setNonBlockingRedelivery(boolean nonBlockingRedelivery) {
      this.nonBlockingRedelivery = nonBlockingRedelivery;
   }

   public int getMaxThreadPoolSize() {
      return maxThreadPoolSize;
   }

   public void setMaxThreadPoolSize(int maxThreadPoolSize) {
      this.maxThreadPoolSize = maxThreadPoolSize;
   }

   public TaskRunnerFactory getSessionTaskRunner() {
      return sessionTaskRunner;
   }

   public void setSessionTaskRunner(TaskRunnerFactory sessionTaskRunner) {
      this.sessionTaskRunner = sessionTaskRunner;
   }

   public RejectedExecutionHandler getRejectedTaskHandler() {
      return rejectedTaskHandler;
   }

   public void setRejectedTaskHandler(RejectedExecutionHandler rejectedTaskHandler) {
      this.rejectedTaskHandler = rejectedTaskHandler;
   }

   public long getOptimizedAckScheduledAckInterval() {
      return optimizedAckScheduledAckInterval;
   }

   public void setOptimizedAckScheduledAckInterval(long optimizedAckScheduledAckInterval) {
      this.optimizedAckScheduledAckInterval = optimizedAckScheduledAckInterval;
   }

   public boolean isRmIdFromConnectionId() {
      return rmIdFromConnectionId;
   }

   public void setRmIdFromConnectionId(boolean rmIdFromConnectionId) {
      this.rmIdFromConnectionId = rmIdFromConnectionId;
   }

   public boolean isConsumerExpiryCheckEnabled() {
      return consumerExpiryCheckEnabled;
   }

   public void setConsumerExpiryCheckEnabled(boolean consumerExpiryCheckEnabled) {
      this.consumerExpiryCheckEnabled = consumerExpiryCheckEnabled;
   }
}
