/**
 * 
 * Copyright 2004 Protique Ltd
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 * 
 **/
package org.activemq;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
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.activemq.broker.Broker;
import org.activemq.broker.BrokerConnector;
import org.activemq.broker.BrokerContainer;
import org.activemq.broker.BrokerContainerFactory;
import org.activemq.broker.BrokerContext;
import org.activemq.broker.impl.BrokerClientImpl;
import org.activemq.broker.impl.BrokerConnectorImpl;
import org.activemq.broker.impl.BrokerContainerFactoryImpl;
import org.activemq.io.WireFormat;
import org.activemq.io.WireFormatLoader;
import org.activemq.io.impl.DefaultWireFormat;
import org.activemq.io.util.ByteArrayCompression;
import org.activemq.io.util.ByteArrayFragmentation;
import org.activemq.jndi.JNDIBaseStorable;
import org.activemq.management.JMSStatsImpl;
import org.activemq.management.StatsCapable;
import org.activemq.management.StatsImpl;
import org.activemq.message.ActiveMQQueue;
import org.activemq.message.ActiveMQTopic;
import org.activemq.message.ConnectionInfo;
import org.activemq.message.ConsumerInfo;
import org.activemq.service.Service;
import org.activemq.transport.TransportChannel;
import org.activemq.transport.TransportChannelFactory;
import org.activemq.transport.TransportChannelListener;
import org.activemq.transport.TransportChannelProvider;
import org.activemq.transport.vm.VmTransportChannel;
import org.activemq.util.BeanUtils;
import org.activemq.util.IdGenerator;
import org.activemq.util.URIHelper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * A ConnectionFactory is an an Administed object, and is used for creating
 * Connections.
 * <p/>
 * This class also implements QueueConnectionFactory and TopicConnectionFactory and is an Administered object.
 * You can use this connection to create both QueueConnections and TopicConnections.
 *
 * @version $Revision: 1.2 $
 * @see javax.jms.ConnectionFactory
 */
public class ActiveMQConnectionFactory extends JNDIBaseStorable implements ConnectionFactory, QueueConnectionFactory, TopicConnectionFactory, Service, StatsCapable {

    private static final Log log = LogFactory.getLog(ActiveMQConnectionFactory.class);

    private BrokerContext brokerContext = BrokerContext.getInstance();
    private BrokerContainerFactory brokerContainerFactory;
    protected BrokerContainer brokerContainer;

    protected String userName;
    protected String password;
    protected String brokerURL;
    protected String clientID;
    protected String brokerName;
    private boolean useEmbeddedBroker;
    /**
     * Should we use an async send for persistent non transacted messages ?
     */
    protected boolean useAsyncSend = true;
    protected boolean disableTimeStampsByDefault = false;
    protected boolean J2EEcompliant = true;
    
    /* The list of emebeded brokers that this object started */
    private List startedEmbeddedBrokers = new ArrayList();

    private JMSStatsImpl stats = new JMSStatsImpl();
    private WireFormat wireFormat = new DefaultWireFormat();
    private IdGenerator idGenerator = new IdGenerator();
    private int connectionCount;
    private String brokerXmlConfig;
    
    
    //compression and fragmentation variables
    
    protected boolean doMessageCompression = true;
    protected int messageCompressionLimit = ByteArrayCompression.DEFAULT_COMPRESSION_LIMIT;//data size above which compression will be used
    protected int messageCompressionLevel = ByteArrayCompression.DEFAULT_COMPRESSION_LEVEL;
    protected int messageCompressionStrategy = ByteArrayCompression.DEFAULT_COMPRESSION_STRATEGY;//default compression strategy
    
    protected boolean doMessageFragmentation = true;
    protected int messageFragmentationLimit = ByteArrayFragmentation.DEFAULT_FRAGMENTATION_LIMIT;
    
    protected boolean cachingEnabled = true;
    
    protected boolean prepareMessageBodyOnSend = true; //causes pre-serialization of messages
    
    protected boolean quickClose = false;
    
    protected boolean internalConnection = false;//connections are used internally - for networks etc.

    protected boolean optimizedMessageDispatch = false;//set to true for better consumption for transient topics
    
    protected boolean copyMessageOnSend = true;//set false for better throughput
    /**
     * Default Constructor for ActiveMQConnectionFactory
     */
    public ActiveMQConnectionFactory() {
        this( ActiveMQConnection.DEFAULT_USER, ActiveMQConnection.DEFAULT_PASSWORD, ActiveMQConnection.DEFAULT_URL);
    }


    public ActiveMQConnectionFactory(String brokerURL) {
        this(ActiveMQConnection.DEFAULT_USER, ActiveMQConnection.DEFAULT_PASSWORD, brokerURL);
    }

    public ActiveMQConnectionFactory(String userName, String password, String brokerURL) {
        this.userName = userName;
        this.password = password;
        this.brokerURL = brokerURL;
        
        if( brokerURL.indexOf("?")>= 0 ) {
            String options = brokerURL.substring(brokerURL.indexOf("?")+1);
            Map properties = URIHelper.parseQuery(options);
            if (!properties.isEmpty()) {
                BeanUtils.populate(this, properties);
            }   
        }
    }

    /**
     * Constructs a {@link ConnectionFactory} with an already configured and <b>started</b> {@link BrokerContainer}
     * ready for use in embedded mode.
     *
     * @param container
     */
    public ActiveMQConnectionFactory(BrokerContainer container) {
        this(container, "vm://" + container.getBroker().getName());
    }

    /**
     * Constructs a {@link ConnectionFactory} with an already configured and <b>started</b> {@link BrokerContainer}
     * ready for use in embedded mode and the brokerURL connection.
     *
     * @param container
     */
    public ActiveMQConnectionFactory(BrokerContainer container, String brokerURL) {
        this.brokerContainer = container;
        this.useEmbeddedBroker = false;
        this.brokerURL = brokerURL;
        
        if( brokerURL.indexOf("?")>= 0 ) {
            String options = brokerURL.substring(brokerURL.indexOf("?")+1);
            Map properties = URIHelper.parseQuery(options);
            if (!properties.isEmpty()) {
                BeanUtils.populate(this, properties);
            }   
        }
    }


    public StatsImpl getStats() {
        return stats;
    }

    public JMSStatsImpl getFactoryStats() {
        return stats;
    }

    /**
     * @return Returns the brokerURL.
     */
    public String getBrokerURL() {
        return brokerURL;
    }

    /**
     * @param brokerURL The brokerURL to set.
     */
    public void setBrokerURL(String brokerURL) {
        this.brokerURL = brokerURL;
    }

    /**
     * @return Returns the clientID.
     */
    public String getClientID() {
        return clientID;
    }

    /**
     * @param clientID The clientID to set.
     */
    public void setClientID(String clientID) {
        this.clientID = clientID;
    }

    /**
     * @return Returns the password.
     */
    public String getPassword() {
        return password;
    }

    /**
     * @param password The password to set.
     */
    public void setPassword(String password) {
        this.password = password;
    }

    /**
     * @return Returns the userName.
     */
    public String getUserName() {
        return userName;
    }

    /**
     * @param userName The userName to set.
     */
    public void setUserName(String userName) {
        this.userName = userName;
    }

    /**
     * Is an embedded broker used by this connection factory
     *
     * @return true if an embedded broker will be used by this connection factory
     */
    public boolean isUseEmbeddedBroker() {
        return useEmbeddedBroker;
    }

    /**
     * Allows embedded brokers to be associated with a connection factory
     *
     * @param useEmbeddedBroker
     */
    public void setUseEmbeddedBroker(boolean useEmbeddedBroker) {
        this.useEmbeddedBroker = useEmbeddedBroker;
    }

    /**
     * The name of the broker to use if creating an embedded broker
     *
     * @return
     */
    public String getBrokerName() {
        if (brokerName == null) {
            // lets auto-create a broker name
            brokerName = idGenerator.generateId();
        }
        return brokerName;
    }
    
    /**
     * The name of the broker to use if creating an embedded broker
     *
     * @return
     */
    public String getBrokerName(String url) {
        if (brokerName == null) {
            brokerName = url;
        }
        return brokerName;
    }

    public void setBrokerName(String brokerName) {
        this.brokerName = brokerName;
    }

    /**
     * @return Returns the useAsyncSend.
     */
    public boolean isUseAsyncSend() {
        return useAsyncSend;
    }

    /**
     * @param useAsyncSend The useAsyncSend to set.
     */
    public void setUseAsyncSend(boolean useAsyncSend) {
        this.useAsyncSend = useAsyncSend;
    }

    public WireFormat getWireFormat() {
        return wireFormat.copy();//need a separate instance - especially if wire format caching enabled
    }
    
    /**
     * Set this flag for fast throughput!
     * <P>
     * Enables asynchronous sending of messages and disables timestamps by default
     * </P>
     * @param value - the flag to set
     */
    public void setTurboBoost(boolean value){
        
        disableTimeStampsByDefault = value;
        useAsyncSend = value;
        cachingEnabled = value;
        optimizedMessageDispatch = value;
        prepareMessageBodyOnSend = !value;
        copyMessageOnSend = !value;
    }
    
    /**
     * @return true if turboBoost enabled
     */
    public boolean isTurboBoost(){
        return disableTimeStampsByDefault && useAsyncSend && cachingEnabled;
    }
    
    /**
     * @return Returns the optimizedMessageDispatch.
     */
    public boolean isOptimizedMessageDispatch() {
        return optimizedMessageDispatch;
    }
    /**
     * @param optimizedMessageDispatch The optimizedMessageDispatch to set.
     */
    public void setOptimizedMessageDispatch(boolean optimizedMessageDispatch) {
        this.optimizedMessageDispatch = optimizedMessageDispatch;
    }
    /**
     * @return Returns the disableTimeStampsByDefault.
     */
    public boolean isDisableTimeStampsByDefault() {
        return disableTimeStampsByDefault;
    }
    /**
     * @param disableTimeStampsByDefault The disableTimeStampsByDefault to set.
     */
    public void setDisableTimeStampsByDefault(boolean disableTimeStampsByDefault) {
        this.disableTimeStampsByDefault = disableTimeStampsByDefault;
    }
    /**
     * @return Returns the j2EEcompliant.
     */
    public boolean isJ2EEcompliant() {
        return J2EEcompliant;
    }
    /**
     * @param ecompliant The j2EEcompliant to set.
     */
    public void setJ2EEcompliant(boolean ecompliant) {
        J2EEcompliant = ecompliant;
    }

    /**
     * @return Returns the internalConnection.
     */
    public boolean isInternalConnection() {
        return internalConnection;
    }
    /**
     * @param internalConnection The internalConnection to set.
     */
    public void setInternalConnection(boolean internalConnection) {
        this.internalConnection = internalConnection;
    }
    /**
     * @return Returns the quickClose.
     */
    public boolean isQuickClose() {
        return quickClose;
    }
    /**
     * @param quickClose The quickClose to set.
     */
    public void setQuickClose(boolean quickClose) {
        this.quickClose = quickClose;
    }
    /**
     * @return Returns the doMessageCompression.
     */
    public boolean isDoMessageCompression() {
        return doMessageCompression;
    }
    /**
     * @param doMessageCompression The doMessageCompression to set.
     */
    public void setDoMessageCompression(boolean doMessageCompression) {
        this.doMessageCompression = doMessageCompression;
    }
    /**
     * @return Returns the doMessageFragmentation.
     */
    public boolean isDoMessageFragmentation() {
        return doMessageFragmentation;
    }
    /**
     * @param doMessageFragmentation The doMessageFragmentation to set.
     */
    public void setDoMessageFragmentation(boolean doMessageFragmentation) {
        this.doMessageFragmentation = doMessageFragmentation;
    }
    /**
     * @return Returns the messageCompressionLimit.
     */
    public int getMessageCompressionLimit() {
        return messageCompressionLimit;
    }
    /**
     * @param messageCompressionLimit The messageCompressionLimit to set.
     */
    public void setMessageCompressionLimit(int messageCompressionLimit) {
        this.messageCompressionLimit = messageCompressionLimit;
    }
    /**
     * @return Returns the messageCompressionStrategy.
     */
    public int getMessageCompressionStrategy() {
        return messageCompressionStrategy;
    }
    /**
     * @param messageCompressionStrategy The messageCompressionStrategy to set.
     */
    public void setMessageCompressionStrategy(int messageCompressionStrategy) {
        this.messageCompressionStrategy = messageCompressionStrategy;
    }
    /**
     * @return Returns the messageFragmentationLimit.
     */
    public int getMessageFragmentationLimit() {
        return messageFragmentationLimit;
    }
    /**
     * @param messageFragmentationLimit The messageFragmentationLimit to set.
     */
    public void setMessageFragmentationLimit(int messageFragmentationLimit) {
        this.messageFragmentationLimit = messageFragmentationLimit;
    }
    
    /**
     * @return Returns the cachingEnabled.
     */
    public boolean isCachingEnabled() {
        return cachingEnabled;
    }
    /**
     * @param cachingEnabled The cachingEnabled to set.
     */
    public void setCachingEnabled(boolean cachingEnabled) {
        this.cachingEnabled = cachingEnabled;
    }
    /**
     * Causes pre-serialization of messages before send
     * By default this is on
     * @return Returns the prePrepareMessageOnSend.
     */
    public boolean isPrepareMessageBodyOnSend() {
        return prepareMessageBodyOnSend;
    }
    /**
     * Causes pre-serialization of messages before send
     * By default this is on
     * @param prePrepareMessageOnSend The prePrepareMessageOnSend to set.
     */
    public void setPrepareMessageBodyOnSend(boolean prePrepareMessageOnSend) {
        this.prepareMessageBodyOnSend = prePrepareMessageOnSend;
    }
    /**
     * @return Returns the copyMessageOnSend.
     */
    public boolean isCopyMessageOnSend() {
        return copyMessageOnSend;
    }
    /**
     * @param copyMessageOnSend The copyMessageOnSend to set.
     */
    public void setCopyMessageOnSend(boolean copyMessageOnSend) {
        this.copyMessageOnSend = copyMessageOnSend;
    }
    /**
     * Allows a custom wire format to be used; otherwise the default Java wire format is used
     * which is designed for minimum size and maximum speed on the Java platform
     *
     * @param wireFormat
     */
    public void setWireFormat(WireFormat wireFormat) {
        this.wireFormat = wireFormat;
    }
    
    /**
     * set the WireFormat by name - e.g. 'default','amqpfast' etc.
     * 
     * @param format
     * @throws JMSException
     */
 
    public void setWireFormat(String format) throws JMSException{
        this.wireFormat = WireFormatLoader.getWireFormat(format);
    }

    public String getBrokerXmlConfig() {
        return brokerXmlConfig;
    }

    public BrokerContainer getBrokerContainer() {
        return brokerContainer;
    }

    /**
     * Sets the <a href="http://activemq.org/Xml+Configuration">XML configuration file</a>
     * used to configure the ActiveMQ broker via Spring if using embedded mode.
     *
     * @param brokerXmlConfig is the filename which is assumed to be on the classpath unless a URL
     *                        is specified. So a value of <code>foo/bar.xml</code> would be assumed to be on the classpath
     *                        whereas <code>file:dir/file.xml</code> would use the file system.
     *                        Any valid URL string is supported.
     * @see #setUseEmbeddedBroker(boolean)
     */
    public void setBrokerXmlConfig(String brokerXmlConfig) {
        this.brokerXmlConfig = brokerXmlConfig;
    }

    public BrokerContainerFactory getBrokerContainerFactory() throws JMSException {
        if (brokerContainerFactory == null) {
            brokerContainerFactory = createBrokerContainerFactory();
        }
        return brokerContainerFactory;
    }

    public void setBrokerContainerFactory(BrokerContainerFactory brokerContainerFactory) {
        this.brokerContainerFactory = brokerContainerFactory;
    }

    /**
     * Returns the context used to store broker containers and connectors which defaults
     * to using the singleton
     */
    public BrokerContext getBrokerContext() {
        return brokerContext;
    }

    public void setBrokerContext(BrokerContext brokerContext) {
        this.brokerContext = brokerContext;
    }

    /**
     * Create a JMS Connection
     *
     * @return the JMS Connection
     * @throws JMSException if an error occurs creating the Connection
     */
    public Connection createConnection() throws JMSException {
        return this.createConnection(this.userName, this.password);
    }

    /**
     * @param userName
     * @param password
     * @return the Connection
     * @throws JMSException if an error occurs creating the Connection
     */
    public Connection createConnection(String userName, String password) throws JMSException {
        ActiveMQConnection connection = new ActiveMQConnection(this, userName, password, createTransportChannel(this.brokerURL));
        connection.setCachingEnabled(isCachingEnabled());
        connection.setUseAsyncSend(isUseAsyncSend());
        connection.setDisableTimeStampsByDefault(isDisableTimeStampsByDefault());
        connection.setJ2EEcompliant(isJ2EEcompliant());
        connection.setDoMessageCompression(isDoMessageCompression());
        connection.setMessageCompressionLevel(messageCompressionLevel);
        connection.setMessageCompressionLimit(getMessageCompressionLimit());
        connection.setMessageCompressionStrategy(getMessageCompressionStrategy());
        connection.setDoMessageFragmentation(isDoMessageFragmentation());
        connection.setMessageFragmentationLimit(getMessageFragmentationLimit());
        connection.setPrepareMessageBodyOnSend(isPrepareMessageBodyOnSend());
        connection.setInternalConnection(isInternalConnection());
        connection.setQuickClose(isQuickClose());
        connection.setOptimizedMessageDispatch(isOptimizedMessageDispatch());
        connection.setCopyMessageOnSend(isCopyMessageOnSend());
        if (this.clientID != null && this.clientID.length() > 0) {
            connection.setClientID(this.clientID);
        }
        return connection;
    }

    /**
     * Create a JMS QueueConnection
     *
     * @return the JMS QueueConnection
     * @throws JMSException if an error occurs creating the Connection
     */
    public QueueConnection createQueueConnection() throws JMSException {
        return this.createQueueConnection(this.userName, this.password);
    }

    /**
     * @param userName
     * @param password
     * @return the QueueConnection
     * @throws JMSException if an error occurs creating the Connection
     */
    public QueueConnection createQueueConnection(String userName, String password) throws JMSException {
        return (QueueConnection) createConnection(userName, password);
    }

    /**
     * Create a JMS TopicConnection
     *
     * @return the JMS TopicConnection
     * @throws JMSException if an error occurs creating the Connection
     */
    public TopicConnection createTopicConnection() throws JMSException {
        return this.createTopicConnection(this.userName, this.password);
    }

    /**
     * @param userName
     * @param password
     * @return the TopicConnection
     * @throws JMSException if an error occurs creating the Connection
     */
    public TopicConnection createTopicConnection(String userName, String password) throws JMSException {
        return (TopicConnection) createConnection(userName, password);
    }


    public void start() throws JMSException {
    }

    /**
     * A hook to allow any embedded JMS Broker's to be closed down
     *
     * @throws JMSException
     */
    public synchronized void stop() throws JMSException {
        // Stop all embded brokers that we started.
        for (Iterator iter = startedEmbeddedBrokers.iterator(); iter.hasNext();) {
            String uri = (String) iter.next();
            brokerContext.deregisterConnector(uri);
        }
        if (brokerContainer != null) {
            brokerContainer.stop();
            brokerContainer = null;
        }
    }


    public Broker getEmbeddedBroker() throws JMSException {
        if (isUseEmbeddedBroker()) {
            return getContainer(getBrokerName(), getBrokerName()).getBroker();
        }
        return null;
    }

    public static synchronized void registerBroker(String theURLString, BrokerConnector brokerConnector) {
        BrokerContext.getInstance().registerConnector(theURLString, brokerConnector);
    }

    public static synchronized void unregisterBroker(String theURLString) {
        BrokerContext.getInstance().deregisterConnector(theURLString);
    }


    // Implementation methods
    //-------------------------------------------------------------------------


    /**
     * Set the properties that will represent the instance in JNDI
     *
     * @param props
     */
    protected void buildFromProperties(Properties props) {
        this.userName = props.getProperty("userName", this.userName);
        this.password = props.getProperty("password", this.password);
        String temp = props.getProperty(Context.PROVIDER_URL);
        if (temp == null || temp.length() == 0) {
             temp = props.getProperty("brokerURL");
        }
        if (temp != null && temp.length() > 0) {
            this.brokerURL = temp;
        }
        this.brokerName = props.getProperty("brokerName", this.brokerName);
        this.clientID = props.getProperty("clientID");
        this.useAsyncSend = getBoolean(props, "useAsyncSend", true);
        this.useEmbeddedBroker = getBoolean(props, "useEmbeddedBroker");
        this.brokerXmlConfig = props.getProperty("brokerXmlConfig", this.brokerXmlConfig);
        this.J2EEcompliant = getBoolean(props,"J2EEcompliant",true);
        if (props.containsKey("turboBoost")){
            this.setTurboBoost(getBoolean(props, "turboBoost"));
        }
    }

    /**
     * Initialize the instance from properties stored in JNDI
     *
     * @param props
     */
    protected void populateProperties(Properties props) {
        props.put("userName", this.userName);
        props.put("password", this.password);
        props.put("brokerURL", this.brokerURL);
        props.put(Context.PROVIDER_URL, this.brokerURL);
        props.put("brokerName", this.brokerName);
        if (this.clientID != null) {
            props.put("clientID", this.clientID);
        }
        props.put("useAsyncSend", (useAsyncSend) ? "true" : "false");
        props.put("useEmbeddedBroker", (useEmbeddedBroker) ? "true" : "false");
        props.put("J2EEcompliant", (this.J2EEcompliant) ? "true" : "false");
        props.put("turboBoost", (isTurboBoost()) ? "true" : "false");
        if (this.brokerXmlConfig != null) {
            props.put("brokerXmlConfig", this.brokerXmlConfig);
        }
    }

    /**
     * Helper method to return the property value as a boolean flag
     *
     * @param props
     * @param key
     * @return
     */
    protected boolean getBoolean(Properties props, String key) {
        return getBoolean(props, key, false);
    }

    /**
     * Helper method to return the property value as a boolean flag
     *
     * @param props
     * @param key
     * @param defaultValue
     * @return
     */
    protected boolean getBoolean(Properties props, String key, boolean defaultValue) {
        String value = props.getProperty(key);
        return value != null ? value.equalsIgnoreCase("true") : defaultValue;
    }

    protected BrokerContainerFactory createBrokerContainerFactory() throws JMSException {
        if (brokerXmlConfig != null) {
            return XmlConfigHelper.createBrokerContainerFactory(brokerXmlConfig);
        }
        return new BrokerContainerFactoryImpl();
    }

    /**
     * Factory method to create a TransportChannel from a URL
     * @param theURLString
     * @return the TransportChannel to use with the embedded broker
     * @throws JMSException
     */
    protected TransportChannel createTransportChannel(String theURLString) throws JMSException {
        URI uri = createURI(theURLString);
        TransportChannelFactory factory = TransportChannelProvider.getFactory(uri);
        BrokerConnector brokerConnector = null;
        boolean created = false;
        TransportChannel transportChannel = null;
        boolean embedServer = isUseEmbeddedBroker() || factory.requiresEmbeddedBroker();
        if (embedServer) {
            synchronized (this) {
                if (factory.requiresEmbeddedBroker()) {
                    transportChannel = factory.create(getWireFormat(), uri);
                    brokerConnector = transportChannel.getEmbeddedBrokerConnector();
                }
                if (brokerConnector == null) {
                    brokerConnector = brokerContext.getConnectorByURL(theURLString);
                    if (brokerConnector == null) {
                        brokerConnector = createBrokerConnector(theURLString);
                        brokerContext.registerConnector(theURLString, brokerConnector);
                        startedEmbeddedBrokers.add(theURLString);
                        created = true;
                    }
                }
                else {
                    created = true;
                }
            }
        }
        if (transportChannel == null){
            transportChannel = factory.create(getWireFormat(), uri);
        }
       
        if (embedServer) {
            return ensureServerIsAvailable(uri, transportChannel, brokerConnector, created);
        }
        return transportChannel;
    }

    protected synchronized BrokerContainer getContainer(String url, String name) throws JMSException {
        if (brokerContainer == null) {
            brokerContainer = brokerContext.getBrokerContainerByName(url, name, getBrokerContainerFactory());
        }
        return brokerContainer;
    }

    protected BrokerConnector createBrokerConnector(String url) throws JMSException {
        BrokerConnector brokerConnector;
        brokerConnector = new BrokerConnectorImpl(getContainer(url, getBrokerName()), url, getWireFormat());
        brokerConnector.start();

        // lets wait a little for the server to startup
        log.info("Embedded JMS Broker has started");
        try {
            Thread.sleep(1000);
        }
        catch (InterruptedException e) {
            System.out.println("Caught: " + e);
            e.printStackTrace();
        }
        return brokerConnector;
    }


    protected TransportChannel ensureServerIsAvailable(URI remoteLocation, TransportChannel channel, BrokerConnector brokerConnector, boolean created) throws JMSException {
        ensureVmServerIsAvailable(channel, brokerConnector);
        if (channel.isMulticast()) {
            return ensureMulticastChannelIsAvailable(remoteLocation, channel, brokerConnector, created);
        }
        return channel;
    }

    private void ensureVmServerIsAvailable(TransportChannel channel, BrokerConnector brokerConnector) throws JMSException {
        if (channel instanceof VmTransportChannel && brokerConnector instanceof TransportChannelListener) {
            VmTransportChannel answer = (VmTransportChannel) channel;
            answer.connect(brokerConnector);
        }
    }

    protected TransportChannel ensureMulticastChannelIsAvailable(URI remoteLocation, TransportChannel channel, BrokerConnector brokerConnector, boolean created) throws JMSException {
        if (created) {
            BrokerConnectorImpl brokerImpl = (BrokerConnectorImpl) brokerConnector;

            BrokerClientImpl client = new BrokerClientImpl();
            client.initialize(brokerImpl, channel);
            channel.start();
            String brokerClientID = createMulticastClientID();
            channel.setClientID(brokerClientID);

            // lets spoof a consumer for topics which will replicate messages
            // over the multicast transport
            ConnectionInfo info = new ConnectionInfo();
            info.setHostName(IdGenerator.getHostName());
            info.setClientId(brokerClientID);
            info.setStarted(true);
            client.consumeConnectionInfo(info);

            ConsumerInfo consumerInfo = new ConsumerInfo();
            consumerInfo.setDestination(new ActiveMQTopic(">"));
            consumerInfo.setNoLocal(true);
            consumerInfo.setClientId(brokerClientID);
            consumerInfo.setConsumerId(idGenerator.generateId());
            consumerInfo.setStarted(true);
            client.consumeConsumerInfo(consumerInfo);

            consumerInfo = new ConsumerInfo();
            consumerInfo.setDestination(new ActiveMQQueue(">"));
            consumerInfo.setNoLocal(true);
            consumerInfo.setClientId(brokerClientID);
            consumerInfo.setConsumerId(idGenerator.generateId());
            consumerInfo.setStarted(true);
            client.consumeConsumerInfo(consumerInfo);
        }

        // now lets create a VM channel that the JMS client will use
        // to connect to the embedded brokerConnector
        URI localURI = createURI("vm", remoteLocation);
        TransportChannel localChannel = TransportChannelProvider.create(getWireFormat(), localURI);
        ensureVmServerIsAvailable(localChannel, brokerConnector);
        return localChannel;
    }

    /**
     * Creates the clientID for the multicast client (used to dispatch local
     * messages over a multicast bus)
     */
    protected String createMulticastClientID() {
        return idGenerator.generateId();
    }

    protected URI createURI(String protocol, URI uri) throws JMSException {
        try {
            return new URI(protocol, uri.getRawSchemeSpecificPart(), uri.getFragment());
        }
        catch (URISyntaxException e) {
            JMSException jmsEx = new JMSException("the URL string is badly formated:", e.getMessage());
            jmsEx.setLinkedException(e);
            throw jmsEx;

        }
    }

    protected URI createURI(String uri) throws JMSException {
        try {
            if (uri == null) {
                throw new JMSException("The connection URI must be specified!");
            }
            return new URI(uri);
        }
        catch (URISyntaxException e) {
            e.printStackTrace();
            JMSException jmsEx = new JMSException("the URL string is badly formated:", e.getMessage());
            jmsEx.setLinkedException(e);
            throw jmsEx;

        }
    }

    /**
     * Called when a connection is closed so that we can shut down any embedded brokers cleanly
     *
     * @param connection
     */
    synchronized void onConnectionClose(ActiveMQConnection connection) throws JMSException {
        if (--connectionCount <= 0) {
            // close any broker if we've got one
            stop();
        }

    }

    synchronized void onConnectionCreate(ActiveMQConnection connection) {
        ++connectionCount;
    }
    
}
